RSA的PSCK1 和 PSS 加密、签名封装以及填充方式和实例程序

RSA的PSCK1 和 PSS 加密、签名封装以及填充方式

SSA = Signature Scheme with Appendix
PSS = Probabilistic Signature Scheme
ES = Encryption Schemes
SSA是填充、封装格式
PSS是私钥签名流程。
ES 是公钥加密流程。

https://tools.ietf.org/html/rfc8017 定义了PKCS规范
这里描述其中 和本文有关的的内容

为什么RSA加密、签名需要填充?

主要还是为了安全性考虑。
1:
例如TLS流程中的RSA公钥加密(client key exchange),client会使用随即算法生成2+46=48字节的pre_master_key。
若不进行填充而直接加密,那么显然相同的pre_master_key,会得到相同的密文。这种在语义上来说,是不安全的。

2:
加密流程
加密方加密m:c = m^e mod n,传输c
解密方解密c:m = c^d mod n,还原m
由于c在网络上传输,如果网络上有人对其进行c' = c*k^e mod n,这样的替换
那么解密方得到的结果是

(c*k^e)^d mod n
= c^d mod n * k^ed mod n
= m*k

即中间人有办法控制m。

RSAES-PKCS1-v1_5 加密流程

作用:RSA公钥加密

加密

1:待加密数据为M,规范要求M必须不大于k-11,其中k是模数n的字节数。
2:若1满足,则计算不存在字节0的随机值PS,显然根据下面的等式可以推算出,PS的长度是 k - M_len - 3
EM = 0x00 || 0x02 || PS || 0x00 || M
EM作为RSA运算的底数M,进行运算。C = EM ^e mod n

解密

1:校验C的长度,C必须是k字节长度。
2:C ^d mod n得到EM
EM理论上是0x00 || 0x02 || PS || 0x00 || M这种格式的,所以校验的方法也相对比较简单。
先判断开头2字节是否是0x00 0x02,然后找到第一个0x00,这个0x00后面的值就是解密后的明文。

RSASSA-PKCS1-V1_5-SIGN 签名流程

该签名流程,使用了EMSA-PKCS1-v1_5 封装格式

RSA签名填充

例如需要签名一段数据 M,其长度m_len。
(下面1-3 是 EMSA-PKCS1-v1_5封装流程,4 是 RSASSA-PKCS1-V1_5-SIGN 签名流程。)

1:计算M的哈希值,H = hash(M),哈希可能是MD5、SHA1、SHA2等算法。
2:H并不会简单的进行模幂运算,而是需要进行封装后才会进行。
RFC上这么描述,它需要ASN1编码

   DigestInfo ::= SEQUENCE {
       digestAlgorithm AlgorithmIdentifier,
       digest OCTET STRING
   }

翻译成C语言的话,就是下面段数据T的数据组织(OID指的具体HASH算法的id,oid_size表示这个id的长度)

p = T
*p++ = ASN1_SEQUENCE | ASN1_CONSTRUCTED;
*p++ = (unsigned char) ( 0x08 + oid_size + hashlen );
*p++ = ASN1_SEQUENCE | ASN1_CONSTRUCTED;
*p++ = (unsigned char) ( 0x04 + oid_size );
*p++ = ASN1_OID;
*p++ = oid_size & 0xFF;
*memcpy( p, oid, oid_size );
*p += oid_size;
*p++ = ASN1_NULL;
*p++ = 0x00;
*p++ = ASN1_OCTET_STRING;
*p++ = hashlen;
*memcpy( p, H, hashlen );//哈希值在这里

熟悉ASN1格式的同学必然觉得这个很简单,但是不熟悉ASN1格式的同学肯定看到了一头雾水。

换个角度来看T,如果哈希算法确定,即oid确定,那么hashlen之前的数据都是确定的值。的确如此:

RFC中举例了各个常用算法组织成的 T

MD2:     (0x)30 20 30 0c 06 08 2a 86 48 86 f7 0d 02 02 05 00 04
             10 || H.

MD5:     (0x)30 20 30 0c 06 08 2a 86 48 86 f7 0d 02 05 05 00 04
             10 || H.

SHA-1:   (0x)30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14 || H.

SHA-224:  (0x)30 2d 30 0d 06 09 60 86 48 01 65 03 04 02 04
              05 00 04 1c || H.

