Python 实现 RSA 非对称加解密

在阅读《HTTP/2 in Action》的 HTTPS 一节后,不觉一脚踏入到非对称加密这一领地而不能自拔。与非对称加密相对应的是对称加密,有点像是由一把钥匙反锁的门,只能用同一把钥匙打开; 而非对称加密是用一把钥匙反锁门,但只能用另一把特定的钥匙才能打开它,锁门的叫做公钥,开门的叫做私钥。

在此之前我理解的非对称加密以为是像 MD5 那种摘要(Digest), 由明文生成的 MD5 摘要信息是无法还原出原始数据的,谬以为那就是所谓的非对称。

1976 年,两位美国计算机学家 Whitefield Diffie 和 Martin Hellman 提出了非对称加解密的的构思。1977 年三位数学家 Ron Rivest, Adi Shamir 和 Leonard Adleman 实现了非对称加密算法,即 RSA,取自这三个的姓的首字母

具体的 RSA 算法原理可参考阮一峰的两篇网络日志:RSA算法原理 (一)RSA算法原理 (二), 大致就是通过互质的两个数,计算欧拉函数, 模反元素,最终算法公钥和私钥,公钥加密的数据只能用用私钥解密,以当前的算力,只要 RSA 的密钥足够长,如 1024 位以上,私钥是无法通过公钥推断出来的。

下面是用 Python 演示 RSA 加密和解密,要用到的库是 pycryptodome。早先有 cryptoPyCrypto, 但它们都已不再维护,而 PyCrypto 建议使用 CryptographyPyCryptodome

一个完整的例子

安装 pycryptodome 库

pip install pycryptodoem

下面的代码生成公钥与私钥,并用公钥加密,私钥解密

运行的输出类似如下

sAcpyMx6N9L11dDER37xnM8KUIis5EUmpiacEPealzuNnmt85S1h8NQUevcomnBzYkv5/7E0kr0oyVJK4sos8J5lm5VR803c5RMLzLLJxZe4Sbhna000TQ3lgmkaLYbQ2pJ7EAdjutUwWHATCQoPd21E7XskGqOWMA/zzf7o1sY=
hello world

说明

  1. 因为每次的公,私钥不一样,所以加密后的表现形式不一样,但都能用私钥解密出来
  2. 用 base64 编解码是为了能在控制台打印出二进制字节数据
  3. rsa.public_key() 能直接获得 public_key, 如果是用 exportKey() 方法导出的 key, 需用 RSA.importKey() 导入后才能使用

RsaKey 所蕴藏的秘密

我们在上面的 RSA.generate(1024, Random.new().read) 后打个断点,睢瞧一个 RsaKey 包含些什么,或者说是怎么不断运算生成的

参考 RSA 的算法,对上面的 d, e, n, p, q 和 u 的解释

  1. p 和 q 为随意选择的两个质数
  2. n 为 p * q 的乘机, n 的二进制值的位数即密钥的长度
  3. u 是 n 的欧拉函数值 φ(n) 
  4. e 可以随便一个在 1 与 n 的欧拉函数φ(n) 之间的数,这里直接用  65537
  5. d 为 e 对于 φ(n) 的模板元素

最后 n, e 封装为公钥,n, d 封装为私钥

输出公钥和私钥

如果我们打印出公钥和钥是什么样子的呢?来

控制台看到

是不是很熟悉的味道,后面还要继续联想

上面的公钥私钥可以保存成二进制文件,以及从文件中读回的代码如下

其他工具生成的 RSA keys

前面我们用的 pycryptodome 组件生成的 RSA 公私钥,记得之前在 Mac OS X 下用 ssh-keygen 生成 RSA 密钥,运行

ssh-keygen -t rsa

就会在 ~/.ssh 目录下生成公钥和私钥文件 id_rsaid_rsa.pub。如果用 ssh key 免密码连接 SSH 服务的话,我们需要把 id_rsa.pub 的内容上传到 SSH 服务器。

那么我们是否能用ssh-keygen 生成的公钥和私钥进行加密,解密呢?下面来试下

输出

dgrWzMYZL2OIx/Xu0hSRTn7Q0yhyjDJRygKgMCXN/KxLJAb8nGnovmnkAhUH16glAQgzUNw7j0R73EaKT5bg8iwzMDFH5ufJx2+iOUdjXQOf6ZBQ7yyFi+Jz9ZFF2TLT8DGscS/cvh2whxvZZ+zA+xF5tVlgeIY4HPWXXSOuJ0LsCKaRxg25MTSjOWmaiWDlGLi4lGjXJu0wCMxjBCFR4PbtqIaleYL7bQndv03CCWQxNmTMgoBAjQ7TZXfDmpfYc17qgANfZ86gqhM1ILqfu7SlQBWWBL0URDcy6ED6AwsccI5W7l/k0abBX2aQ8YUfh6ZD5wX0/39sOqJ0Am1LbESeI7p3G1iDFy/i0s/3uk+RuzzK4oDxzbpLAXoy+GgNk3cf9gRP1+/pP2H83fTu0H1L5cKYVcgPXQJ/5l+aIoZrZLz+f7+hfAkphuy76jZKGJ0a4nTVJwMuJHQUz81mvYNi6jJB2iF//id+LsG9lZ4Byvio8X+ymKlhHuSjiSb1
hello world

