RSA加解密的OAEP MGF1 填充解析

RSA加解密的OAEP MGF1 填充解析

加密时的填充

PKCS#1 v2.1: RSA密码学规范中关于 OAEP的模式的讲解如下:

RSAES-OAEP-ENCRYPT  (( n,  e),  M, L )
可选:  Hash  哈希函数(hLen代表哈希函数的输出字节数)
MGF  掩码生成函数
输入:
(n,e)  输入的RSA公钥(k代表RSA模数n的字节长度)
M  待加密的数据, 一个长度为mLen的字节串,并且mLen<=k-2hLen-2
L  可选的和消息关联的标签;如果L没有提供,默认的值是空串
输出:
C  加密输出,长度为k的字节串
错误:  “消息太长”;“标签太长”
假设:  RSA公钥(n,e)是有效的。
操作:
1.长度检查
a.如果L的长度超过了哈希函数的输入限制(SHA-1是2^61-1字节),输出"标签太长“并中止。
b.如果mLen>k-2hLen-2,输出“消息太长”并中止。
2.EME-OAEP编码
a.如果标签L没有提供,使得L为空串。使得lHash=Hash(L),一个长度为hLen的字节串。
b.生成一个字节串PS,由k-mLen-2hLen-2个字节零组成,PS的长度有可能是0。
c.连接lHash,PS,一个单字节值为0x01和消息M,形成一个长度为k-hLen-1的字节串: DB = lHash || PS || 0x01 || M
d.生成一个长度为hLen的随机字节串种子seed。
e.使得 dbMask = MGF(seed, k-hLen-1)
f.使得 maskedDB = DB ⊕ dbMask.
g.使得 seedMask = MGF(maskedDB, hLen)
h.使得 maskedSeed = seed ⊕ seedMask
i.级联值为0x00的单字节,maskedSeed,和maskedDB形成长度为k的消息EM: EM = 0x00 || maskedSeed || maskedDB

代码如下:

byte[] lHash = SHAUtils.encrypt("", "SHA-256");//hash 根据选择 。前面的消息就睡上文的L,这里采用空串。
byte[] ps = new byte[publicKey.getModulus().bitLength() / 8
                - data.length - 2 * lHash.length - 2];  //需要填充的部分
for (int i = 0; i < ps.length; i++) {//全部填充为0
  ps[i] = 0;
 }
byte[] DB = new byte[publicKey.getModulus().bitLength() / 8
                - lHash.length - 1];//生成DB长度
 System.arraycopy(lHash, 0, DB, 0, lHash.length);//开始是hash
System.arraycopy(ps, 0, DB, lHash.length, ps.length);//填充字符
 DB[lHash.length + ps.length] = 1;//0x1作为间隔符
System.arraycopy(data, 0, DB, lHash.length + ps.length + 1, data.length); //真实的加密数据
 byte[] seed = SecureRandom.getSeed(lHash.length);//生成seed
 MGF1 mgf1 = new MGF1(MessageDigest.getInstance("SHA-1"));
 byte[] dbMask = mgf1.generateMask(seed, publicKey.getModulus()
                .bitLength() / 8 - lHash.length - 1);
// DB和dbMask做异或运算生成maskedDB
 byte[] maskedDB = new BigInteger(DB).xor(new BigInteger(dbMask))
                .toByteArray();
 byte[] seedMask = mgf1.generateMask(maskedDB, lHash.length);
 // seed和seedMask做异或运算生成maskedSeed
 byte[] maskedSeed = new BigInteger(seed).xor(new BigInteger(seedMask))
                .toByteArray();
byte[] EM = new byte[publicKey.getModulus().bitLength() / 8];
EM[0] = 0;//开始位置0
System.arraycopy(maskedSeed, 0, EM, 1, maskedSeed.length); System.arraycopy(maskedDB, 0, EM, maskedSeed.length + 1,  maskedDB.length);

所以为什么OAEP的长度限制是mLen<=k-2hLen-2
EM = 0x00 || maskedSeed || maskedDB

解密时的填充

规则如下:

a.如果标签L没有提供,使得L作为空串。使得 lHash = Hash(L), 一个长度为hLen的字节串。
b.将编码消息EM切分为一个字节Y,一个长度为hLen的字节串maskedSeed,和一个长度为k-hLen-1的字节串maskedDB
 EM = Y || maskedSeed || maskedDB
c.使得 seedMask = MGF(maskedDB, hLen)
 d.使得 seed = maskedSeed ⊕  seedMask
 e.使得 dbMask = MGF(seed, k-h-1)
 f.使得 DB = maskedDB  ⊕  dbMask
 g.将DB分成长度为lHash的字节串,一个由0组成的填充串,和消息M