SHA-256: (0x)30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00
             04 20 || H.

SHA-384: (0x)30 41 30 0d 06 09 60 86 48 01 65 03 04 02 02 05 00
             04 30 || H.

SHA-512: (0x)30 51 30 0d 06 09 60 86 48 01 65 03 04 02 03 05 00
             04 40 || H.

SHA-512/224:  (0x)30 2d 30 0d 06 09 60 86 48 01 65 03 04 02 05
                  05 00 04 1c || H.
SHA-512/256:  (0x)30 31 30 0d 06 09 60 86 48 01 65 03 04 02 06
                  05 00 04 20 || H.

不熟悉ASN1格式的新同学,就认为H需要如上的由HASH算法决定的定值进行填充即可,但是这不会对拓展你的知识面有帮助。

3:EM = 0x00 || 0x01 || PS || 0x00 || T

到此,EMSA-PKCS1-v1_5规范定义完毕,下一步才是真正的签名

4:计算C = EM^d mod n,得到C就是签名的最后结果。

验证RSA签名

这里不赘述了,将上面的流程逆过就行了。

RSASSA-PSS 签名流程

作用:RSA私钥签名
RSASSA-PSS 签名流程 使用了 EMSA-PSS 封装格式。
我们先描述 EMSA-PSS 封装格式,因为这是 RSASSA-PSS签名流程的一部分。

签名

若要对数据M进行签名
步骤:
1:计算 mHash = Hash(M),其长度为hashlen
2:计算slen长度的随机值salt,这里slen取hashlen。
3:计算 M' = Hash(00 00 00 00 00 00 00 00 || mHash || salt)
4:计算 DB = PS || 0x01 || salt,PS是字节0。PS的长度是nLen - 2*hashlen - 2 。然后
到这里,待签名数据的内存布局如下

ptr = 00 00 00 ... 00 00 || 0x01 || salt || M' || 0xBC
看上面这种内存布局,也就好理解为什么PS的长度是 nLen - 2*hashlen - 2 了。

5:进行MGF运算

5-1:
计算hash值mask

