Search code examples
c#securityrsapycryptoasp.net-core-6.0

RSA Public key not importing into C# - ASN1 corrupted data exception


I am creating a set of RSA keys in Python with the Crypto library, then exporting the public key, and sending it to a .NET Core 6 application. But .NET is having trouble reading the public key.

Python Crypto library code - this code is running on a Ubuntu Linux development PC:

key = RSA.generate(2048)
public_key = base64.b64encode(key.publickey().exportKey()).decode('utf8')

C# code - this is running on a Windows 10 development PC:

byte[] publicKeyBytes = Convert.FromBase64String(base64PublicKey);
RSA rsa = RSA.Create();
rsa.ImportRSAPublicKey(publicKeyBytes, out int bytesRead);

The error I received is:

AsnContentException: The provided data is tagged with 'Universal' class value '13', but it should have been 'Universal' class value '16'.

Looking at this SO article, and at this GitHub issue, it looked like I needed a different import method. So I also tried changing the last line to:

rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out int bytesRead);

But I got the exact same error. Only the stacktrace was a little different because it was using a different provider.

If it helps, below is a link to the full help text for the Crypto library's export_key() method. But this is the part I thought was most relevant, and what made me think that ImportSubjectPublicKeyInfo() would work:

This (the pkcs) parameter is ignored for a public key. For DER and PEM, an ASN.1 DER SubjectPublicKeyInfo structure is always used.

Edit: this might be related to encoding, or possibly a little endian vs big endian issue between libraries...

Here is a link to the full description of the exportKey() function.

Edit: the important part turned out to be the format parameter defaults to PEM formatting.


Solution

  • On the Python side, key.publickey().exportKey() returns a PEM encoded key in X.509/SPKI format. An additional Base64 encoding is not necessary:

    from Crypto.PublicKey import RSA
    
    key = RSA.generate(2048)
    public_key = key.publickey().exportKey().decode('utf8')
    print(public_key)
    

    gives e.g.:

    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5mPsHC34l7uz/Qlxma0s
    fibiql2hcqbNVG/jyW6qAD9w+L9/ZBmaSo+wLMKI+xXVp5aHqDlgS07PxcWWLXFf
    FLwdIk12rEXisbEPIj+3O+Fayqvk9NuUh9sDiVGOX7pMlnICFc+CdB3mK0sFRGkK
    zOq3FJO19VxhzAI0dPQ6HDD4SLz/onuOoYMpcVfPa6bWDEe9MBCc1U+ypn9JNaq+
    WrUjEw5RJbYCz451WPLa6wMKyIwA0EPck+h1yQc9+VCXwoK97awKE7wEPBwEG9HV
    7UYMLJiVGvPkJypR+9+h/3M7CGVruhQwZG9Cz7QEqNm7oBQuyki5jyFAQueq8nKd
    kwIDAQAB
    -----END PUBLIC KEY-----
    

    On the C# side, a PEM encoded key can be imported directly with RSA.ImportFromPem(), which is available since .NET 5.
    Alternatively RSA.ImportSubjectPublicKeyInfo() can be used to import a public key in X.509/SPKI format, which is available since .NET Core 3.0. However, this can only import DER encoded keys. From the PEM encoded key, the DER encoded key can be easily derived by removing header, footer and line breaks and Base64 decoding the rest.
    Since you are working on .NET 6 and both implementations are available, RSA.ImportFromPem() is the most convenient:

    using System.Security.Cryptography;
    ...
    string pemX509 = @"-----BEGIN PUBLIC KEY-----
                    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5mPsHC34l7uz/Qlxma0s
                    fibiql2hcqbNVG/jyW6qAD9w+L9/ZBmaSo+wLMKI+xXVp5aHqDlgS07PxcWWLXFf
                    FLwdIk12rEXisbEPIj+3O+Fayqvk9NuUh9sDiVGOX7pMlnICFc+CdB3mK0sFRGkK
                    zOq3FJO19VxhzAI0dPQ6HDD4SLz/onuOoYMpcVfPa6bWDEe9MBCc1U+ypn9JNaq+
                    WrUjEw5RJbYCz451WPLa6wMKyIwA0EPck+h1yQc9+VCXwoK97awKE7wEPBwEG9HV
                    7UYMLJiVGvPkJypR+9+h/3M7CGVruhQwZG9Cz7QEqNm7oBQuyki5jyFAQueq8nKd
                    kwIDAQAB
                    -----END PUBLIC KEY-----";
    RSA rsa = RSA.Create();
    rsa.ImportFromPem(pemX509);
    

    For completeness: RSA.ImportRSAPublicKey(), available as of .NET Core 3.0, is intended for importing a DER encoded public key in PKCS#1 format, which does not apply to the exported key.