英文:
Encrypt a password using a public key + RSA in Python. What am I doing wrong?
问题
抱歉,我不能提供代码翻译。
英文:
I'm accessing an API that returns the following public key:
"publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArZj/8FWa9e2PmHIBzdMwA/Wo5HYyOHBOxORU5bVBOsb8ZJekhgNWplZxskpuMx1GC9m0WTvCHK+lLmlxKyOomu85q7MxocM8n7iF8Cc0Qrgjushut35FM1bT36em46eCCuO4WqG9/GhCsUeLTsQFBTUxF2Zk6++EJcmBgwU1yNvFZNUScfTmNSMpOcnWlGgt0GpOCdsx8GECOgZhwkJFDnUa01k4BeHYDJEufgNkq4lXh8wxep03S6RyZIAye9zDTaGhGvA5+loQq8bBWCbBzNTJWNhn1kpsnPQJHFcugLMYUyglzxk6phy1Et/s1ANH8H8jdRojhoJEVjg7+Y0JwwIDAQAB"
I need to use this public key to encrypt a password and send it to another endpoint in Base64 format.
What I need to do is exactly what this website does: https://www.devglan.com/online-tools/rsa-encryption-decryption
I've followed several tutorials, asked gpt chat for help and tested it in several ways but I can't.
When I encrypt the password using the website above and call the API directly through Postman it works, but when I encrypt it via Python it doesn't work. It says the encrypted password was not recognized.
My code:
@staticmethod
def get_pem_format(publickey: str):
lenkey = int(len(publickey) / 64)
key = ""
for x in range(0, lenkey+1):
startpos = x*64
endpos = startpos + 64
keylen = str(publickey[startpos:endpos])
key += f"{keylen}\n" if len(keylen) == 64 else keylen
key = f"-----BEGIN PUBLIC KEY-----\n{key}\n-----END PUBLIC KEY-----"
return key
def get_rsa_credentials(self, sourceid, newpass):
userkeyinfo = self._query_password_info(sourceid)
# Save public key
with open('/tmp/public_key.pem', 'w') as f:
f.write(self.get_pem_format(userkeyinfo["publicKey"]))
# Get public key
with open('/tmp/public_key.pem', 'rb') as f:
publickey = RSA.importKey(f.read())
cipher = PKCS1_OAEP.new(publickey)
encryptedpass = cipher.encrypt(newpass.encode())
base64encryptedpass = base64.b64encode(encryptedpass).decode()
return base64encryptedpass, userkeyinfo["publicKeyId"]
newpass = "Oliveir4souz@"
sourceid = "2c9180878168627f018192ff06f66ccb"
ecryptedpass, publickeyid = self.get_rsa_credentials(sourceid, newpass)
The _query_password_info method is where I call the api and get the public key.
And I created this method get_pem_format that generates the file in pem format, because all the libraries I found only carry the key of a file.
The code above does not generate any errors. But the encrypted value is not valid. But when I use the website as in the image below, it works perfectly in the API call.
I cannot understand what I am doing wrong.
答案1
得分: 1
这个问题是由不同的填充方式引起的:在“Cipher Type”字段中使用RSA(就像在RSA/ECB/PKCS1Padding中一样),网站应用了PKCS#1 v1.5填充。由于网站生成的密文可以被端点成功解密,显然端点也使用了PKCS#1 v1.5填充。
然而,在Python代码中,cipher = PKCS1_OAEP.new(publickey)
指定了OAEP作为填充方式,这就是为什么Python代码与网站(或端点)不兼容。为了解决问题,Python代码中必须使用 cipher = PKCS1_v1_5.new(publickey)
,以便在那里也应用PKCS#1 v1.5填充。通过这种改变,使用Python代码生成的密文可以通过在“Cipher Type”字段中使用RSA的网站成功解密(并且也应该能够被端点成功解密)。
为完整起见:PKCS1_OAEP()
使用默认的SHA-1用于内容摘要和MGF1摘要来应用OAEP填充,这等同于网站选项中的RSA/ECB/OAEPWithSHA-1AndMGF1Padding。
作为附注:PyCryptodome支持以不同格式和编码导入和导出密钥。这样,您可以将Base64编码的ASN.1/DER编码密钥(以X.509/SPKI格式)转换为PEM编码的密钥,方法如下:
key = RSA.import_key(base64.b64decode(publickey)).exportKey(format='PEM').decode('utf8')
其中 publickey
是:MIIBIj...
。
关于网站上发布的关于加密的截图:请记住,RSA加密(包括OAEP和PKCS#1 v1.5)是非确定性的,即使用相同的密钥和明文进行加密会生成不同的密文。因此,如果使用相同的密钥和明文进行测试,得到不同的密文并不表示错误。
英文:
The problem is caused by different paddings: With RSA (just like with RSA/ECB/PKCS1Padding) in the Cipher Type field, the website applies PKCS#1 v1.5 padding. Since the ciphertext generated with the website can be successfully decrypted by the endpoint, the endpoint obviously uses PKCS#1 v1.5 padding as well.
However, in the Python code, cipher = PKCS1_OAEP.new(publickey)
specifies OAEP as padding, which is why the Python code on the one hand and the website (or the endpoint) on the other hand are incompatible. To fix the problem, cipher = PKCS1_v1_5.new(publickey)
must be used in the Python code so that PKCS#1 v1.5 padding is applied there as well. With this change, the ciphertext generated with the Python code can be successfully decrypted using the website with RSA in the Cipher Type field (and should also be successfully decrypted by the endpoint).
For completeness: PKCS1_OAEP()
applies OAEP as padding using the default SHA-1 for content digest and MGF1 digest, which is equivalent to the RSA/ECB/OAEPWithSHA-1AndMGF1Padding option of the website.
As side note: PyCryptdome supports import and export of keys in different formats and encodings. This way you can convert your Base64 encoded ASN.1/DER encoded key (in X.509/SPKI format) into a PEM encoded key as follows:
key = RSA.import_key(base64.b64decode(publickey)).exportKey(format='PEM').decode('utf8')
where publickey
is: MIIBIj...
.
Regarding the posted screenshot on encryption with the website: Keep in mind that RSA encryptions (both with OAEP and PKCS#1 v1.5) are non-deterministic, i.e. encryptions with the same key and plaintext generate different ciphertexts. Therefore, it is not an indication of an error if tests with identical key and plaintext result in different ciphertexts.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论