CryptographicException: ASN1 corrupted data in F#

Viewed 68

I want to hash a message with the RSA class using f#. Currently, I managed to create these few lines using the default randomly generated key pair.

let rsa = RSA.Create()
let dataInBytes = System.Text.Encoding.UTF8.GetBytes ("Message to be hashed.")
let bytes = rsa.SignData(dataInBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)

In order to use my own private/public key pair I want to use .ImportPkcs8PrivateKey(). My code is:

let privKey = System.IO.File.ReadAllText @"C:\Users\User\Documents\FSharp\privkey.pem";; 
val privKey: string =
  "-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASC"+[1671 chars]

let convertFromBase64ToBytes (input: string) =
    let str = match input.Length % 4 with
                | 1 -> input.Substring (1, input.Length - 1)
                | 2 -> input + string "=="
                | 3 -> input + string "="
                | _ -> input
    let strReplaced = str
                        .Replace("-", "+")
                        .Replace("_", "/")
    System.Convert.FromBase64String strReplaced

let privKeyBytes = convertFromBase64ToBytes privKey
> rsa.ImportPkcs8PrivateKey (privKeyBytes);;

but it throws me the error:

System.Security.Cryptography.CryptographicException: ASN1 corrupted data.
 ---> System.Formats.Asn1.AsnContentException: The encoded length exceeds the maximum supported by this library (Int32.MaxValue).
   at System.Formats.Asn1.AsnDecoder.ReadLength(ReadOnlySpan`1 source, AsnEncodingRules ruleSet, Int32& bytesConsumed)
   at System.Formats.Asn1.AsnDecoder.ReadEncodedValue(ReadOnlySpan`1 source, AsnEncodingRules ruleSet, Int32& contentOffset, Int32& contentLength, Int32& bytesConsumed)   
   at System.Security.Cryptography.CngPkcs8.ImportPkcs8PrivateKey(ReadOnlySpan`1 source, Int32& bytesRead)
   --- End of inner exception stack trace ---
   at System.Security.Cryptography.CngPkcs8.ImportPkcs8PrivateKey(ReadOnlySpan`1 source, Int32& bytesRead)
   at System.Security.Cryptography.RSAImplementation.RSACng.ImportPkcs8PrivateKey(ReadOnlySpan`1 source, Int32& bytesRead)
   at <StartupCode$FSI_0012>.$FSI_0012.main@() in C:\Users\User\PowerShell\stdin:line 26
Stopped due to error

What am I missing?

2 Answers

ImportPkcs8PrivateKey() expects a DER encoded key in PKCS#8 format. The posted key has the correct format, but is PEM encoded. The conversion from PEM to DER consists of removing the header, footer, all line breaks, and Base64 decoding the rest:

...
let derB64 = privKey.Replace("-----BEGIN PRIVATE KEY-----", "").Replace("-----END PRIVATE KEY-----", "").Replace("\r\n", "")
let privKeyBytes = Convert.FromBase64String derB64
rsa.ImportPkcs8PrivateKey privKeyBytes 
...

As of .NET 5, the ImportFromPem() method is available, which can directly import the PEM encoded key.

The convertFromBase64ToBytes() method converts Base64url encoded data to Base64 encoded data and then performs a Base64 decoding.
Since PEM encoded keys are Base64 encoded (and not Base64url encoded) this method is actually not necessary, instead Convert.FromBase64String() can be applied directly (if it is used anyway, it has no effect, because already Base64 encoded data is not changed).

The format that begins / ends with -----BEGIN XXX----- and ends with a similar line is called PEM encoding.

You are missing the fact that the base 64 also contains newlines and - etc. (so the length calculation fails). You should not have to pad with = symbols - those are mandatory for PEM. Similarly, PEM uses normal base 64, so there is no need to do anything with - or _ characters, as those should not be present (in the base 64, i.e. outside the header / footer lines obviously). In short, the base 64 (url) decoding fails on multiple levels.

Probably best to use PEM specific encoding / decoding routines. You can find those here if you're using a new runtime, or otherwise you can use the Bouncy Castle libraries.

Related