I have a 10 character code that I want to sign by my python program, then put both the code as well as the signature in an URL, which then get’s processed by a PHP SLIM API. Here the signature should get verified.
I generated my RSA keys in python like this:
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)
I sign the key like this in 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() )
The url is built like this
my_url = 'https://www.exmaple.com/codes?code={}&signature={}'.format( code, signature.hex() )
Because the signature is a byte object I’m converting it to a string using the .hex()
function
Now, in PHP, I am trying to verify the code and signature:
use phpseclib3CryptPublicKeyLoader; $key = PublicKeyLoader::load(file_get_contents(__DIR__ . "/public_key.pem")); echo $key->verify($code, pack('h*', $signature)) ? 'yes' : 'no';
I also tried using PHP openssl_verify
$pub_key = file_get_contents(__DIR__ . "/public_key.pem"); $res = openssl_verify($code, pack('n*', $signature), $pub_key, OPENSSL_ALGO_SHA256);
However, it always tells me the signature is wrong, when I obviously know, that in general it is the correct signature. The RSA keys are all the correct and same keys in both python and php. I think the issue is with the signature and how I had to convert it to a string and then back to a bytes like string in both python and php.
Advertisement
Answer
The Python code uses 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
In RFC8017, which specifies PKCS#1 and thus also PSS, the default value of the salt length is defined as the output length of the hash (s. A.2.3. RSASSA-PSS):
For a given
hashAlgorithm
, the default value ofsaltLength
is the octet length of the hash value.
Most libraries, e.g. PHPSECLIB, apply for the default value of the salt length the default defined in RFC8017, i.e. the output length of the 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
for a 2048 bits key and SHA256.
Thus, the verification in the PHP code must be changed as follows:
$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()
Note that in the posted code of the question h
(hex string, low nibble first) is specified in the format string of pack()
. I’ ve chosen the more common H
(hex string, high nibble first) in my code snippet which is also compatible with Python’s hex()
. Ultimately, the format string to choose depends on the encoding applied in the Python code.
Using this change, on my machine, the signature generated with the Python code can be successfully verified with the PHP code.
Alternatively, of course, the salt length of the Python code can be adapted to the output length of the digest (32 bytes in this case).
By the way, a verification with openssl_verify()
is not possible, because PSS is not supported.