unsigned char counter[4] = {0};
mask = Hash(M' || counter);
counter++;

5-2:

ptr ^= mask; 
ptr += hashlen;

5-3:
执行5-1,5-2,直到 ptr 中的 salt 也被 mask 亦或运算 为止。最后的结果是 EM。
EM = maskDB || M' || 0xBC
换句话说,不考虑counter以及hash函数,maskDB, 是由M’亦或得到的。

6:执行 EM^d mod n 得到签名结果C。

注意:RFC上,对EM的首字节还需要特殊处理,但是实际应用中,都是一个固定的操作,这里不说了。(具体见 9.1.1 节的 step 11 )。

上述1-5的步骤,我们称之为 EMSA-PSS 封装格式,使用流程图描述:

                                 +-----------+
                                 |     M     |
                                 +-----------+
                                       |
                                       V
                                     Hash
                                       |
                                       V
                         +--------+----------+----------+
                    M' = |Padding1|  mHash   |   salt   |
                         +--------+----------+----------+
                                        |
              +--------+----------+     V
        DB =  |Padding2|   salt   |   Hash
              +--------+----------+     |
                        |               |
                        V               |
                       xor <--- MGF <---|
                        |               |
                        |               |
                        V               V
              +-------------------+----------+--+
        EM =  |    maskedDB       |     H    |bc|
              +-------------------+----------+--+

验签

1:解密C,得到的结果是 EM。
2:解封装EM。EM解密出的结果是EM = maskDB || M' || 0xBC

验证签名没有EMSA-PKCS1-v1_5那么简单,因为EMSA-PKCS1-v1_5封装格式中没有随机值。所以这里需要进行文字描述。

EMSA-PSS解封装的核心就是需要恢复salt,就是封装时的那个随机值。
如何恢复salt, 我们回顾签名第四步的ptr,salt就是M’前的hashlen字节的值,只是被mask亦或了。我们只需要再被mask亦或一次,就能恢复salt。

那mask是什么,我们回顾签名第5-1步,mask = Hash(M' || counter);,我们的EM
中是存在M’的,所以,验证签名的一方能够计算mask。

换句话说,dataA ^ dataB = dataC,dataC ^ dataB = dataA,这个是简单的数学原理。既然我们有 dataC(EM), dataB(hash(M’+counter)),自然能得到 DataA。

3:执行MGF, 也就是签名流程的第5步,他的结果就是我们能够得到 签名流程第四步的ptr。即能够得到salt。

4:至此我们从EM得到salt,M’,然后我们的入参又有 M,接下来的步骤就是使用 salt 和 M 生成, M”,理论上
M” 和 M’ 是相等的。这就是验证的流程。

示例程序

下面是RSASSA-PSS签名使用的填充方式;
说明:
1:示例程序没有进行签名操作,只进行了
EMSA-PSS的encode和decode。
2:示例程序固定使用了sha256进行hash计算。
3:示例程序的输出256字节。

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
unsigned char tosigned[] = "aaaaaaaaaaaa\n";
unsigned char encoded[256]={0};
unsigned int hash_len;
const EVP_MD *md;

void mgf1(unsigned char *dst, unsigned int dst_len, unsigned char *src, unsigned int src_len)
{
    unsigned char tmp[EVP_MAX_MD_SIZE] = {0}, tmp2[EVP_MAX_MD_SIZE];
    unsigned char *p = dst, *ctr;
    unsigned int mask_len, hash_len = src_len, i;

    memcpy(tmp, src, src_len);
    ctr = tmp + src_len;

    while((int)dst_len > 0)
    {
        SHA256(tmp, src_len + 4, tmp2);
        mask_len = dst_len < hash_len ? dst_len : hash_len;

        for(i = 0; i < mask_len; i++)
        {
            *p++ ^= tmp2[i];
        }
        dst_len -= mask_len;
        ctr[3]++;
    }
}

void decode(unsigned char *rsa2048)
{
    unsigned int encodelen = 256;
    unsigned char mHash[EVP_MAX_MD_SIZE], selfmap[EVP_MAX_MD_SIZE];
    unsigned char tmp[8 + EVP_MAX_MD_SIZE*2]={0};
    unsigned char *map, *salt;

    /*这个函数应该是decode前被调用,而不是decode中*/
    SHA256(tosigned, strlen(tosigned), mHash);
    /*get M' */
    map = rsa2048 + encodelen - 1 - hash_len;
    /*use M' to do xor*/
    mgf1(rsa2048, encodelen - 1 - hash_len, map, hash_len);
    /*recover the salt in encoded buffer*/
    salt = rsa2048 + encodelen - 1 - hash_len*2;

    /*calc self M' */
    memcpy(tmp + 8, mHash, hash_len);
    memcpy(tmp + 8 + hash_len, salt, hash_len);
    SHA256(tmp, 8 + hash_len + hash_len, selfmap);


    if(!memcmp(map, selfmap, hash_len))
    {
        printf("decode SUCCESS\n");
    }

}

void encode(unsigned char *rsa2048)
{
    unsigned char mHash[EVP_MAX_MD_SIZE], map[EVP_MAX_MD_SIZE];
    unsigned char *salt;
    unsigned char tmp[8 + EVP_MAX_MD_SIZE*2]={0};
    unsigned char *p = rsa2048;
    unsigned int rsa_size = 256;

    /*这个函数应该是encode前被调用,而不是decode中*/
    /*step 1 mHash*/
    SHA256(tosigned, strlen(tosigned), mHash);

    /*step 2 radnom salt*/
    salt = (p + rsa_size - 2 - hash_len*2);

    *salt++ = 0x01;

    /*Random*/
    memset(salt, 0x12, hash_len);

    memcpy(tmp + 8, mHash, hash_len);
    memcpy(tmp + 8 + hash_len, salt, hash_len);
    SHA256(tmp, 8 + hash_len+hash_len, p + rsa_size - hash_len - 1);

    mgf1(rsa2048, rsa_size - hash_len - 1, p + rsa_size - hash_len - 1, hash_len);
    rsa2048[rsa_size - 1] = 0xBC;

    /*Set the leftmost 8emLen - emBits bits of the leftmost octet in maskedDB to zero.*/    
    rsa2048[0] &= 0xFF >> 1;
}

void main()
{
    unsigned char rsa2048[256]={0};
    md = EVP_sha256();
    hash_len = EVP_MD_size(md);

    encode(rsa2048);
    //private_key_enc(rsa2048);
    //private_key_edc(rsa2048);
    decode(rsa2048);
}
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页