大家好。
我想知道是否有可能使用 PyCrypto 进行双重 RSA/PKCS#1 加密。
我有一个服务器,它有自己的RSA密钥(安装所述服务器时使用openssl
命令生成)和一个可以请求服务器密钥的公共部分的客户端。此外,该客户端可以要求服务器为其生成另一个 RSA 密钥(或密钥对)。在这种情况下,服务器还会保留私有(或"整个"RSA 密钥),并向客户端发送其密钥的公共部分。
我一直在玩RSA/PKCS和AES加密。我创建了一个测试Python文件,该文件仅使用一个RSA密钥即可进行加密。它的作用是使用对称AES系统(使用"即时"生成的随机密钥)加密数据,使用RSA/PKCS#1系统密码用于AES并将其放入要发送的结果中:
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Interesting links:
# 1> http://stackoverflow.com/a/9039039/289011
# 2> http://eli.thegreenplace.net/2010/06/25/aes-encryption-of-files-in-python-with-pycrypto/
from Crypto.PublicKey import RSA
import base64
import os
from Crypto.Cipher import AES
import Crypto.Util.number
import random
import struct
import cStringIO
from Crypto.Cipher import PKCS1_OAEP
def encrypt(string):
#Begin RSA Part to get a cypher that uses the server's public key
externKeyFilename="/home/borrajax/rsaKeys/server-key.pub"
externKeyFile = open(externKeyFilename, "r")
rsaKey= RSA.importKey(externKeyFile, passphrase="F00bAr")
pkcs1Encryptor=PKCS1_OAEP.new(rsaKey)
#End RSA Part
#Begin AES Part
iv = ''.join(chr(random.randint(0, 0xFF)) for i in range(16))
thisMessagePassword = os.urandom(16)
aesEncryptor = AES.new(thisMessagePassword, AES.MODE_CBC, iv)
chunksize=64*1024
#End AES Part
#Begin RSA Encription of the AES Key
rsaEncryptedPassword = pkcs1Encryptor.encrypt(thisMessagePassword)
retvalTmp = cStringIO.StringIO()
retvalTmp.write(struct.pack('<Q', len(string)))
retvalTmp.write(struct.pack('<Q', len(rsaEncryptedPassword)))
retvalTmp.write(rsaEncryptedPassword)
retvalTmp.write(iv)
while len(string) > 0:
chunk = string[0:chunksize]
string = string[chunksize:]
if len(chunk) % 16 != 0:
chunk += ' ' * (16 - len(chunk) % 16)
retvalTmp.write(aesEncryptor.encrypt(chunk))
return retvalTmp.getvalue()
def decrypt(string):
stringAsBuffer = cStringIO.StringIO(string)
retval = str()
chunksize=64*1024
externKeyFilename="/home/borrajax/rsaKeys/server-key.pem"
externKey = open(externKeyFilename, "r")
rsaKey = RSA.importKey(externKey, passphrase="F00bAr")
pkcs1Decryptor=PKCS1_OAEP.new(rsaKey)
origsize = struct.unpack('<Q', stringAsBuffer.read(struct.calcsize('Q')))[0]
rsaEncryptedPasswordLength = long(struct.unpack('<Q', stringAsBuffer.read(struct.calcsize('Q')))[0])
rsaEncryptedPassword = stringAsBuffer.read(rsaEncryptedPasswordLength)
thisMessagePassword = pkcs1Decryptor.decrypt(rsaEncryptedPassword)
iv = stringAsBuffer.read(16)
decryptor = AES.new(thisMessagePassword, AES.MODE_CBC, iv)
while True:
chunk = stringAsBuffer.read(chunksize)
if len(chunk) == 0:
break
retval += decryptor.decrypt(chunk)
return retval
if __name__ == "__main__":
encryptedThingy=encrypt(base64.b64encode("Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉"))
print "Decrypted thingy: %s" % base64.b64decode(decrypt(encryptedThingy))
如您所见,AES 密码是使用服务器的 RSA 密钥加密的。现在,我想格外偏执,并使用客户端的公钥加密已经加密的密码,因此"加密"方法如下所示:
def encrypt(string):
#Begin RSA Part to get a cypher that uses the server's public key
externServerKeyFilename="/home/borrajax/rsaKeys/server-key.pub"
externServerKeyFile = open(externServerKeyFilename, "r")
rsaServerKey= RSA.importKey(externServerKeyFile, passphrase="F00bAr")
pkcs1ServerEncryptor=PKCS1_OAEP.new(rsaServerKey)
#End RSA Part
#Begin RSA Part to get a cypher that uses the client's public key
externClientKeyFilename="/home/borrajax/rsaKeys/client-key.pub"
externClientKeyFile = open(externClientKeyFilename, "r")
rsaClientKey= RSA.importKey(externClientKeyFile, passphrase="F00bAr")
pkcs1ClientEncryptor=PKCS1_OAEP.new(rsaClientKey)
#End RSA Part
#Begin AES Part
iv = ''.join(chr(random.randint(0, 0xFF)) for i in range(16))
thisMessagePassword = os.urandom(16)
aesEncryptor = AES.new(thisMessagePassword, AES.MODE_CBC, iv)
chunksize=64*1024
#End AES Part
#Begin RSA Encription of the AES Key
rsaEncryptedPasswordWithServer = pkcs1ServerEncryptor.encrypt(thisMessagePassword)
rsaEncryptedPasswordWithServerAndClient = pkcs1ClientEncryptor.encrypt(rsaEncryptedPasswordWithServer) #Katacrasssshh here!!
retvalTmp = cStringIO.StringIO()
retvalTmp.write(struct.pack('<Q', len(string)))
retvalTmp.write(struct.pack('<Q', len(rsaEncryptedPasswordWithServerAndClient)))
#...Probably some yadda yadda here with key lengths and stuff so it would help re-build the keys in the server's side...
retvalTmp.write(rsaEncryptedPasswordWithServerAndClient)
retvalTmp.write(iv)
while len(string) > 0:
chunk = string[0:chunksize]
string = string[chunksize:]
if len(chunk) % 16 != 0:
chunk += ' ' * (16 - len(chunk) % 16)
retvalTmp.write(aesEncryptor.encrypt(chunk))
return retvalTmp.getvalue()
但是当我尝试重新加密密钥时,我得到了一个ValueError("Plaintext too large")
异常。这是有道理的(至少对几乎不了解加密的人来说是有意义的),因为 PKCS 添加了一个填充,所以当我使用服务器的公钥加密" thisMessagePassword
"时,我得到一个 256 字节的字符串,这对于第二个 PKCS 加密器来说太长了(我一直在做一些"手动测试",限制似乎是 214 字节......我的意思是。。。这是不会引发异常的最后一个值)。
我知道这可能是一个奇怪的结构,使用服务器的公钥进行加密和使用客户端的密钥签名可能更有意义,但我只是想玩一些加密的东西,并试图理解它们是如何工作的以及为什么。这就是为什么任何提示都会受到赞赏。
提前谢谢你!
PKCS1OAEP.encrypt
的文档对其输入进行了如下说明:
消息(字符串):要加密的消息,也称为纯文本。它可以是可变长度,但不能超过RSA模数(以字节为单位)减去2,减去哈希输出大小的两倍。
SHA-1(默认哈希函数)具有 160 位摘要,即 20 个字节。您看到的限制听起来是正确的:256 = 214 + 2 + 2*20。
除此之外,您计划添加的额外步骤不会增加太多价值。如果你想让客户端向服务器证明真的是他,而不是其他人,你应该向客户端提供私钥,并让服务器保留公共的一半。在加密步骤之后,客户端可以使用PKCS#1 PSS对整个包(包装的AES密钥+加密数据)进行签名并发送签名。服务器将使用客户端的公钥验证源,然后使用自己的私钥解密密钥,最后使用 AES 解密数据。
我不建议你这样做,或者建议它有任何意义,但如果你只是想玩它,你可以这样做。
- 确保您应用的第一个 RSA 密钥的模数小于您应用的第一个 RSA 密钥的模量。
- 使用较小的模数和适当的 PKCS#1 填充执行第一个 RSA 加密
- 使用较大的模数和无填充执行第二个 RSA 加密。
在解密时,您当然必须颠倒这些操作的顺序。
所以,你正在做的事情似乎没有什么意义。您想安全地将消息从服务器发送到客户端吗?
您拥有的代码尝试在服务器公钥下加密消息,然后在客户端的公钥下加密消息。客户端将无法读取此密钥,因为他永远不应该拥有服务器的私钥(这是读取在服务器公钥下加密的消息所必需的)。或者换句话说,如果服务器和客户端都有相同的私钥,那么你应该只使用 AES。你为什么要这么做?
真的,您可能应该使用 ssl/tls/https 向客户端发送消息,因为编写加密代码是有问题的,并且您在代码中至少犯了两个错误以及您想要修复的错误。
-
您的静脉注射需要安全随机。python 随机调用不是,这就是为什么你使用 os.random(16) 作为密钥。您也应该为 IV 这样做
您需要使用 hmac 对 加密数据进行身份验证,并使用单独的随机密钥对 hmac 进行密钥。 然后在另一端使用相同的键,在相同的输入上重新生成 hmac,并比较两者。如果您不这样做,有人可能会篡改您的数据并使用加密库中的错误来读取它。
您发布的问题:请注意,正如我上面所说,您根本不应该这样做,因为它是荒谬的。您需要在新密钥下使用 AES 加密 rsaEncryptedPasswordWithServer(并按照上面的 2 使用 HMAC),然后使用客户端公钥加密新密钥。