为什么RSA公钥每次加密得到的结果都不一样?
<<OpenSSL和Python实现RSA Key公钥加密私钥解密>>中提到,发现使用RSA公钥对同一数据加密,每次的结果都不一样。百度一下,很多人都有这个疑问,但并没有看到详细的分析解答,即使有人说是因为padding填充的原因,也都是一带而过。
为什么私钥对同一数据进行签名加密的结果是一样的,使用公钥进行加密就不一样了呢?
是的,这个问题跟对数据的padding即填充有关,详细说来,是跟PKCS #1 v1.5指定的padding方式有关,下面对这个问题进行详细的说明。
1. 问题的来源
重复下这个问题的来源:
1.1 准备测试数据
将字符串"I Love China!"保存到msg.bin作为测试数据
$ echo -n "I Love China!" > msg.bin
$ hexdump -Cv msg.bin
00000000 49 20 4c 6f 76 65 20 43 68 69 6e 61 21 |I Love China!|
0000000d
随机生成测试用的私钥key.pem,并导出公钥key_pub.pem:
$ openssl genrsa -out key.pem -f4 2048
Generating RSA private key, 2048 bit long modulus
........+++
.......+++
e is 65537 (0x10001)
$ openssl rsa -in key.pem -pubout -out key_pub.pem
writing RSA key
1.2 使用私钥对同一数据签名
使用私钥key.pem对msg.bin进行两次签名,签名结果分别为msg.bin.sig1和msg.bin.sig2:
$ openssl dgst -sha256 -out msg.bin.sig1 -sign key.pem msg.bin
$ openssl dgst -sha256 -out msg.bin.sig2 -sign key.pem msg.bin
$ md5sum msg.bin.sig1 msg.bin.sig2
4d10a2163f92f90f114126de2371deb8 msg.bin.sig1
4d10a2163f92f90f114126de2371deb8 msg.bin.sig2
从签名数据的md5校验和看,msg.bin.sig1和msg.bin.sig2的md5值一样,其内容显然是一样的。
1.3 使用公钥对同一数据加密
使用公钥key_pub.pem对msg.bin进行两次加密,加密结果分别为msg.bin.enc1和msg.bin.enc2:
$ openssl rsautl -in msg.bin -out msg.bin.enc1 -inkey key_pub.pem -pubin -encrypt
$ openssl rsautl -in msg.bin -out msg.bin.enc2 -inkey key_pub.pem -pubin -encrypt
$ md5sum msg.bin.enc1 msg.bin.enc2
19c3bf692e94eaf87770001181c5eb10 msg.bin.enc1
f576f31a796332fcd3c4d3627ef4ad5d msg.bin.enc2
显然msg.bin.enc1与msg.bin.enc2的md5不一样,二者的内容也不一样,也就是说,使用同一个RSA公钥对同一段数据加密,两次加密的结果不一样。
2. PKCS #1 v1.5指定的填充方式
除了PKCS #1 v1.5指定的填充方式外,后续版本对填充方式进行了更新:
- PKCS #1 v2.0 指定了针对加密使用的OAEP填充方式
- PKCS #1 v2.1 又进一步指定了针对签名使用的PSS填充方式
这里只讨论早期PKCS #1 v1.5指定的简单的填充方式,也是目前最常见的填充方式。
RSA建议,为提高安全性,在新的应用中应逐步采用OAEP和PSS方式的进行填充。
2.1 填充方式的描述
不管是使用RSA私钥进行签名还是公钥进行加密,操作中都需要对待处理的数据先进行填充,然后再对填充后的数据进行加密处理。针对如何对内容进行填充,"[RFC2313] PKCS #1: RSA Encryption Version 1.5
“的”8.1 Encryption-block formatting
"节提供了详细的说明,原文如下:
8.1 Encryption-block formattingA block type BT, a padding string PS, and the data D shall beformatted into an octet string EB, the encryption block.EB = 00 || BT || PS || 00 || D . (1)The block type BT shall be a single octet indicating the structure ofthe encryption block. For this version of the document it shall havevalue 00, 01, or 02. For a private- key operation, the block typeshall be 00 or 01. For a public-key operation, it shall be 02.The padding string PS shall consist of k-3-||D|| octets. For blocktype 00, the octets shall have value 00; for block type 01, theyshall have value FF; and for block type 02, they shall bepseudorandomly generated and nonzero. This makes the length of theencryption block EB equal to k.
由于篇幅的原因,这里没有列出针对这段话的"Notes"部分,可以通过点击原文链接查看。
简单说来,PKCS #1 v1.5规定的填充格式为:
EB = 00 || BT || PS || 00 || D
其中:
D: data (指待处理数据,即填充前的原始数据)
PS: padding string (填充字符串)
BT: block type (数据块类型)
EB: encryption block (待加密的数据块,经过填充后结果)
||: 表示连接操作 (X||Y表示将X和Y的内容连接到一起)
所以:
"填充后数据" = "00" + "数据块类型" + "填充字符串" + "00" + "原始数据"
"填充块类型"BT决定了紧挨着的"填充字符串"PS的内容。
BT的可能取值包括00, 01和02:
- 针对私钥处理的数据,BT取值为00或01;
- BT取值为00时,PS为全00的字符串
- BT取值为01时,PS为全FF的字符串,通过填充得到的整数会足够大,可以阻止某些攻击,因此也是推荐的填充方式
- 针对公钥处理的数据,BT取值为02;
- 使用伪随机的16进制字符串填充PS,而且每次操作进行填充的伪随机书都是独立的
重点来了,针对公钥处理的数据,其填充内容为伪随机的16进制字符串,每次操作的填充内容都不一样。这就是为什么每次使用公钥加密数据得到的结果都不一样了。
下面还是以本文开始的公钥加密数据进行说明。
2.2 检查公钥加密的填充数据
第1节中,使用公钥key_pub.pem对数据msg.bin进行了加密,我们使用私钥key.pem解密看看加密前填充的数据。
2.2.1 解密msg.bin.enc1到msg.bin.enc1.dec
$ openssl rsautl -in msg.bin.enc1 -out msg.bin.enc1.dec -inkey key.pem -decrypt -raw
为了显示填充结构,这里用UltraEdit打开解密后的数据:
2.2.2 解密msg.bin.enc2到msg.bin.enc2.dec
$ openssl rsautl -in msg.bin.enc2 -out msg.bin.enc2.dec -inkey key.pem -decrypt -raw
为了显示填充结构,这里用UltraEdit打开解密后的数据:
上面两张图都是对数据msg.bin进行填充后,并且在使用公钥key_pub.pem加密前的内容:
- 两个绿色部分是指定的“00”填充;
- 紫色部分指定BT为02, 说明后续使用公钥处理的数据;
- 灰色部分为PS,其根据BT=02,填充了伪随机数;
- 橙色部分为原始数据。
可见,两次填充的伪随机数是不一样,这样在使用公钥加密后其结果自然就不一样了。
我看网上有帖子说JAVA下可以通过设置,使每次填充生成同样的内容,但这样似乎不够安全。
我在openssl工具下没有找到相应的针对公钥操作时数据的填充设置选项。
2.3 检查私钥加密的填充数据
在第1节的签名例子中,使用私钥签名时,会先对数据计算SHA256编码,然后对SHA256哈希进行BER编码,再进行填充。
我们可以通过对签名数据使用公钥解密后,看看填充数据的内容:
$ openssl rsautl -in msg.bin.sig1 -out msg.bin.sig1.dec -inkey key_pub.pem -pubin -verify -raw
注意:这里针对
openssl rsault
命令使用-raw
选项显示原始的数据结构,否则只会显示数据部分,而不会显示填充的内容
按照惯例,为了显示填充结构,这里用UltraEdit打开解密后的签名数据:
上图中是对SHA256数据进行BER编码填充,使用私钥key.pem加密前的内容:
- 两个绿色部分是指定的“00”填充;
- 紫色部分指定BT为01, 说明后续是使用私钥处理的数据;
- 灰色部分为PS,其根据BT=01,填充为全FF的数据;
- 橙色部分为原始数据(即SHA256数据经过BER编码的内容)。
再来假设下BT为00的情况:
当BT为00时,此时填充的内容PS部分为全00,又由于填充格式中指定了两个“00”的分隔符,如果此时原始数据又是以“00”开始的话,如何划分填充数据和原始数据呢?
显然此时是无法进行区分的,除非你知道原始数据由多长。幸运的是,我们基本上不会采用BT=00的填充方式,所以这种情况几乎不会发生。
3. 一个"openssl rsautl"的bug
最后,以我发现的一个"openssl rsautl"命令的bug来结束本文。
在进行RSA公钥加密私钥解密时,如果加密的原始数据为全00,解密时通过-hexdump
选项,无法正确在控制台显示原始数据,问题的复现步骤如下:
生成16字节全0数据:
$ dd if=/dev/zero of=data.bin bs=1 count=32
32+0 records in
32+0 records out
32 bytes (32 B) copied, 0.000227422 s, 141 kB/s
$ hexdump -Cv data.bin
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000020
使用公钥key.pem对全0数据data.bin进行加密:
$ openssl rsautl -in data.bin -out data.bin.enc -inkey key_pub.pem -pubin -encrypt
使用私钥解密数据,并直接在控制台显示:
$ openssl rsautl -in data.bin.enc -inkey key.pem -decrypt -hexdump
0020 - <SPACES/NULS>
$ openssl rsautl -in data.bin.enc -inkey key.pem -decrypt -hexdump -raw
0000 - 00 02 49 d0 3d 94 b6 90-8b 4e 40 e7 e4 d1 e2 90 ..I.=....N@.....
0010 - e8 18 29 7f cf 5c c7 4a-bd cc 11 97 81 b3 a7 2b ..)..\.J.......+
0020 - c3 83 43 60 6d f9 d5 1d-e9 29 ab 51 d1 98 58 49 ..C`m....).Q..XI
0030 - 22 5b ae d2 27 d4 da bd-2b 0d ba 54 7e 04 10 b1 "[..'...+..T~...
0040 - 3b c7 a1 7d 57 02 d8 1c-53 41 91 52 2d ac 0b da ;..}W...SA.R-...
0050 - 1e 0e 0b 94 b3 90 0d 20-49 2c 94 84 22 87 30 c1 ....... I,..".0.
0060 - 31 36 6e 18 e0 52 5e ec-59 f9 36 47 37 c6 45 1e 16n..R^.Y.6G7.E.
0070 - 24 db dc 9a e4 d8 4e dc-b6 0b b2 57 f4 27 a9 56 $.....N....W.'.V
0080 - 05 11 2d 03 75 75 64 58-87 b8 86 8d 4c 3d d5 f4 ..-.uudX....L=..
0090 - 10 34 9d 24 ab 48 b4 27-58 59 f7 27 3b 9b 39 5a .4.$.H.'XY.';.9Z
00a0 - b4 ed 2e fb 1f 6e 1f 13-b3 cd 67 78 5f ec f5 63 .....n....gx_..c
00b0 - ae 46 a7 17 87 f7 10 09-32 6d 30 dc 0e 1b 48 2a .F......2m0...H*
00c0 - 2c 27 09 bc 42 32 38 22-8c 76 c9 cb 8c e9 0d f9 ,'..B28".v......
00d0 - 5e d0 c3 a8 af 8f cd 68-b6 96 3c 94 5c 3c d1 ^......h..<.\<.
0100 - <SPACES/NULS>
这里使用私钥解密后竟然没有原始数据,你说神奇不神奇?我第1次使用不带"-raw"选项操作,发现命令输出没有数据显示时简直大吃了一惊。
还好,如果将解密的数据送到指定文件再显示则没有问题:
$ openssl rsautl -in data.bin.enc -out data.bin.enc.dec -inkey key.pem -decrypt -raw
$ hexdump -Cv data.bin.enc.dec
00000000 00 02 49 d0 3d 94 b6 90 8b 4e 40 e7 e4 d1 e2 90 |..I.=....N@.....|
00000010 e8 18 29 7f cf 5c c7 4a bd cc 11 97 81 b3 a7 2b |..)..\.J.......+|
00000020 c3 83 43 60 6d f9 d5 1d e9 29 ab 51 d1 98 58 49 |..C`m....).Q..XI|
00000030 22 5b ae d2 27 d4 da bd 2b 0d ba 54 7e 04 10 b1 |"[..'...+..T~...|
00000040 3b c7 a1 7d 57 02 d8 1c 53 41 91 52 2d ac 0b da |;..}W...SA.R-...|
00000050 1e 0e 0b 94 b3 90 0d 20 49 2c 94 84 22 87 30 c1 |....... I,..".0.|
00000060 31 36 6e 18 e0 52 5e ec 59 f9 36 47 37 c6 45 1e |16n..R^.Y.6G7.E.|
00000070 24 db dc 9a e4 d8 4e dc b6 0b b2 57 f4 27 a9 56 |$.....N....W.'.V|
00000080 05 11 2d 03 75 75 64 58 87 b8 86 8d 4c 3d d5 f4 |..-.uudX....L=..|
00000090 10 34 9d 24 ab 48 b4 27 58 59 f7 27 3b 9b 39 5a |.4.$.H.'XY.';.9Z|
000000a0 b4 ed 2e fb 1f 6e 1f 13 b3 cd 67 78 5f ec f5 63 |.....n....gx_..c|
000000b0 ae 46 a7 17 87 f7 10 09 32 6d 30 dc 0e 1b 48 2a |.F......2m0...H*|
000000c0 2c 27 09 bc 42 32 38 22 8c 76 c9 cb 8c e9 0d f9 |,'..B28".v......|
000000d0 5e d0 c3 a8 af 8f cd 68 b6 96 3c 94 5c 3c d1 00 |^......h..<.\<..|
000000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000100
看吧,通过后者得到的输出,其全0数据在结果的最后两行都好好的在呢,虚惊一场啊。
联系和福利
- 关注微信公众号“洛奇看世界”
- 回复关键词“Android电子书”,获取超过150本Android相关的电子书和文档。电子书包含了Android开发相关的方方面面,从此你再也不需要到处找Android开发的电子书了。
- 回复关键词“Android电子书”,获取超过150本Android相关的电子书和文档。电子书包含了Android开发相关的方方面面,从此你再也不需要到处找Android开发的电子书了。
- 个人微信号,添加请备注“微信公众号”。
为什么RSA公钥每次加密得到的结果都不一样?相关推荐
- RSA加密为什么每次加密的结果不一样
JAVA写RSA加密,私钥都是一样的,公钥每次加密的结果不一样跟对数据的padding(填充)有关. 1.Padding (填充)属性定义元素边框与元素内容之间的空间. 2.padding 简写属性在 ...
- 转载:Openssl生成rsa和椭圆曲线加密公钥密钥创建根ca签名https服务器证书签名...
转载:Openssl生成rsa和椭圆曲线加密公钥密钥创建根ca签名https服务器证书签名 Openssl生成rsa和椭圆曲线加密公钥密钥创建根ca签名https服务器证书签名 July 20, 20 ...
- 使用OpenSSL加密,使用Java解密,使用OpenSSL RSA公钥
抽象 在2017年,我撰写了一个由三部分组成的系列文章,内容涉及选择最佳的哈希和加密算法. 在对该系列进行研究时,我学到了很多有关哈希和加密的知识. 我学到的最重要的事情是,尽管我必须对如何使用最安全 ...
- daoi php_使用php生成RSA公钥私钥及进行加密解密和签名验证
这篇文章主要介绍使用PHP开发接口,数据实现RSA加密解密后使用,实例分析了PHP自定义RSA类实现加密与解密的技巧,非常具有实用价值,需要的朋友可以参考下. 简单介绍RSA: RSA加密算法是最常用 ...
- Python3.6.1 RSA 公钥pubkey分段加密
前段时间,遇到有接口是使用rsa公钥加密方法实现的,于是需要编写rsa 加密的脚本.其中 ,公钥(publickey)是已知的.不同长度的字段使用RSA加密出来的长度不一样. 加密的字段长短规则如下: ...
- RSA公钥格式PKCS#1,PKCS#8互转(微信获取RSA加密公钥)
/把RSA公钥格式PKCS#1转换成PKCS#8org.bouncycastle.asn1.pkcs.RSAPublicKey rsaPublicKey = org.bouncycastle.asn1 ...
- 数字证书原理,公钥私钥加密原理
文中首先解释了加密解密的一些基础知识和概念,然后通过一个加密通信过程的例子说明了加密算法的作用,以及数字证书的出现所起的作用.接着对数字证书做一个详细的解释,并讨论一下windows中数字证书的管理, ...
- java之RSA和Base64加密帮助类
1.RSAUtils.java类 package com.sangfor.vpn.client.service.utils; import java.io.BufferedReader; import ...
- Python crypto模块实现RSA和AES加密解密
Python crypto模块实现RSA和AES加密解密 Python的crypto是用于RSA加密解密,AES加密解密的. 一.RSA和AES简介 RSA加密算法是一种非对称加密算法.RSA 是19 ...
最新文章
- 《Unity 4 3D开发实战详解》一6.7 物理引擎综合案例
- [zz]启动apache后访问系统,提示没有权限访问目录,报403错误。
- 【Android RTMP】x264 图像数据编码 ( NV21 格式中的 YUV 数据排列 | Y 灰度数据拷贝 | U 色彩值数据拷贝 | V 饱和度数据拷贝 | 图像编码操作 )
- CF809C Find a car
- SAP Fiori Elements 概述
- 深度学习导论(6)误差计算
- 深入理解 PHP7 中全新的 zval 容器和引用计数机制
- Java的String/byte[]/16进制相互转换
- angularjs通信以及postmessage与iframe通信
- echarts如何画地图
- QCC300x 芯片ota升级PSKey 操作方法
- 计算机网络安全ipc默认共享,关闭远程ipc共享
- 大数据人工智能行业技术图谱
- mysql中%3c%3e和=_如何巧妙去除隐藏“站长统计”文字链接?
- python棋类项目规划一——五子棋游戏策划书——项目一
- colab使用入门(1)-安装库,保存/加载笔记本
- 苹果手机怎么用计算机隐藏应用,iphone怎么隐藏应用功能 iphone隐藏应用功能方法【详解】...
- C++学习(二零三)Quaro Geforce Tesla
- linux下R安装RMySQL不成功 configuration failed for package ‘RMySQL’
- PHPMyWind编辑器支持Word上传
热门文章
- 【Unity Shader】(2)半兰伯特模型 构建光照
- 考研调剂信息2020计算机专业,30所院校2020考研调剂信息汇总!马上收藏备用!...
- [Java开发]搭建人力资源管理系统——简历管理模块(附带下载链接)
- 18.11 项目:自动填表程序
- HTML---->常用标签链接特殊符号
- crypto 抄错的字符
- Win11怎么连接宽带?
- 福禄克DTX-1800与 DSX 系列(DSX-5000、DSX-8000)有什么区别?
- TL-link WAR1208L多wan路由器带宽叠加设置
- 高中英语教师资格证考试经验贴