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。
我有一个 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 toPSS.MAX_LENGTH
在 RFC8017 中,它指定了 PKCS#1,因此也指定了 PSS,salt 长度的默认值定义为散列的输出长度 (s. A.2.3. RSASSA-PSS):
For a given
hashAlgorithm
, the default value ofsaltLength
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。