Akki
Akki

Reputation: 95

RSA PSS signature verification between python (cryptography) and .NET5 (System.Security.Cryptography) fails

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

Answers (1)

Topaco
Topaco

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

  • either use the digest output length in the Python code, i.e. 32 (bytes for SHA256),
  • or in the C# code the maximum salt length: signature length - digest output length - 2 = 512 - 32 - 2 = 478 (for a 4096 bits key).
    As far as I know this is not possible with .NET onboard means, but with BouncyCastle and the class Org.BouncyCastle.Crypto.Signers.PssSigner, which also provides constructors for defining the salt length. You can find an example here, last section.

Upvotes: 3

Related Questions