So i tried to verify rsa-ps signature that signed in golang with php library phpseclib v3. The php verification keep getting me an error. But what makes me confuse is when i try sign the signature in php and verify that in both golang and php it works.
this is the code that i use for sign and verify the signature in golang
Sign signature
rng := rand.Reader
rsa.SignPSS(rng, key, crypto.SHA256, digest, nil)
Verify signature
rsa.VerifyPSS(key, crypto.SHA256, digest, signature, nil)
and this is the code to verify and sign the signature in php
Verify signature
$key = RSA::loadPublicKey('the key');
$result = $key->withHash('sha256')->withMGFHash('sha256')->verify($message, $signature);
Sign signature
$key = RSA::loadPrivateKey('the key');
$key = $private->withPadding(RSA::SIGNATURE_PSS);
$signature = $private->withHash('sha256')->withMGFHash('sha256')->sign($message);
NOTE The value of signature return in encrypted base64 and i already decrypt the value before verify the signature
Anyone can explain hows that happen ? and what should i do to make it works for both ?
PSS allows the specification of various parameters (s. RFC8017, 9.1.1): Digest, mask generation function (in practice, MGF1 is applied and only its digest can be specified) and salt length.
The verification with PHP fails because although you specify the digests explicitly (so that they are identical in both codes), you use the library defaults for the salt length, which are different. Therefore, the two codes are not compatible:
In your Go code, in the SignPSS()
call the 5th parameter is specified as nil
, so saltLength()
gives PSSSaltLengthAuto
, resulting in the maximum possible salt length being applied.
The maximum possible salt length is signature length - digest output length - 2
in bytes (e.g. with a 2048 bits signature / key length and SHA256 as digest: 256 - 32 - 2
bytes).
In the PHP code, the digest output length (32 bytes for SHA256) is used by default for the salt length, s. here.
Therefore, in order for the PHP code to verify the signature of the Go code, the salt length must be explicitly set to the maximum size using withSaltLength()
:
$key = RSA::load($pubKey);
$result = $key
->withHash('sha256') // default (in V3)
->withMGFHash('sha256') // default (in V3)
->withSaltLength(256 - 32 - 2) // Fix! Replace 256 with your signature / key length in bytes
->verify($message, $signature);
print($result ? 'valid signature' : 'invalid signature'); // valid signature
For completeness: The maximum salt length can also be determined with:
$key->getLength()/8 - $key->getHash()->getLengthInBytes() - 2
Alternatively, the digest output length (32 bytes for SHA256), which is applied by default in the PHP code, can be used as salt length in the Go code:
var ops rsa.PSSOptions
ops.SaltLength = rsa.PSSSaltLengthEqualsHash
signature, err := rsa.SignPSS(rng, keyPkcs8, crypto.SHA256, digest, &ops)
In practice, the digest output length is often used as salt length.