徽州骆驼 · 7月31日

为什么RSA公钥加密结果不是固定值?

目录

1.问题现象

2.填充方式解读

2.1 RSA公钥加密填充解读

2.2 RSA私钥签名填充解读

3.小结

大家好,这里是快乐的肌肉。今天聊聊RSA一些以前没有关注到的小细节。

1.问题现象

最近Muscle v01进入收尾阶段,涵盖了绝大部分密码算法,具体效果如下:

image.png

在做RSA的时候发现了使用RSA公钥对相同的数据进行加密,每次得到密文竟然都不一样。如下图:

image.png

明文数据为0x11223344,使用同一RSA公钥对同一数据(使用私钥解密可以验证,如上图绿框)进行三次加密,得到的密文数据为:

  1. 0x80208321b8......
  2. 0xcf997e6677......
  3. 0x85213fcf97......

公钥加密是如此现象,那么如果用RSA私钥进行签名(可以认为是签名加密),结果会一致吗?事与愿违,它竟然是一致的,如下图:

image.png

签名值均为0xbe34bcb8d3......这个现象就有意思了。

我们来思考下,上述过程参与到加密计算的内容包括:原始数据+填充值、密钥;

现在原始数据和密钥是一致的,那么问题大概率就出现在填充方式上面。

2.填充方式解读

好,在这里我使用的是PKCS1v15进行填充:

image.png

根据pyCryptography提供的说明,使用的是RFC 3447标准进行填充,如下图:

image.png

这里面有详细的Padding方式解读,但是我本来是想dump出填充后的数据来进行分析,没有找到途径,因此转向Openssl。

2.1 RSA公钥加密填充解读

示例如下:

image.png

首先看看该txt的hex,如下:

image.png

然后使用public_key.pem对demo.txt进行两次加密,可以看到密文仍不相同:

rsautl -encrypt -in Demo.txt -inkey public_key.pem -pubin -out DemoEnc1.bin
rsautl -encrypt -in Demo.txt -inkey public_key.pem -pubin -out DemoEnc2.bin

image.png

接着我们想个办法把加密前填充后的数据dump出来:

rsautl -decrypt -in DemoEnc1.bin -inkey private_key.pem -out DemoPadDec1.bin -raw
rsautl -decrypt -in DemoEnc2.bin -inkey private_key.pem -out DemoPadDec2.bin -raw

通过hex比对,我们可以发现,原始数据被拼接在最后,如下图:

image.png

通过上图,很明显可以发现,每次填充的前两个字节均为00 02,中间的填充数据都不一样,因此造成了不同的密文结果。 

Openssl默认使用PKCS#1 v1.5进行填充,如下图:

image.png

所以还是得去解读下标准填充格式, 原文路径如下:RFC 2313: PKCS #1: RSA Encryption Version 1.5

在8.1 Encryption-block formatting章节,详细解读了加密块格式,如下图:

image.png
EB整体由00 || BT || PS || 00 || D拼接组成:

  • 第一个00:确保加密块转换成整数后小于模数;
  • BT:Block Type,当前版本仅有00、01、02取值;00、01表示私钥操作,00、02表示公钥操作;这里用的是公钥加密,因此为02;
  • PS:Padding String,包含k-3-D个字节,k指模数,D为数据;但是!!!
  • BT == 00时,填充字节值为0;
  • BT == 01时,填充字节值为FF;
  • BT == 02时,填充字节应该是伪随机生成的非零值。
  • D:原始数据

所以我们来看,使用RSA公钥dump出来的加密前填充数据,PS这一块都是不同的:

image.png

因此得到的密文数据肯定是不一样的。

2.2 RSA私钥签名填充解读

为了验证BT对PS的影响,我们继续使用RSA私钥进行签名,代码如下:

dgst -sha224 -sign private_key.pem -out DemoSign1.bin Demo.txt
dgst -sha224 -sign private_key.pem -out DemoSign1.bin Demo.txt

两个签名完全一致,如下图:

image.png

那我们老规矩,同样使用公钥验签,并dump出填充后的数据,代码如下:

rsautl -in DemoSign1.bin -out DemoPadSign1.bin -inkey public_key.pem -pubin -verify -raw
rsautl -in DemoSign2.bin -out DemoPadSign2.bin -inkey public_key.pem -pubin -verify -raw

得到的hex,一比较完全一致:BT确实为01,同时PS值全为FF,如下图:

image.png

这个填充也满足 EB == 00 || BT || PS || 00 || D;值得注意的是D这里出现了变化,原因也在标准里进行了说明,10.2 Verification process

RSA进行验签时需要有如下步骤:

  1. Bit-string-to-octet-string conversion
  2. RSA decryption
  3. Data decoding
  4. Message digesting and comparison

在第三步中,对数据D进行ber解码,得到DigestInfo类型的ASN.1值,该值分为消息摘要MD和消息摘要算法标识符。后面这块深入研究了再分析分析。

3.小结

通过上面两次对比,我们可以发现,使用RSA公钥进行加密,因为填充数据每次都是伪随机数生成,因此得到密文不一样;使用RSA私钥进行签名,根据PKCS#1 v1.5的填充规则,PS均为FF,故签名都是一致的。

这种填充方式一旦理解了就变得很容易实现了,所以Cryptography这个库不是那么推荐使用这个进行填充。

It is not recommended that PKCS1v15 be used for new applications, OAEP should be preferred for encryption and PSS should be preferred for signature

后面这几种搞明白了再分享给大家,就酱!!

END

作者:快乐的肌肉
来源:汽车MCU软件设计

推荐阅读:

更多汽车电子干货请关注汽车电子与软件专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
推荐阅读
关注数
5713
内容数
453
汽车电子与软件行业的相关技术报道及解读。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息