python实现RSA与AES混合加密
RSA加密
这是一种非对称加密算法,密钥分为公钥和私钥。通常私钥长度有512bit,1024bit,2048bit,4096bit,长度越长,越安全,但是生成密钥越慢,加解密也越耗时。一般公钥用来加密数据,私钥用来解密数据,并且为防止有人冒充发送密文,私钥又可以负责签名,公钥负责验证。公钥可以分发给其他系统,但是私钥一般保存在唯一的系统中。
AES加密
这是一种对称加密算法,加密和解密公用同一密钥。密钥最长只有256个bit,执行速度快,易于硬件实现。通常发送方使用密钥加密数据后会使用口令对密钥进行加密,然后把密文和加密后的密钥一同发送给接收方,接收方接收到数据后首先使用口令对密钥进行解密,在使用密钥对密文进行解密。
以上两种加密方式都有各自的特点,在互联网领域有着广泛的应用,我们需要根据不同的场景使用不同的加密解密方案。
一般需要加密传输的时候存在以下两种场景:
客户端发送加密数据,服务端解密后返回明文:
例如用户在客户端提交个人信息时,客户端把个人信息加密后发送给服务端,服务端再返回code或者提交成功的提示
客户端发送加密数据,服务端解密后同样返回密文
例如用户登录时,客户端发送加密后的账号密码,服务端解密后生成对应的token,并且对token进行加密,客户端收到返回的token再对其进行解密
如何生成RSA公钥和私钥可以参考我的另一篇文章
RSA公钥和私钥的生成以及PKCS#1与PKCE#8格式的转换
如何在python实现RSA?
可以直接用python的rsa库
import rsa
(pubkey, privkey) = rsa.newkeys(1024)
print('公钥:\n%s' % pubkey)
print('私钥:\n%s' % privkey)
message = 'hello'
encrypt_text = rsa.encrypt(message.encode(), pubkey)
print('加密后的密文:\n%s' % encrypt_text)
decrypt_text = rsa.decrypt(encrypt_text, privkey)
print('解密后的明文:\n%s' % decrypt_text)
运行结果
但是这只是简单的实现,中间省去了很多细节,这就导致我们以后跨语言加密解密的时候可能会出问题。比如java在实现RSA加密解密的时候有很多参数需要设置,如果java和python之间的参数不能保持一致,则会导致用java加密的密文用python解密不了,或者用python加密的密文用java解密不了。所以还是推荐使用可以设置更多参数的第三方RSA库。python的第三方RSA库有crypto、pycrypto和pycryptodome。crypto不推荐使用,已经停止维护了。可以使用pycrypto,不过我的电脑安装pycrypto会报错。
错误信息
提示我安装Microsoft Visual C++ 14.0,所以我直接用的pycryptodome,其实都差不多。
Config配置类
class Config:
PUBLIC_KEY = '''
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDv/LnAoDkyewwjwqwgi9VSg51F
+tUJ8cGwL6Rqdf5ZXrRCHI1KLjOxdFbzB81YjS76cOzezQRz2vuYDo7OvLfYSjFI
fmukUxN+EliKkg0TwswylVroLBW9OKN70Zd62dc+gfkA3Vu8cDoRKzz6BKpo4yDo
0D3FOsbNEj80opjmtQIDAQAB
'''
PRIVATE_KEY = '''
MIICXAIBAAKBgQDv/LnAoDkyewwjwqwgi9VSg51F+tUJ8cGwL6Rqdf5ZXrRCHI1K
LjOxdFbzB81YjS76cOzezQRz2vuYDo7OvLfYSjFIfmukUxN+EliKkg0TwswylVro
LBW9OKN70Zd62dc+gfkA3Vu8cDoRKzz6BKpo4yDo0D3FOsbNEj80opjmtQIDAQAB
AoGADuZtDgWkp3q2TT4X+8lSzFW5nQ+uzHhDI1JB7g43ZYsYvAYTy6hEs17ayyoP
2NCjOw9p1Yd7IEpXVqCIw1M6QsfGdshy1NStsGpDHQYBBd8XiT8cWUaT/nmq5dEs
i0wOITMZePLgI5/5pD4M6DIEJKskM+Rzlo47AiyRchL6pqECQQD+XAZNCl6R5wjI
DrqW4v6Vw8mhdaPnQhPexmhHa1f9D7sA32A2H2N8M3dUDOwuG+DJhPkjVaQtFvT8
mjDjSZTdAkEA8Yj4hncF/WnLTDSXmiWfpNwYwjfpjOj8e4/5rWHF1jWZMgl0l1AS
Otna2dIbXp64dqsInITJTIDSQpbxuhrvuQJBAN9Ee6toLLa5KzYf55zGR13Ca9wz
3NkDYVmsop/+E0/oXOdZK6SWTMcajeXTKgUXJ2r8M4vWgrOpcQXBeqQnVGkCQDYX
e7j5bOD80Wemm5EM/fy4wd61ENvazbiKXNske17msAFRtsewSfTeFzIS6Mg++Yax
9QLAhihY7T22ejo4kBkCQBdg2yKHQrmG+njGfLsdQG9MARFlnOfohoBFQTYdtrmf
5JRNfwtPiis2YaoM2gP7z2qaunYbibDV5SYmtdD8GK0=
'''
RSA工具类
import base64
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from app.config import Config
class RSACipher():
'''
RSA加密、解密、签名、验签工具类
'''
def encrypt(self, key, text):
'''
加密方法
:param key: 公钥
:param text: 需要加密的明文
:return: 加密后的密文
'''
public_key = RSA.importKey(base64.b64decode(key))
cipher = PKCS1_OAEP.new(public_key, hashAlgo=SHA256)
return base64.b64encode(cipher.encrypt(text.encode())).decode()
def decrypt(self, key, text):
'''
解密方法
:param key: 私钥
:param text: 加密后的密文
:return: 解密后的明文
'''
private_key = RSA.importKey(base64.b64decode(key))
cipher = PKCS1_OAEP.new(private_key, hashAlgo=SHA256)
return cipher.decrypt(base64.b64decode(text)).decode()
def sign(self, key, text):
'''
签名方法
:param key: 私钥
:param text: 需要签名的文本
:return: 签名信息
'''
private_key = RSA.importKey(base64.b64decode(key))
hash_value = SHA256.new(bytes(text, encoding="utf-8"))
signer = PKCS1_v1_5.new(private_key)
signature = signer.sign(hash_value)
return base64.b64encode(signature).decode()
def verify(self, key, text, signature):
'''
验签方法
:param key: 公钥
:param text: 需要验签的文本
:param signature: 签名信息
:return: 验签结果
'''
public_key = RSA.importKey(base64.b64decode(key))
hash_value = SHA256.new(bytes(text, encoding="utf-8"))
verifier = PKCS1_v1_5.new(public_key)
return verifier.verify(hash_value, base64.b64decode(signature))
if __name__ == '__main__':
text = 'hello'
cipher = RSACipher()
# 加密
encrypt_text = cipher.encrypt(Config.PUBLIC_KEY, text)
print('加密后:\n%s' % encrypt_text)
# 签名
signature = cipher.sign(Config.PRIVATE_KEY, text)
print('签名:\n%s' % signature)
# 解密
decrypt_text = cipher.decrypt(Config.PRIVATE_KEY, encrypt_text)
print('解密后:\n%s' % decrypt_text)
# 验签
result = cipher.verify(Config.PUBLIC_KEY, decrypt_text, signature)
print('验签:\n%s' % result)
运行结果:
加密后:
f/Ju3QYLvlhGd+EKxlH/MZQX9g2XQ8nkTq5zWP7sCnoQUmPZPAb1H8//j+2B9Uz7HdiUtys2wtk1eWsD7/TSsWz5XDWA/LFXcN0r4AdJnaI1HvLZ8625Ahh3YYia8DmW7j7IvhNDJIES1otlPKgfRb7srDr+p7x/YzrZTFp66PM=
签名:
N5CAgMhhNzTNeyjY8bCSxPOcrUL48ZIDz4LpJGuncAsDwn/sFYr2Tba57DZF2XoXXCLt+3A46Wk7O+758XmxIDmOlOzdIqBtq6X9Ya9W/1f8mR2igc1xTvfNGt5Eb5xLjmATEk6grFx0bzMd4SJg0p5ayEH4fyCrXMlCKarEsCs=
解密后:
hello
验签:
True
签名有什么用?
下面我们模拟两个场景
第一个场景:客户端发送加密数据给服务端
- 客户端使用公钥对数据进行加密
- 客户端发送密文给服务器端
- 服务端使用私钥进行解密
第二个场景:服务端发送数据给客户端
- 服务端使用私钥对数据进行签名
- 服务器端发送签名和数据给客户端
- 客户端使用公钥进行验签,如果验签出来的内容和数据一致,则证明消息是服务端回复的
在第一个场景中,即使客户端发送的加密消息被截获,也不会造成泄密,因为只有服务端用私钥才能解密,而第二个场景中即使签名和数据都被截获,也没有危险性,因为只有服务端的私钥才能进行签名,所以即使知道消息内容也无法伪造带签名的回复给客户端,防止了消息的纂改。
虽然上面的场景中防止了泄密和纂改的问题,但是仍然存在安全隐患。在第一个场景中虽然解决了泄密的问题,但是可以通过公钥加密假信息发送给服务端。而第二个场景中,虽然解决了纂改的问题但是造成了数据的泄露。所以我们可以改进一下验证的方式,客户端和服务端同时生成一套公钥私钥,并且把客户端的公钥和服务端的私钥进行交换。
综合上面两个场景:客户端发送加密数据给服务端
- 客户端使用服务端公钥进行加密,并且使用客户端私钥进行签名
- 客户端发送密文和签名给服务端
- 服务端使用服务端私钥进行解密,并使用客户端公钥进行验签,证明消息来自客户端
服务端返回数据同理
下面贴上改进后的代码:
Config配置类
class Config:
CLIENT_PUBLIC_KEY = '''
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDv/LnAoDkyewwjwqwgi9VSg51F
+tUJ8cGwL6Rqdf5ZXrRCHI1KLjOxdFbzB81YjS76cOzezQRz2vuYDo7OvLfYSjFI
fmukUxN+EliKkg0TwswylVroLBW9OKN70Zd62dc+gfkA3Vu8cDoRKzz6BKpo4yDo
0D3FOsbNEj80opjmtQIDAQAB
'''
CLIENT_PRIVATE_KEY = '''
MIICXAIBAAKBgQDv/LnAoDkyewwjwqwgi9VSg51F+tUJ8cGwL6Rqdf5ZXrRCHI1K
LjOxdFbzB81YjS76cOzezQRz2vuYDo7OvLfYSjFIfmukUxN+EliKkg0TwswylVro
LBW9OKN70Zd62dc+gfkA3Vu8cDoRKzz6BKpo4yDo0D3FOsbNEj80opjmtQIDAQAB
AoGADuZtDgWkp3q2TT4X+8lSzFW5nQ+uzHhDI1JB7g43ZYsYvAYTy6hEs17ayyoP
2NCjOw9p1Yd7IEpXVqCIw1M6QsfGdshy1NStsGpDHQYBBd8XiT8cWUaT/nmq5dEs
i0wOITMZePLgI5/5pD4M6DIEJKskM+Rzlo47AiyRchL6pqECQQD+XAZNCl6R5wjI
DrqW4v6Vw8mhdaPnQhPexmhHa1f9D7sA32A2H2N8M3dUDOwuG+DJhPkjVaQtFvT8
mjDjSZTdAkEA8Yj4hncF/WnLTDSXmiWfpNwYwjfpjOj8e4/5rWHF1jWZMgl0l1AS
Otna2dIbXp64dqsInITJTIDSQpbxuhrvuQJBAN9Ee6toLLa5KzYf55zGR13Ca9wz
3NkDYVmsop/+E0/oXOdZK6SWTMcajeXTKgUXJ2r8M4vWgrOpcQXBeqQnVGkCQDYX
e7j5bOD80Wemm5EM/fy4wd61ENvazbiKXNske17msAFRtsewSfTeFzIS6Mg++Yax
9QLAhihY7T22ejo4kBkCQBdg2yKHQrmG+njGfLsdQG9MARFlnOfohoBFQTYdtrmf
5JRNfwtPiis2YaoM2gP7z2qaunYbibDV5SYmtdD8GK0=
'''
SERVER_PUBLIC_KEY = '''
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDxpMZQCTykdwUcUzyHgd6Q79de
4/F26bhIpOVCDpWNxlLQFdbGneTTQ1AJz/wwfNMgEPMnJvV3ZLrbqH9uV5W+8NG0
UDaXyZYo8fXhfD7Aeret6/CgH1iZamzR4DfADCvT+V81cjeGIhJ1JSYfxGIsC4mM
35TZ5p530ayOJ1KPHwIDAQAB
'''
SERVER_PRIVATE_KEY = '''
MIICXQIBAAKBgQDxpMZQCTykdwUcUzyHgd6Q79de4/F26bhIpOVCDpWNxlLQFdbG
neTTQ1AJz/wwfNMgEPMnJvV3ZLrbqH9uV5W+8NG0UDaXyZYo8fXhfD7Aeret6/Cg
H1iZamzR4DfADCvT+V81cjeGIhJ1JSYfxGIsC4mM35TZ5p530ayOJ1KPHwIDAQAB
AoGBAL8E8ZvNYXnleE3G4t9/41ARuOATMws8gOg0KeMJImI7t7U0vl6t7HixCnFn
T8WIt2Du5Tg7DOo/35LK5Ul1xTHtYmQBdxTbg1WT89s3RWEvL4epHZQxzCQFJ1Pz
zjDFifPNDEA7ZME7sx/E2qPDinAD+JHNELhtNMDq5rhPuYGRAkEA/W6mZCbDfrY9
/qcbOe2s8ugeoDqoJueP7owfrut11KzvUn+e2rIBeDn1BZbWlYY5VDEq1nIKen+B
0su4QUiiaQJBAPQXjC+YgyBzoEyMLPiA+eRC28zuwP2LXGJ1FNPZpCgnaJzF215H
vhISwKX+b1/WZq3qBHyQnbE5zBHEIn5I5EcCQQC6X9k15dv3H4bP84xuOX/q0xFS
vFBU7A5JW/sg5EAvO0502S21nxq9k8HBboA4ThFy/QWH1y4lkAelQfQq7oOhAkBh
Jk4hU249KEgQr2nmrk7HTuT0t8IQJ7tpZHgZqXHwmV7FpuocqCk6QER0zMO/PTI4
3f9TJKvescZK++lOoexZAkB0XNnpYA4fOMKhyKbcGvpqKLFb3e2ks5LbjgeAkETt
dKipEh1RbiPrJeOwOChsx/51/cnVJrabE50AJV8AXM3e
'''
RSA工具类
import base64
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from app.config import Config
class RSACipher():
"""
RSA加密、解密、签名、验签工具类
"""
def encrypt(self, key, raw):
"""
加密方法
:param key: 公钥
:param raw: 需要加密的明文 bytes
:return: base64编码的密文 bytes
"""
public_key = RSA.importKey(base64.b64decode(key))
cipher = PKCS1_OAEP.new(public_key, hashAlgo=SHA256)
return base64.b64encode(cipher.encrypt(raw))
def decrypt(self, key, enc):
"""
解密方法
:param key: 私钥
:param enc: base64编码的密文 bytes
:return: 解密后的明文 bytes
"""
private_key = RSA.importKey(base64.b64decode(key))
cipher = PKCS1_OAEP.new(private_key, hashAlgo=SHA256)
return cipher.decrypt(base64.b64decode(enc))
def sign(self, key, text):
"""
签名方法
:param key: 私钥
:param text: 需要签名的文本 bytes
:return: base64编码的签名信息 bytes
"""
private_key = RSA.importKey(base64.b64decode(key))
hash_value = SHA256.new(text)
signer = PKCS1_v1_5.new(private_key)
signature = signer.sign(hash_value)
return base64.b64encode(signature)
def verify(self, key, text, signature):
"""
验签方法
:param key: 公钥
:param text: 需要验签的文本 bytes
:param signature: base64编码的签名信息 bytes
:return: 验签结果 bool
"""
public_key = RSA.importKey(base64.b64decode(key))
hash_value = SHA256.new(text)
verifier = PKCS1_v1_5.new(public_key)
return verifier.verify(hash_value, base64.b64decode(signature))
if __name__ == '__main__':
# 客户端代码
text = b'hello server!'
cipher = RSACipher()
# 使用服务端公钥加密
encrypt_text = cipher.encrypt(Config.SERVER_PUBLIC_KEY, text)
print('加密后:\n%s' % encrypt_text)
# 使用客户端私钥签名
signature = cipher.sign(Config.CLIENT_PRIVATE_KEY, encrypt_text)
print('签名:\n%s' % signature)
# 服务端代码
# 使用客户端公钥验签
result = cipher.verify(Config.CLIENT_PUBLIC_KEY, encrypt_text, signature)
print('验签:\n%s' % result)
# 使用服务端私钥解密
decrypt_text = cipher.decrypt(Config.SERVER_PRIVATE_KEY, encrypt_text)
print('解密后:\n%s' % decrypt_text)
运行结果:
加密后:
b'miG43R3Xz5hyUntwfvToY96rhqgvsXzZGW6+VErgTdgs2Ew/wDlBlhjgpJQt3b13Rnq0m3sMydw7jly7/yRGW/eWaeOhuJRUQ6y1pGbvt3jIgnJvsj3XwT+S1Zah5Po87c51rQ7UamVmUvh4qFCZLzYdgXGfLLeknOTzVd+WIJo='
签名:
b'sG4qoMTccZOlrSzn6y16qGAVNtdBdnbduHPbNbEmCo+7Pe2AUg4VRK9VjG67Hqitwoa5AmHWe2M4DyolhEuuSYwrriYnSR2y6flBv0yb0G0CqJ4VODYVDlRrKTLLnTBnHoBeKNhqruir5JJ5cB9oE8XnE5rGI1yCPPhtBRjUXMM='
验签:
True
解密后:
b'hello server!'
如何在python中实现AES加密?
AES工具类
import base64
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
class AESCipher:
"""
AES加密、解密工具类
"""
def __init__(self, key):
self.key = key
# 这里直接用key充当iv
self.iv = key
def encrypt(self, raw):
"""
加密方法
:param raw: 需要加密的密文 str
:return: base64编码的密文 str
"""
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
return base64.b64encode(cipher.encrypt(self.__pad(raw).encode())).decode()
def decrypt(self, enc):
"""
解密方法
:param enc: base64编码的密文 str
:return: 解密后的明文 str
"""
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
return self.__unpad(cipher.decrypt(base64.b64decode(enc)).decode())
def __pad(self, text):
# 填充方法,加密内容必须为16字节的倍数
text_length = len(text)
amount_to_pad = AES.block_size - (text_length % AES.block_size)
if amount_to_pad == 0:
amount_to_pad = AES.block_size
pad = chr(amount_to_pad)
return text + pad * amount_to_pad
def __unpad(self, text):
# 截取填充的字符
pad = ord(text[-1])
return text[:-pad]
if __name__ == '__main__':
# 随机生成16位aes密钥
cipher = AESCipher(get_random_bytes(16))
text = "hello"
encrypt = cipher.encrypt(text)
print('加密后:\n%s' % encrypt)
decrypt = cipher.decrypt(encrypt)
print('解密后:\n%s' % decrypt)
运行结果:
加密后:
2IVIT2hWtZo1WQ2xMd0LUg==
解密后:
hello server!
RSA+AES混合使用
客户端发送加密信息给服务端流程
- 客户端使用服务端公钥对数据进行加密
- 客户端使用客户端私钥对密文进行签名
- 客户端发送密文和签名给服务端
- 服务端使用客户端公钥对签名进行验签
- 服务端使用服务端私钥对密文进行解密
代码模拟客户端向服务端发送加密信息:
from Crypto.Random import get_random_bytes
from app.AESCipher import AESCipher
from app.RSACipher import RSACipher
from app.config import Config
# 客户端代码
text = 'hello server!'
# 随机生成aes的密钥
aes_key = get_random_bytes(16)
print('随机生成的aes密钥:\n%s' % aes_key)
aes_cipher = AESCipher(aes_key)
rsa_cipher = RSACipher()
# 使用aes密钥对数据进行加密
encrypt_text = aes_cipher.encrypt(text)
print('经过aes加密后的数据:\n%s' % encrypt_text)
# 使用客户端私钥对aes密钥签名
signature = rsa_cipher.sign(Config.CLIENT_PRIVATE_KEY, aes_key)
print('签名:\n%s' % signature)
# 使用服务端公钥加密aes密钥
encrypt_key = rsa_cipher.encrypt(Config.SERVER_PUBLIC_KEY, aes_key)
print('加密后的aes密钥:\n%s' % encrypt_key)
# 客户端发送密文、签名和加密后的aes密钥
print('\n************************分割线************************\n')
# 接收到客户端发送过来的signature encrypt_key encrypt_text
# 服务端代码
# 使用服务端私钥对加密后的aes密钥解密
aes_key = rsa_cipher.decrypt(Config.SERVER_PRIVATE_KEY, encrypt_key)
print('解密后的aes密钥:\n%s' % aes_key)
# 使用客户端公钥验签
result = rsa_cipher.verify(Config.CLIENT_PUBLIC_KEY, aes_key, signature)
print('验签结果:\n%s' % result)
# 使用aes私钥解密密文
aes_cipher = AESCipher(aes_key)
decrypt_text = aes_cipher.decrypt(encrypt_text)
print('经过aes解密后的数据:\n%s' % decrypt_text)
运行结果:
随机生成的aes密钥:
b't\xb6\x8da\x8f2fa\xdc\x05\xc0\xff\x97\x84\xa2\xa9'
经过aes加密后的数据:
9TbA3VDRrLV9cz/ky/dUAQ==
签名:
b'o5sdTd+0vBUNV2zRslIvUppRIDpb4HYfMhEAQEogih5nQmLvTy6h/1hIoUS240KasoiewUAfV+V9VABkNszIzWKh1B0HKpItwpzLDmbTd3iqE2BlIx3C3b+ak1Y14YTdI626RqhrCZQMAvlxerWWYu+sMCBCTfNt+WkKNiUUJvM='
加密后的aes密钥:
b'Ybpxv9jtLhKLiUiIBTR7aRmOivvbh9KTIqjM9rMIqn4PezRftBqDa8HU4vn2y/2PEsPUY8tLbCkGC1E07d93ZrNrEX9PbU0zYTeWpRpJwfF0yWB41RonyWxQIO4cETOtqEitnRs4DRehqgVufU1SeEx45Wwy7wCGBOnudoo4bpA='
************************分割线************************
解密后的aes密钥:
b't\xb6\x8da\x8f2fa\xdc\x05\xc0\xff\x97\x84\xa2\xa9'
验签结果:
True
经过aes解密后的数据:
hello server!
服务端发送加密信息给客户端同理,只是加密方从客户端变成了服务端,解密方从服务端变成了客户端。
最后附上java版RSA和AES混合加密文章链接与相关源码:
java实现RSA与AES混合加密
python,java跨语言RSA+AES混合加密解密以及踩过的那些坑
https://github.com/taoyimin/rsa-aes-python
https://github.com/taoyimin/rsa-aes-java