PyCrypto:使用 RSA 和 PKCS#1 对字符串加密两次



大家好。

我想知道是否有可能使用 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 解密数据。

我不建议你这样做,或者建议它有任何意义,但如果你只是想玩它,你可以这样做。

  1. 确保您应用的第一个 RSA 密钥的模数小于您应用的第一个 RSA 密钥的模量。
  2. 使用较小的模数和适当的 PKCS#1 填充执行第一个 RSA 加密
  3. 使用较大的模数和无填充执行第二个 RSA 加密。

在解密时,您当然必须颠倒这些操作的顺序。

所以,你正在做的事情似乎没有什么意义。您想安全地将消息从服务器发送到客户端吗?

您拥有的代码尝试在服务器公钥下加密消息,然后在客户端的公钥下加密消息。客户端将无法读取此密钥,因为他永远不应该拥有服务器的私钥(这是读取在服务器公钥下加密的消息所必需的)。或者换句话说,如果服务器和客户端都有相同的私钥,那么你应该只使用 AES。你为什么要这么做?

真的,您可能应该使用 ssl/tls/https 向客户端发送消息,因为编写加密代码是有问题的,并且您在代码中至少犯了两个错误以及您想要修复的错误。

  1. 您的静脉注射需要安全随机。python 随机调用不是,这就是为什么你使用 os.random(16) 作为密钥。您也应该为 IV 这样做

  2. 您需要使用 hmac 对
  3. 加密数据进行身份验证,并使用单独的随机密钥对 hmac 进行密钥。 然后在另一端使用相同的键,在相同的输入上重新生成 hmac,并比较两者。如果您不这样做,有人可能会篡改您的数据并使用加密库中的错误来读取它。

  4. 发布的问题:请注意,正如我上面所说,您根本不应该这样做,因为它是荒谬的。您需要在新密钥下使用 AES 加密 rsaEncryptedPasswordWithServer(并按照上面的 2 使用 HMAC),然后使用客户端公钥加密新密钥。

相关内容

  • 没有找到相关文章

最新更新