0

大家好。

我想知道是否可以使用 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 字节......我的意思是......这是最后一个不抛出异常的值)。

我知道这可能是一个奇怪的结构,使用服务器的公钥进行加密和使用客户端的密钥进行签名可能更有意义,但我只是想尝试一下加密的东西并尝试了解它们是如何进行的工作以及为什么。这就是为什么任何提示都会受到赞赏的原因。

先感谢您!

4

3 回答 3

3

的文档PKCS1OAEP.encrypt对它的输入进行了以下说明:

message (string) - 要加密的消息,也称为明文。它可以是可变长度,但不能长于 RSA 模数(以字节为单位)减去 2,减去哈希输出大小的两倍。

SHA-1(默认散列函数)有一个 160 位的摘要,即 20 个字节。您看到的限制听起来很正确:256 = 214 + 2 + 2*20。

除此之外,您计划添加的额外步骤不会增加太多价值。如果您希望客户端向服务器证明它确实是他,而不是其他人,您应该向客户端提供私钥并让服务器保留公共的一半。在加密步骤之后,客户端可以使用 PKCS#1 PSS 对整个包(封装的 AES 密钥 + 加密数据)进行签名并发送签名服务器将使用客户端的公钥验证来源,然后使用自己的私钥解密密钥,最后使用 AES 解密数据。

于 2012-04-18T22:47:26.597 回答
1

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

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

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

  1. 您的 IV 必须是安全随机的。python 随机调用不是,这就是为什么您使用 os.random(16) 作为密钥。您也应该为 IV 这样做

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

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

于 2012-04-18T22:12:00.087 回答
1

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

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

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

于 2012-04-19T03:30:39.973 回答