Recursive AI

Load a PEM or PKCS#8 private key in C#

All onecrypto recipes

Crypto.Load reads a key or certificate file without being told its format: PEM or DER, PKCS#8 plain or encrypted, traditional RSA PRIVATE KEY and EC PRIVATE KEY PEM (including the old DEK-Info encryption), PKCS#12 and OpenSSH. Each private key comes back ready to use. The program below loads three keys made by OpenSSL, one of them Ed25519, which .NET can’t load at all.

Terminal
dotnet new console -o LoadKeys && cd LoadKeys
dotnet add package RecursiveAI.OneCrypto --version 0.1.0-preview.1
openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:3072 \
  -aes-256-cbc -pass pass:s3cret -out rsa-pkcs8.pem
openssl ecparam -name prime256v1 -genkey -noout \
  | openssl ec -aes128 -passout pass:s3cret -out ec-legacy.pem
openssl genpkey -algorithm ed25519 -out ed25519.pem

OneCrypto is a preview, so dotnet add package needs --version or --prerelease. Put this in Program.cs and run dotnet run:

C#
using System.Text;
using RecursiveAI.OneCrypto;
using RecursiveAI.OneCrypto.Keys;
 
foreach (string path in new[] { "rsa-pkcs8.pem", "ec-legacy.pem", "ed25519.pem" })
{
    // The callback is asked for a password only if the file is encrypted
    using LoadedItems file = Crypto.Load(path, _ => "s3cret");
    LoadedPrivateKey key = file.PrivateKeys[0];
    Console.WriteLine($"{path,-14} {file.Format}  {key.Algorithm,-13} " +
        $"{key.PublicKey?.KeySizeBits,4} bits  can sign: {key.Signing is not null}");
}
 
// Sign with the Ed25519 key and save the signature for OpenSSL
using LoadedItems ed = Crypto.Load("ed25519.pem");
ISigningKey signer = ed.PrivateKeys[0].Signing!;
byte[] data = Encoding.UTF8.GetBytes("release-1.4.2.tar.gz sha256 9f2c...");
File.WriteAllBytes("data.txt", data);
File.WriteAllBytes("data.sig", signer.Sign(data, signer.DefaultSpec));
Console.WriteLine($"Signed with {signer.DefaultSpec}");
Text
rsa-pkcs8.pem  PEM  Rsa           3072 bits  can sign: True
ec-legacy.pem  PEM  EllipticCurve  256 bits  can sign: True
ed25519.pem    PEM  Ed25519        256 bits  can sign: True
Signed with Ed25519

Format names the container that was found. Every private key carries Signing, Decryption or Agreement as its algorithm allows (RSA signs and decrypts, EC signs with ECDSA and agrees with ECDH, X25519 only agrees), plus its PublicKey. Info holds the PKCS#8 form when you need it: Info.ToRsa() and Info.ToECDsa() give .NET’s own key objects, Info.ToEd25519() OneCrypto’s.

Check the signature with OpenSSL

Terminal
openssl pkeyutl -verify -inkey ed25519.pem -rawin -in data.txt -sigfile data.sig
Text
Signature Verified Successfully

Wrong passwords and bad files

A file that is not a key raises CryptoLoadException. Its Error says why (PasswordRequired, WrongPasswordOrCorrupt, Unrecognized, Malformed…) and Considered lists the formats looked at; Crypto.TryLoad returns false with the same exception instead. The password callback gets a short description of the encrypted item, and a password that opened one item is tried on the next first, so one passphrase for a whole bundle is asked once. OpenPGP armor (-----BEGIN PGP …) is not loaded: OpenPGP comes in a later package.

Limits

  • Not audited. This library has not been independently audited. It is a preview: 0.1.0-preview.1, and the API can change.
  • What isn’t read. OpenPGP, age and minisign files; [email protected] key files; RC4-encrypted PKCS#12. DSA keys load, but only as Info, with nothing to sign.
  • Writing. PrivateKeyInfo.Encrypt(password) writes encrypted PKCS#8 (PBES2); the legacy DEK-Info PEM encryption is read only.

The format decision table and every error: Crypto.Load. PKCS#8 and PKCS#12 details: PKCS.