实施“ openssl_private_encrypt”在最新的Python中3个版本

发布于 2025-02-09 08:59:20 字数 1154 浏览 1 评论 0 原文

我正在尝试在Python维护Fastspring电子商务平台安全有效负载API实现。

他们的文档中有示例用于加密(或技术上签名?)Java和php中使用私钥的有效载荷: https://developer.fastspring.com/docs/pass-a-secure-request-request#locally-cencrypt-eccrypt-censession-sessise-object

,我有以前是基于此存储库的基于Python“密码学”库的实现:

但是,该依靠无证件的OPENSSL openssl“ _lib.rib.rsa_rib.rsa_private_encrivate_encrivate_encrivate_encrypt()较高,该函数的函数更高。借助最新的Python版本,它不再包含二进制包,并且PIP必须从源头进行编译。

pycryptodome似乎包括与PKCS#1 V1.5填充的类似RSA私钥签名,但是它要求有效载荷是一个哈希对象,因此自然而然地,生成的输出与FastSpring所期望的不符合我使用的hash功能,而我使用的是什么:<

pkcs1_15#pkcs-1-v1-5-rsa 没有成功的“私钥加密”。因此,我的问题是:有什么方法可以使用最新的Python库进行此操作,还是我坚持使用过时的密码库,直到根本不再支持它?

I'm trying to maintain FastSpring e-commerce platform Secure Payload api implementation in Python.

Their documentation has examples for encrypting (or technically signing?) the payload with private key in Java and PHP: https://developer.fastspring.com/docs/pass-a-secure-request#locally-encrypt-the-session-object

And I have been previously using a Python "cryptography" library based implementation based on this repository:
https://github.com/klokantech/flask-fastspring/blob/0462833f67727cba9755a26c88941f11f0159a48/flask_fastspring.py#L247

However, that relies on undocumented openssl "_lib.RSA_private_encrypt()" function that is no longer exposed in cryptography versions higher than 2.9.2, which is already several years old. And with latest python versions it no longer includes binary packages and PIP must compile it from source.

PyCryptodome seems to include similar RSA private key signing with PKCS #1 v1.5 padding, but it requires payload to be a Hash object, so naturally the produced output doesn't match what FastSpring expects regardless of what Hash function I use: https://pycryptodome.readthedocs.io/en/latest/src/signature/pkcs1_v1_5.html?highlight=pkcs1_15#pkcs-1-v1-5-rsa

I have been trying to figure out any alternative ways to implement this kind of "private key encryption" without success. So my question is: Is there ANY way to do this with up-to-date python libraries or am I stuck to use an outdated cryptography library until it no longer is supported at all?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

蒗幽 2025-02-16 08:59:20

这两个链接的代码使用 rsassa-pkcs1-v1_5 ,但修改后的编码用于消息而不是,因此处理与标准不同。

两个主要的python加密库 pycryptodome 加密 仅支持封装整个整个过程的高级签名,遵循标准,因此不允许对消息编码进行任何修改。

解决问题的最有效方法是使用也支持低级签名的Python库,以便可以使用链接的Java或Python代码的消息编码。但是,我不知道这样的图书馆。

如果您也不知道这样的库,则有以下选择:因为RSASSA-PKCS1-V1_5非常简单,Python支持大整数及其操作,因此与EG 的辅助功能结合使用了自定义实现。 pycryptodome 很容易。至少您不必再依靠旧库:

from Crypto.Util import number

def customizedSign(key, msg):
    
    modBits = number.size(key.n)
    k = number.ceil_div(modBits, 8) 
  
    ps = b'\xFF' * (k - len(msg) - 3)
    em = b'\x00\x01' + ps + b'\x00' + msg 
  
    em_int = number.bytes_to_long(em)
    m_int = key._decrypt(em_int) 
    signature = number.long_to_bytes(m_int, k)
    
    return signature

说明:
该实现遵循 pycryptodome sign() 方法。 功能差异是,使用链接代码的编码代替EMSA-PKCS1-V1_5。
EMSA-PKCS1-V1_5定义为:

EM = 0x00 || 0x01 || PS || 0x00 || T

其中 t 是DER编码的DigestInfo值的串联和Hashed消息的串联,请参见在这里
链接代码的编码只需使用消息 msg 而不是 t

EM = 0x00 || 0x01 || PS || 0x00 || MSG 

在两种情况下, ps ps 是一个带有0xff值的填充物钥匙大小(即模量的大小)。

用法和测试:

由于签名是确定性的,因此相同的密钥和相同的消息始终提供相同的签名。这样,很容易证明上述功能等效于链接的Java或Python代码:

from Crypto.PublicKey import RSA
import base64

