Reputation: 95
I need to take the RSA PSS signatures of a message generated from Python and validate in .NET. But validation in .NET fails.
I have generated the RSA key pair using the following command:
openssl req -x509 -nodes -newkey rsa:4096 -keyout /tmp/certs/private.pem -out /tmp/certs/public.pem -days 365
Python code:
from cryptography import x509
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from nacl.encoding import HexEncoder
def sign(message: str) -> str:
with open("/tmp/certs/private.pem", "rb") as pem:
private_key = serialization.load_pem_private_key(
pem.read(),
password=None,
backend=default_backend(),
)
return HexEncoder.encode(
private_key.sign(
message.encode("utf-8"),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256(),
)
).decode("utf-8")
def verify(message: str, signature: str) -> bool:
with open("/tmp/certs/public.pem", "rb") as pem:
cert = x509.load_pem_x509_certificate(pem.read(), default_backend())
public_key = cert.public_key()
try:
signature = HexEncoder().decode(signature)
public_key.verify(
signature,
message.encode(),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256(),
)
return True
except InvalidSignature:
return False
def main():
message = "hello"
signature = sign(message)
print(f"Signature: {signature}")
# print(f"isValidated: {verify(message, signature)}")
if __name__ == '__main__':
main()
C# code targeting .NET 5.0:
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace HelloWorld
{
internal static class Program
{
private static string ByteArrayToString(IReadOnlyCollection<byte> ba)
{
var hex = new StringBuilder(ba.Count * 2);
foreach (var b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
private static byte[] StringToByteArray(string hex)
{
var numberChars = hex.Length;
var bytes = new byte[numberChars / 2];
for (var i = 0; i < numberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
private static string Sign(string message)
{
var rsa = RSA.Create();
rsa.ImportFromPem(File.ReadAllText("/tmp/certs/private.pem"));
var messageBytes = Encoding.UTF8.GetBytes(message);
var signature = rsa.SignData(messageBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
return ByteArrayToString(signature);
}
private static bool Verify(string message, string signature)
{
var rsa = RSA.Create();
var cert = X509Certificate.CreateFromCertFile("/tmp/certs/public.pem");
var publicKey = cert.GetPublicKey();
rsa.ImportRSAPublicKey(publicKey, out _);
var messageBytes = Encoding.UTF8.GetBytes(message);
var signatureBytes = StringToByteArray(signature);
return rsa.VerifyData(messageBytes, signatureBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
}
public static void Main()
{
const string message = "hello";
// var signature = Sign(message);
const string signature = <value_from_python>;
// Console.WriteLine($"Signature is: {signature}");
Console.WriteLine($"isValidated: {Verify(message, signature)}");
}
}
}
Upvotes: 2
Views: 1798
Reputation: 49121
The verification fails because both codes use different salt lengths. The Python code explicitly applies the maximum salt length, the C# code defaults to the digest output length. The latter is also defined as PSS default in RFC8017, A.2.3. RSASSA-PSS.
So to fix this
signature length - digest output length - 2 = 512 - 32 - 2 = 478
(for a 4096 bits key).Org.BouncyCastle.Crypto.Signers.PssSigner
, which also provides constructors for defining the salt length. You can find an example here, last section.Upvotes: 3