DB = lHash' || PS || 0x01 || M
如果没有值为0x01的数据将PS和M分隔开,如果lHash不等于lHash',或者如果Y非零,输出“解密错误“并停止。

代码如下:

//cache是解密出来的数据
 byte Y = cache[0];
 if (Y != 0) {//首位不是0,解密失败
   throw new Exception("error");
 }
   byte[] maskSeed = Arrays.copyOfRange(cache, 1, hashLength+1);
 byte[] maskDB = Arrays.copyOfRange(cache, hashLength + 1, cache.length);
 MGF1 mgf1 = new MGF1(MessageDigest.getInstance("SHA-1"));//与加密时候的MGF1相同
byte[] seedMask = mgf1.generateMask(maskDB, hashLength);
 byte[] seed = new BigInteger(maskSeed).xor(new BigInteger(seedMask)).toByteArray();
 byte[] dbMask = mgf1.generateMask(seed, 256 - hashLength - 1);//这里的256是因为我的密钥是2048
 byte[] DB = new BigInteger(maskDB).xor(new BigInteger(dbMask)).toByteArray();
 byte[] lHash = SHAUtils.encrypt("", "SHA-256");//与加密的时候相同,L值
 int index = -1;
 for (int k = 0; k < hashLength; k++) {
	 if (lHash[k] != DB[k]){//比较hash,hash不对的话,解密失败
		 throw  new Exception("hash error");
	   }
 }
for (int j = hashLength; j < DB.length; j++) {//从hash后面开始找1
 if (DB[j] == 1) {
   	index = j + 1;
	 break;
 }
 }
 byte[] data = Arrays.copyOfRange(DB, index, DB.length);//1后面就是明文数据

最后

以上,我们完成了OAEP的填充和反解。使用的时候采用RSA/ECB/NOPADDING,就可以正常进行。

1。Android的KeyStore系统不支持传入:L 可选的和消息关联的标签
如果需要这部分,就需要自己完成填充和反解。
2.RSA/ECB/PKCS1Padding 这个模式:应小心在长数据加密过程中基于 Coppersmith, Franklin, Patarin, 和 Reiter的小指数RSA攻击。作为一般规则,此方案用于加密普通的消息,而不建议用于加密随机生成的密钥。它有一定概率成功的在不知道对应的明文的前提下产生有效的RSAES-PKCS-v1_5的密文。这个能力可能被密文选择性攻击利用。因此如果采用RSAES-PKCS1_v1_5,应采取一些容易实施的对策阻扰攻击。
典型的例子是在数据中加入附加结构,在解密的消息中严格检查PKCS#1 v1.5的一致性(和其它冗余)

a.生成长度为k-mLen-3的字节串PS包含随机生成的非零的伪随机数。PS的长度最小为8个字节。
b.级联PS,消息M和其它的填充组成长度为k的消息EM
EM = 0x00 || 0x02 || PS || 0x00 || M

数据比较固定。

附录 MGF1代码

import java.security.MessageDigest;

/**
 * 掩模生成函数
 * mask generator function, as described in PKCS1v2.
 */
public class MGF1
{
    private MessageDigest digest;

    /**
     * Create a version of MGF1 for the given digest.
     *
     * @param digest
     *            digest to use as the basis of the function.
     */
    public MGF1(MessageDigest digest)
    {
        this.digest = digest;
    }

    /**
     * int to octet string.
     */
    private void I2OSP(int i, byte[] sp)
    {
        sp[0] = (byte) (i >>> 24);
        sp[1] = (byte) (i >>> 16);
        sp[2] = (byte) (i >>> 8);
        sp[3] = (byte) (i >>> 0);
    }

    /**
     * Generate the mask.
     *
     * @param seed
     *            source of input bytes for initial digest state
     * @param length
     *            length of mask to generate
     *
     * @return a byte array containing a MGF1 generated mask
     */
    public byte[]
    generateMask(byte[] seed, int length)
    {
        byte[] mask = new byte[length];
        byte[] C = new byte[4];
        int counter = 0;
        int hLen = digest.getDigestLength();
        digest.reset();
        while (counter < (length / hLen))
        {
            I2OSP(counter, C);
            digest.update(seed);
            digest.update(C);
            System.arraycopy(digest.digest(), 0, mask, counter * hLen, hLen);

            counter++;
        }
        if ((counter * hLen) < length)
        {
            I2OSP(counter, C);
            digest.update(seed);
            digest.update(C);
            System.arraycopy(digest.digest(), 0, mask, counter * hLen,
                    mask.length - (counter * hLen));
        }
        return mask;
    }
}
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值