完全没问题。

查看 id_rsa 和 id_rsa.pub 中的内容,大致的样子是

 

也可以用 openssl 命令生成的公钥私钥

生成私钥

openssl genrsa -out rsa_private_key.pem 1024

生成与私钥相关联的公钥

openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

使用这里生成的公钥加密,私钥解密也没有问题

是否能用私钥加密公钥解密呢?

先来尝试一下

输出为

gVc1t/HEJFdh9WeBQKpW4fpcfTkIiQvCY3Pnly38vARB6F2rlsIhkFRHQtSeZwdcUoN+qsX8Bfa8CMuP0Gr8RIJj7LaVkNpSSZ80tLbBC1Bfbc34qOtowfbmBBoMdCu9cpTwO8ePepGB06+MQTKgUwmzXRo0AeXxzjhvGTtz3y8=
.....
TypeError: This is not a private key

说明可以用私钥加密,但不能用公钥解密

那么只用私钥加密和解密呢?

测试

输出

GpeISnhTUTHoJbGGF02PB9+pHtrjL2pfo+AiOGas7Euit/e+njhxWU7bLcurX7KbtHWgjxkp2ObAQh1JbebkckLKTHQA3hebuA4zH5DXStovYwX80EMFm2bKH7slz0Dql0Oc4JPRKqTJhttrizrrsFAyDYFCNBj2RNvzaHyZCvM=
hello world

通过,但这又回到对称加密的原地了,还要 RSA 做什么,况且生成密钥的一方是不会把私钥告知另一方的。

私钥制作签名, 公钥验证签名

前面测试过用私钥加密的数据不能用公钥解密,用私钥加密的数据仍能用私钥解密。RSA 的正确做法都是用公钥加密数据,然后用私钥解密,在进行 TLS 握手时,初步猜想(有待于以后验证)应该是在各自一端生成公钥和私钥,然后交换公钥,所以数据总是由对方提供过来的公钥加密,最后由保存在本地私钥进行解密。这样就无需发送私钥。

比如 A 和与 B 进行加密通信,在进行 RSA 握手时

  1. A 生成 private_key_A, public_key_A, 并发送 public_key_A 给 B
  2. 同样的, B 生成 private_key_B, public_key_B, 也把 public_key_B 发给 A

加密,传送,解密过程,比如 A 要发数据给 B

  1. A  先用收到的 public_key_B 对数据加密
  2. 发送加密数据给 B
  3. B 收到数据后用本地的 private_key_B 进行解密

私钥虽不用来加密数据,但可以用私钥生成签名,另一方再用公钥进行签名验证。数据发送端持有自己生成私钥,并把公钥发送给了对方。对数据进行签名的目的是为了保证数据在传送过程中的完整性,如果数据被篡改,签名未随之修改验证就不能通过,要是签名也被改了,还能被验证通过,问题就大了,说明私钥都被别人掌握了。

比如同样是 A 发送数据给 B

  1. 数据的构成为: public _key_B 加密的数据 + private_key_A 对数据生成的签名
  2. B 接收到数据后用 private_key_B 解密
  3. B 再用 public_key_A 对数据进行签名验证

私钥签名,公钥验证签名的代码如下:

输出为

yVFiA9z7uPCVum0D4pQI/yvfCiArPF+b+Kr2IO0v1g9mOdFZ4YxOlnedtK75b2Slz6p7xwpr4OATrwopadCcnfdbeHlY24CKYzFOZh8W+uK8FPMSNE9//RNvNLy+Znlx+mGbcDQQBjzPsYfoDQ0DGoSpTGgrFFN3FOaj0G4b//M=
True
False

任何对数据的修改都无法通过签名的验证。

注:非对称的设计之初是用公钥加密私钥解密,但由于公钥加密很慢,所以它常用来协商一个共享密钥,以后只用该共享密钥加密数所。

链接:

  1. Python crypto模块实现RSA 加密解密
  2. 用 Python 实现 RSA 加解密

本文链接 https://yanbin.blog/python-implement-rsa-encryption/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

2 Comments
Inline Feedbacks
View all comments
rantrism
rantrism
2 years ago

您好~我是腾讯云开发者社区运营,关注了您分享的技术文章,觉得内容很棒,我们诚挚邀请您加入腾讯云自媒体分享计划。完整福利和申请地址请见:https://cloud.tencent.com/developer/support-plan
作者申请此计划后将作者的文章进行搬迁同步到社区的专栏下,你只需要简单填写一下表单申请即可,我们会给作者提供包括流量、云服务器等,另外还有些周边礼物。