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.
CodePudding user response:
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.