我正在参加「掘金·启航计划」
RSA概念
公开密钥加密(public-key cryptography),也叫做非对称加密,是密码学的一种算法,他需要两个密钥,一个是公开密钥(公钥),另一个是私有密钥(私钥),一个用作加密的时候,另一个则用作解密。
对于RSA算法详细解释这里不做详细介绍了,自行搜索,理解起来也不难。
PEM
我们通常所见到的RSA秘钥时,常见的未加密的格式大概就是PEM格式了。这段格式虽然看起来可读,但是实际上也是一段乱码(实际上是base64编码)一样的东西。他们一般格式是下面这几种情况:
-----BEGIN RSA PRIVATE KEY-----
YG6XRKfkcxnaXGfFDWHL....
-----END RSA PRIVATE KEY-----
-----BEGIN RSA PUBLIC KEY-----
YG6XRKfkcxnaXGfFDWHL....
-----END RSA PUBLIC KEY-----
这些格式看起来都是一样的,但是往往这一点点区别可能影响到我们实际读取。很明显,带有PUBLIC的是RSA的公钥,带有PRIVATE的是RSA的私钥。但是带有RSA和不带有RSA其实是有区别的。头部带有RSA,说明该公/私钥就是RSA的秘钥。头部不带有RSA,说明该公/私钥有可能不是RSA的秘钥。这些头部不带有RSA的秘钥,他们的秘钥类型参数在中间的那段乱码中。
事实上,中间那段乱码是Base64格式的。经过Base64解码之后,是一段二进制格式的数据。这段数据采用的ASN.1的二进制编码格式.
python rsa库使用的基本就是pem的秘钥,或者说使用pem这种秘钥更方便。
python rsa库的使用
注意这里不是使用的pycryto,仅仅使用了rsa,安装也很简单pip install rsa。
Public Key字符串转pkcs1
对于RSA算法的公钥,主要有两个信息:模数(modulus)和指数(exponent)
只要有这两个信息,我们便可以用以下代码段生成公钥,然后使用python rsa库对数据进行加密:
import rsa
public_key = rsa.PublicKey(modulus, exponent)
现在需要做的就是从字符串格式的公钥中提出模数和指数。在查阅的过程中,除了这种字符串的形式,看到最多的应该属于public.pem、private.pem这种文件格式的公钥私钥了(我们上面提到的)。
首先,RSA秘钥由三个整数组成,我们分别称之为n、e、d:
(n、d):私钥,这个我们要私密保存。 (n、e):公钥,可以对外公布。 n:模数(Modulus),私钥和公钥都包含有这个数。 e:公钥指数(publicExponent),一般是固定值65537。 d:私钥指数(privateExponent)。
通过查阅文档得知,公钥Modulus和publicExponent的偏移量分别是29和159,对应的长度分别是128和3,于是实现代码如下:
def str2pubkey(s):
# 对字符串解码
b_str = base64.b64decode(s)
if len(b_str) < 162:
return False
hex_str = ''
hex_str = b_str.hex()
# 找到模数和指数的开头结束位置
m_start = 29 * 2
e_start = 159 * 2
m_len = 128 * 2
e_len = 3 * 2
modulus = hex_str[m_start:m_start + m_len]
exponent = hex_str[e_start:e_start + e_len]
return modulus, exponent
这样就可以成功的将字符串公钥转换成python ras公钥对象了。
Private Key字符串转pkcs1
首先我们可能拿到的是一个字符串型的私钥。如下:
prikey_str = "XIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAK2pZExJztpfUDxb73ygxCuzw0OnNXyVoo/t9ZXMhXVplZ5sVJGIoijk89YiQvunIciL1SCxG1SanXGoGyziwT1md0izwExYPM5SpD9xPcHlU8Z5pXXGNBz5jpsXTmHN2Js1VnjNgC7SQX1nxs8dV4IZM8Jm79KtVTifZ1uNxznpAgMBAAECgYAriWH5ti9Dk82btDiv9+X2RtTXKx3zbTgZ+UAuU93Jd7ToyKPS8Hwm4X846dH9IQNZMaU20u/VIAYErm5RrG9hOL7pUDCZltTXGrb1VBfpedzzkWschBzwXo4Htt27PXpubp0puQxAYUL9YbodKlX/y/IPBpQlOPAE/6EmTu6qYQJBAO594D8L+J6vCHbGqaf3MCYZIjMMa7FG0dLRnqL6aMLUILxwlYeU6XZutnPZCL9cpVbfVRWef5ZAx/N/mDB6tFUCQQC6aR/sbp617sBpp2yKtpL3QKo42DTxOSK1uIpyr17s82cao0HRH1N8g80knMz/aiw5b2YL/ndwnx1BQ9Fy2SNFAkEAo8dRzjocXGz7NQYs0CpSqHcuIzxuYGmwAlgoTugENXeTm5T9ORSpi2fwaBItjazD5qqsNZKJL9gA+FkxXXmHmQJAOMa2E3Qp4O3kKwn0dFdhce9/KSspDOPDH6giewXRGsiT+bgJ3uD0s7MNM36SraSv7ZpxcWvDA0ljRrKaQ6nBJQJBAOt0P+VPnAZABvEwvkFbHkiBXkU7HtqaSAlJmXQvVqeUqI0ODxuZR8TJ+LQccoguC7hSx6S2TyI3kCoJ0j0x6aXY";
我们想要转成对应的pem文件,但是我们不知道具体怎么断行,就得借助openssl的命令,首先将字符串私钥组装成下面这样,前后加上BEGIN... 和 END...,中间直接就是很长一串字符,不需要断行,保存成一个txt文件即可:
-----BEGIN RSA PRIVATE KEY-----
XIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAK2pZExJztpfUDxb73ygxCuzw0OnNXyVoo/t9ZXMhXVplZ5sVJGIoijk89YiQvunIciL1SCxG1SanXGoGyziwT1md0izwExYPM5SpD9xPcHlU8Z5pXXGNBz5jpsXTmHN2Js1VnjNgC7SQX1nxs8dV4IZM8Jm79KtVTifZ1uNxznpAgMBAAECgYAriWH5ti9Dk82btDiv9+X2RtTXKx3zbTgZ+UAuU93Jd7ToyKPS8Hwm4X846dH9IQNZMaU20u/VIAYErm5RrG9hOL7pUDCZltTXGrb1VBfpedzzkWschBzwXo4Htt27PXpubp0puQxAYUL9YbodKlX/y/IPBpQlOPAE/6EmTu6qYQJBAO594D8L+J6vCHbGqaf3MCYZIjMMa7FG0dLtnqL6aMLUILxwlYeU6XZutnPZCL9cpVbfVRWef5ZAx/N/mDB6tFUCQQC6aR/sbp617sBpp2yKtpL3QKo42DTxOSK1uIpyr17s82cao0HRH1N8g80knMz/aiw5b2YL/ndwnx1BQ9Fy2SNFAkEAo8dRzjocXGz7NQYs0CpSqHcuIzxuYGmwAlgoTugENXeTm5T9OwSpi2fwaBItjazD5qqsNZKJL9gA+FkxXXmHmQJAOMa2E3Qp4O3kKwn0dFdhce9/KSspDOPDH6giewXRGsiT+bgJ3uD0s7MNM36SraSv7ZpxcWvDA0ljRrKaQ6nBJQJBAOt0P+VPnAZABvEwvkFbHkiBXkU7HtqaSAlJmXQvVqeUqI0ODxuZR8TJ+LQccoguC7hSx6S2TyI3kCoJ0j0x6aX=
-----END RSA PRIVATE KEY-----
然后执行如下命令:
openssl rsa -in private_key.txt -out private_pkcs1.pem
txt文件就是我们上面用字符串私钥组装的文件,输出文件就是我们想要的pem,有特定断行的秘钥了,如下:
-----BEGIN RSA PRIVATE KEY-----
XIICXQIBAAKBgQCtqXRMSc7aX1A8W+98oMQrs8NDpzRMlaKP7fWVzIV1aZWebFSR
iKIo5PPWIkL7pyHIi9UgsRtUmp1xqBss4sE9ZndIs8BMWDzOUqQ/cT3B5VPGeaV1
xjQc+Y6bF05hzdibNVZ4zYAu0kF9Z8bPHVeCXTPCZu/SrVU4n2dbjcc56QIDAQAB
AoGAK4lh+bYvQ5PNm7Q4r/fl9kbU1ysd8204GflALlPdyXe06Mij0vB8JuBvOOnR
/SEDWTGlNtLv1SAGBK5uUaxvYTi+6VAwmZbU0Rq29VQX6Xnc85FrHIQc8F6OB7bd
uz16bm6dKbkMQGFC/WG6HSpV/8vyDwaUJTjwXP+hJk7uqmECQQDufeA/C/ierwh2
xqmn9zAmGSIzDGuxRtHS7Z6i+mjC1CC8cJWHlOg2brZz2Qi/XKVW31UVnn+WQMfz
f5gwerRVAkEAumkf7G6ete7AaadsiraS90CqONg08TkitbiKcq9e7PNnGqNB0R9T
fIPNJJzM/2osOW9mC/53cJ8dQUPRctkjRQJBAKPHUc46HFxs+zUGLNAqUqh3LiM8
bmBpsAJYKE7oBDV3k5uU/TsEqYtn8GgSLY2sw+aqrDWSiS/YAPhZMVxph5kCQDjG
thN0KeDt5CsJ9HRXYXHvfykrKQzjwx+oInsF0RrIk/m4Cd7g9LOzDTN+kq2kr+2a
cXFrwwNJY0aymkOpwSUCQQDrdD/lT5wGQAbxML5BWx5IgV5FOx7amkgJSZl0L1an
lKiNDg8bmUfEyfi0HHKILgu4Usektk8iN5AqCdI9Xemm
-----END RSA PRIVATE KEY-----
或者可以在线进行转换也可以,转换地址:OpenSSL在线生成合成PEM文件,把上面txt文件内容拷贝到网页上直接转换即可。
测试验证
最后使用python验证程序如下:
import base64
import rsa
priKey = "xxxx"
pubKey = "xxxx"
def str2pubkey(s):
# 对字符串解码
b_str = base64.b64decode(s)
if len(b_str) < 162:
return False
hex_str = ''
hex_str = b_str.hex()
# 找到模数和指数的开头结束位置
m_start = 29 * 2
e_start = 159 * 2
m_len = 128 * 2
e_len = 3 * 2
modulus = hex_str[m_start:m_start + m_len]
exponent = hex_str[e_start:e_start + e_len]
return modulus, exponent
def getPubkey():
key = str2pubkey(pubKey)
modulus = int(key[0], 16)
exponent = int(key[1], 16)
print('exponent:%d' % exponent)
rsa_pubkey = rsa.PublicKey(modulus, exponent)
return rsa_pubkey
def getPrikey():
with open('private_pkcs1.pem', 'rb') as f:
keyData = f.read()
rsa_prikey = rsa.PrivateKey.load_pkcs1(keyData)
return rsa_prikey
def main():
text = "123456789abc"
utf8_text = text.encode("utf-8")
encrypted_data = rsa.encrypt(utf8_text, getPubkey())
text_decrypted = rsa.decrypt(encrypted_data, getPrikey()) # 数据太长则需要分段
print('text_decrypted: %s' % text_decrypted)
if __name__ == '__main__':
print("__main__")
main()
总结
一般来说,使用加密是为了保证传输内容隐私,签名是为了保证消息真实性。服务器存私钥,客户端存公钥(服务器和客户端关系可以考虑为 1:N);客户端往服务器传输内容,更多考虑是隐私性,所以公钥签名、私钥解密。
服务器往客户端传输内容,更多考虑真实性,所以私钥签名,公钥验签。消息的摘要生的算法常用的是MD5或者SHA1,消息内容不一样,生成的摘要信息一定不一样。
真实性的考虑一方面是内容由私钥拥有者发出,另一方面内容在传输过程中没有改变过,所以签名的对象是传输信息生成的消息摘要(摘要内容短,签名也会快些)。
每次加密的长度需要小于密钥长度-特殊位(128位公钥,最长可加密128-11=117位明文)。每次解密的长度需要小于密钥的长度(128位私钥解密,解密密文长度需要小于等于128位)。如果加解密内容过长,就需要分段加密、解密。
参考