# For simplicity, a 512 bits key is used. Note that a 512 bits key may only be used for testing, in practice the key size has to be >= 2048 bits for security reasons.
pkcs8 = """-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA2gdsVIRmg5IH0rG3
u3w+gHCZq5o4OMQIeomC1NTeHgxbkrfznv7TgWVzrHpr3HHK8IpLlG04/aBo6U5W
2umHQQIDAQABAkEAu7wulGvZFat1Xv+19BMcgl3yhCdsB70Mi+7CH98XTwjACk4T
+IYv4N53j16gce7U5fJxmGkdq83+xAyeyw8U0QIhAPIMhbtXlRS7XpkB66l5DvN1
XrKRWeB3RtvcUSf30RyFAiEA5ph7eWXbXWpIhdWMoe50yffF7pW+C5z07tzAIH6D
Ko0CIQCyveSTr917bdIxk2V/xNHxnx7LJuMEC5DcExorNanKMQIgUxHRQU1hNgjI
sXXZoKgfaHaa1jUZbmOPlNDvYYVRyS0CIB9ZZee2zubyRla4qN8PQxCJb7DiICmH
7nWP7CIvcQwB
-----END PRIVATE KEY-----""" 

key = RSA.import_key(pkcs8)
msg = 'The quick brown fox jumps over the lazy dog'.encode('utf8')
signature = customizedSign(key, msg)

print(base64.b64encode(signature).decode('utf8')) # OwpVG/nPmkIbVxONRwXHvOqLdYNnP67YtiWA+GcKBZ3rIzAJ+8izvmlqUQnzVp03Wrrzq2ogUmCMaLSPlInDNw==

链接的Java代码为相同的密钥和消息提供了相同的签名。

The two linked codes implement low level signing using RSASSA-PKCS1-v1_5, but a modified encoding is used for the message rather than EMSA-PKCS1-v1_5, and therefore the processing differs from the standard.

The two major Python crypto libraries PyCryptodome and Cryptography only support high level signing, which encapsulates the entire process, follows the standard and thus does not allow any modification of the encoding of the message.

The most efficient way to solve the problem would be to use a Python library that also supports a low level signing, so that the encoding of the message from the linked Java or Python code can be used. However, I am not aware of such a library.

If you don't know such a library either, there is the following alternative: Since RSASSA-PKCS1-v1_5 is pretty simple and Python supports large integers and their operations natively, a custom implementation in combination with the helper functions of e.g. PyCryptodome is easily possible. At least you wouldn't have to rely on the legacy library anymore:

from Crypto.Util import number

def customizedSign(key, msg):
    
    modBits = number.size(key.n)
    k = number.ceil_div(modBits, 8) 
  
    ps = b'\xFF' * (k - len(msg) - 3)
    em = b'\x00\x01' + ps + b'\x00' + msg 
  
    em_int = number.bytes_to_long(em)
    m_int = key._decrypt(em_int) 
    signature = number.long_to_bytes(m_int, k)
    
    return signature

Explanation:
The implementation follows the PyCryptodome implementation of the sign() method. The only functional difference is that instead of EMSA-PKCS1-v1_5 the encoding of the linked codes is used.
EMSA-PKCS1-v1_5 is defined as:

EM = 0x00 || 0x01 || PS || 0x00 || T

where T is the concatenation of the DER encoded DigestInfo value and the hashed message, see here.
The encoding of the linked codes simply uses the message MSG instead of T:

EM = 0x00 || 0x01 || PS || 0x00 || MSG 

In both cases, PS is a padding with 0xFF values up to the key size (i.e. size of the modulus).

Usage and test:

Since the signature is deterministic, the same key and the same message always provide the same signature. This way it is easy to show that the above function is equivalent to the linked Java or Python code:

from Crypto.PublicKey import RSA
import base64

# For simplicity, a 512 bits key is used. Note that a 512 bits key may only be used for testing, in practice the key size has to be >= 2048 bits for security reasons.
pkcs8 = """-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA2gdsVIRmg5IH0rG3
u3w+gHCZq5o4OMQIeomC1NTeHgxbkrfznv7TgWVzrHpr3HHK8IpLlG04/aBo6U5W
2umHQQIDAQABAkEAu7wulGvZFat1Xv+19BMcgl3yhCdsB70Mi+7CH98XTwjACk4T
+IYv4N53j16gce7U5fJxmGkdq83+xAyeyw8U0QIhAPIMhbtXlRS7XpkB66l5DvN1
XrKRWeB3RtvcUSf30RyFAiEA5ph7eWXbXWpIhdWMoe50yffF7pW+C5z07tzAIH6D
Ko0CIQCyveSTr917bdIxk2V/xNHxnx7LJuMEC5DcExorNanKMQIgUxHRQU1hNgjI
sXXZoKgfaHaa1jUZbmOPlNDvYYVRyS0CIB9ZZee2zubyRla4qN8PQxCJb7DiICmH
7nWP7CIvcQwB
-----END PRIVATE KEY-----""" 

key = RSA.import_key(pkcs8)
msg = 'The quick brown fox jumps over the lazy dog'.encode('utf8')
signature = customizedSign(key, msg)

print(base64.b64encode(signature).decode('utf8')) # OwpVG/nPmkIbVxONRwXHvOqLdYNnP67YtiWA+GcKBZ3rIzAJ+8izvmlqUQnzVp03Wrrzq2ogUmCMaLSPlInDNw==

The linked Java code provides the same signature for the same key and message.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文