使 RSA 加密在 Python (PyCrypto) 和 PHP (OpenSSL) 中兼容

Making RSA encryption compatible in Python (PyCrypto) and PHP (OpenSSL)

我正在迁移整个 PHP API,虽然我之前使用过 PyCrypto,但我不确定如何转换以下加密调用,因为我需要完全相同的结果。 PHP 调用是:

define('KEY', "-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC81t5iu5C0JxYq5/XNPiD5ol3Z
w8rw3LtFIUm7y3m8o8wv5qVnzGh6XwQ8LWypdkbBDKWZZrAUd3lybZOP7/82Nb1/
noYj8ixVRdbnYtbsSAbu9PxjB7a/7LCGKsugLkou74PJDadQweM88kzQOx/kzAyV
bS9gCCVUguHcq2vRRQIDAQAB
-----END PUBLIC KEY-----");
$cypher = "";
$result = openssl_public_encrypt($plain, $cypher, KEY, OPENSSL_PKCS1_PADDING);
echo bin2hex($cypher);

假设一切正常,这将打印来自 $cypher 的内容,并传递给十六进制。对于示例输入 "azzzzzzzzzzzzdfdf",我得到如下内容:"2281aeebc1166cdfb2f17a0a0775d927ca5a9ad999bae0e4954f58bd8082fdf7efe1fd284876530341f714456d7eb8cd44c57b20ab27029b84d5dc77a674bede3fe9065282931404286082e9df8607bdcff0818b90324dfee7d76b566d0f99bebc5cc913372c276ba373712128f1bcc226b59367cff93f7cdd6dbde25b366863".

我必须假定此值是正确的,因为代码取自我正在迁移的现有 API。但是,尝试对 PyCrypto 进行同样的操作(是的,我正在迁移 API 以便在 Python 中可用),我使用以下代码:

def bin2hex(s):
    return "".join([hex(ord(c))[2:].zfill(2) for c in s])

KEY = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC81t5iu5C0JxYq5/XNPiD5ol3Z
w8rw3LtFIUm7y3m8o8wv5qVnzGh6XwQ8LWypdkbBDKWZZrAUd3lybZOP7/82Nb1/
noYj8ixVRdbnYtbsSAbu9PxjB7a/7LCGKsugLkou74PJDadQweM88kzQOx/kzAyV
bS9gCCVUguHcq2vRRQIDAQAB
-----END PUBLIC KEY-----"""

from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
encrypter = PKCS1_v1_5.new(RSA.importKey(KEY))
print bin2hex(encrypter.encrypt("azzzzzzzzzzzzdfdf"));

虽然我希望返回并打印相同的值,但最终值是 "3dd94ffabd01bb0e94010c0fedbcd4eb648f12e5d9e6d934b77ae86f76681d8a1b790cad9fddf6e6720415b4d645e525c33c402fa9778739b8e461790387e9508f7158a5fdc5723f5fc26d166b11a00759f0e0ee3ba6719a2e7c6b918f66e1311d1fff878ee2ca8762e1d6120f1e9585a76cdc7719ca20129ae76182b4277170"

使用 PKCS1_OAEP 输出 "290f60f37088c2cb46ae9221b01ff46a463f270ef7cf70bbea49de0b5ae43aec34a0eb46e694cf22f689eb77e808c590fdc30eda09f9d3f3cb8c15e0505bf5a984c2a121bc9fa83c6b5ccf50235f072467b4ae9cdf0f0ee2e486626ffa62ad1aa715fbe29e8afe4ceab3ca5a5df4c1dc75d7f258285d7ff1f4f2b4dcb7a8413a".

很容易看出我必须修复我的 python 代码。如何修复我的 python 代码,使其 returns 与给定 PHP 调用的结果完全相同?

你的代码没问题。 pyCrypto 中的 PKCS#1 v1.5 填充是随机的 (source)。因此,即使您使用相同的密钥和明文,加密也会始终不同。这是一个令人向往的 属性.

如果您想检查 pyCrypto 和 PHP 的 OpenSSL 扩展之间的兼容性,那么您需要在一个中加密,在另一个中解密并检查您是否得到了预期的结果。


现在不应使用 PKCS#1 v1.5 填充,因为有针对它的有效攻击。 OAEP 是一个更好的选择。

我已经为同样的问题苦苦挣扎了很长时间,但终于设法解决了这个问题。 主要问题是设置正确的 SHA 哈希值,在 PHP 的 openssl_public_encrypt() 情况下,它应该是 SHA1。

这是 openssl_public_encrypt() 的代码 OPENSSL_PKCS1_OAEP_PADDING:

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

def encrypt_key(message):
    with open('public_key', 'rb') as key_file:
        pub_key = key_file.read()
    public_key = load_pem_public_key(pub_key, default_backend())
    ciphertext = public_key.encrypt(
        message,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA1()),
            algorithm=hashes.SHA1(),
            label=None
            )
        )
    return ciphertext

基本上是关于 RSA 的 cryptography.io 文档的副本。