加密(签名)的过程是(M的e次方)mod n,在这里我们把消息M假定为一个数字,但实际上消息一般为字符串,所以必须有一个将字符串转化为数字的规则,并且要让这个数字的大小和n相当(也不能比n大)。这样做的目的是为了使(M的e次方)> n ,假如不是这样那么C=(M的e次方)mod n = (M的e次方),也就是mod n完全没有作用,攻击者就能够轻松的通过取C的第e次方根来恢复M。那么下面来讨论这种转换的标准:
1,计算出格式化加密分组所需的大小。如果n是一个L位的数字,那么加密分组就有L/8字节长(只舍不入)。
2,第一个高位字节总是0,而第二个为分组类型。对加密来说,这是2,对签名来说是1,这样就确保形成的数字比n要稍小。
3,消息存放在加密分组的低位字节中,并在之前安放一个0
00 01 ff ff ff ff ... 00 消息数据 —— 这是签名时的转换方式
00 02 伪随机非零字节 00 消息数据 —— 这是加密时的转换方式
由这里可以看出,对一个消息进行重复签名时,签名结果是一样的。对一个消息进行反复加密时,得出来的加密分组是不一样的。恢复加密分组时,就只需要从第三个字节开始一直向右推进到一个0字节为止,这就找到了数据起始位置。
但是需要注意上面这个过程中还要加入摘要算法的标识符,因为:
在RSA_sign函数中在RSA_eay_private_encrypt(i,s,sigret,rsa,RSA_PKCS1_PADDING);函数以前有一段程序,问题的答案就在这里,楼上说的摘要是存在m中的,而s=3020300C06082A864886F70D020505000410+m(34比特),这段程序意思很好理解,就是加上摘要算法的标识符。所以3020300C06082A864886F70D020505000410就是代表md5这个algorithm。
这样我们就可以得出签名的数据的转化格式
00 01 ff ff ff ff ... 00 算法标识 消息数据
那么我们看看openssl中证书签名的过程:
int X509_sign(X509 *x, EVP_PKEY *pkey, const EVP_MD *md)
{
//先进行ret->cert_info->signature,以及ret->sig_alg的设置;
inl=i2d_X509_CINF(ret->cert_info,NULL);//求出证书编码后的长度
buf_in=(unsigned char *)OPENSSL_malloc((unsigned int)inl);//申请空间
outll=outl=EVP_PKEY_size(pkey1);
buf_outl=(unsigned char *)OPENSSL_malloc((unsigned int)inl);
if ((buf_in == NULL) ││ (buf_outl== NULL))
{
outl=0;
goto err;
}
p=buf_in;//p与buf-in共享一段地址
i2d_X509_CINF(ret->cert_info,&p);//将证书编码存入buf-in
EVP_MD_CTX_init(&ctxl);//初始化
EVP_SignInit(&ctxl,dgst);//将需要使用的摘要算法存入ctxl中
EVP_SignUpdate(&ctxl,(unsigned char *)buf_in,inl);//存入证书的编码值
EVP_DigestFinal(&ctxl,&(m[0]),&m_len);//求取编码的长度为m_len摘要值存入m中
RSA_sign(ctxl->digest->type,m,m_len,buf_out,outl,pkey->pkey.rsa)//求取摘要值的签名值,最后将长度为outl的签名值存入buf-out。
RSA_sign最主要是调用了RSA_eay_private_encrypt(int flen, unsigned char *from, unsigned char *to, RSA *rsa, int padding)函数。这里值得注意的一点是: RSA_eay_private_encrypt 加密时默认调用RSA密钥文件中的p、q因子使用中国剩余定理的算法进行加密,这样可以提高效率,而不是直接调用pkey->pkey.rsa->d进行加密,所以有时候直接改动pkey->pkey.rsa->d而加密结果是不会变化的。
在此过程中编码函数采用了EME-PKCS1——5-ENCOD编码函数,大数与字符串之间的转换函数使用的是OS2IP、I20SP格式转换函数BN_bin2bn和BN_bn2bin。EME-PKCS1_5-ENCODE 函数
输入:字符串 M 、emlen 表示信息编码以后的长度。(注意:信息M 的长度不得大于 emlen-10 个字节)
输出: EM 表示 M 经过编码以后的内容。函数的具体操作步骤是随机产生 emlen-len(M)-2 字节长度的非零字符串PS ,且len(PS)>=8 然后按照下列方式 EM= 02 ││ PS ││ 00 ││ M 的方式把它们连接起来。