RSA:Python 已在 PHP 中验证的签名邮件

RSA: Python signed message verified in PHP

我有一个 10 个字符的代码,我想用我的 python 程序签名,然后将代码和签名都放在 URL 中,然后由 PHP 苗条 API。这里的签名应该得到验证。

我在 python 中生成了我的 RSA 密钥,如下所示:

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization


def gen_key():
    private_key = rsa.generate_private_key(
        public_exponent=65537, key_size=2048, backend=default_backend()
    )
    return private_key


def save_key(pk):
    pem_priv = pk.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
    )
    with open(os.path.join('.', 'private_key.pem'), 'wb') as pem_out:
        pem_out.write(pem_priv)

    pem_pub = pk.public_key().public_bytes(
        encoding=serialization.Encoding.PEM,
        format=crypto_serialization.PublicFormat.SubjectPublicKeyInfo
    )
    with open(os.path.join('.', 'public_key.pem'), 'wb') as pem_out:
        pem_out.write(pem_pub)


def main():
    priv_key = gen_key()
    save_key(priv_key)

我在 python 中这样签署密钥:

private_key = load_key()
pub_key = private_key.public_key()

code = '09DD57CE10'
signature = private_key.sign(
 str.encode(code),
 padding.PSS(
  mgf=padding.MGF1(hashes.SHA256()),
  salt_length=padding.PSS.MAX_LENGTH
 ),
 hashes.SHA256()
)

url是这样搭建的

my_url = 'https://www.exmaple.com/codes?code={}&signature={}'.format(
        code,
        signature.hex()
    )

因为签名是字节对象,所以我使用 .hex() 函数将其转换为字符串

现在,在PHP,我正在尝试验证代码和签名:

use phpseclib3\Crypt\PublicKeyLoader;
$key = PublicKeyLoader::load(file_get_contents(__DIR__ . "/public_key.pem"));
echo $key->verify($code, pack('h*', $signature)) ? 'yes' : 'no';

我也试过用PHP openssl_verify

$pub_key = file_get_contents(__DIR__ . "/public_key.pem");
$res = openssl_verify($code, pack('n*', $signature), $pub_key, OPENSSL_ALGO_SHA256);

然而,它总是告诉我签名是错误的,当我明明知道,一般来说它是正确的签名。 RSA 密钥在 python 和 php 中都是正确且相同的密钥。 我认为问题在于签名以及我如何将其转换为字符串,然后在 python 和 php.

中转换回类似字符串的字节

Python代码使用PSS.MAX_LENGTH as the salt length. This value denotes the maximum salt length and is recommended in the Cryptography documentation (s. here):

salt_length (int) – The length of the salt. It is recommended that this be set to PSS.MAX_LENGTH

在 RFC8017 中,它指定了 PKCS#1,因此也指定了 PSS,salt 长度的默认值定义为散列的输出长度 (s. A.2.3. RSASSA-PSS):

For a given hashAlgorithm, the default value of saltLength is the octet length of the hash value.

大多数图书馆,例如PHPSECLIB,申请RFC8017中定义的默认salt长度的默认值,即hash的输出长度(s.here). Therefore the maximum salt length must be set explicitly. The maximum salt length is given by (s. here):

signature length (bytes) - digest output length (bytes) - 2 = 256 - 32 - 2 = 222 

对于 2048 位密钥和 SHA256。

因此,PHP代码中的验证必须修改如下:

$verified = $key->
            withPadding(RSA::SIGNATURE_PSS)->
            //withHash('sha256')->                  // default
            //withMGFHash('sha256')->               // default
            withSaltLength(256-32-2)->              // set maximum salt length
            verify($code, pack('H*', $signature));  // alternatively hex2bin()

请注意,在问题的发布代码中,h(十六进制字符串,低半字节在前)在pack() 的格式字符串中指定。我在我的代码片段中选择了更常见的 H(十六进制字符串,高半字节优先),它也与 Python 的 hex() 兼容。最终,要选择的格式字符串取决于 Python 代码中应用的编码。

使用此更改,在我的机器上,使用 PHP 代码可以成功验证使用 Python 代码生成的签名。

或者,当然,Python代码的salt长度可以适应摘要的输出长度(本例中为32字节)。

顺便说一句,无法使用 openssl_verify() 进行验证,因为不支持 PSS。