---
title: "Load a PEM or PKCS#8 private key in C#"
url: https://recursiveai.org/onecrypto/cookbook/load-pem-private-key/
description: "Load a PEM private key in C#: encrypted PKCS#8, traditional RSA and EC PEM with DEK-Info, and Ed25519 keys made by OpenSSL, in one call that detects the format, then sign with the key. Runnable code with OneCrypto 0.1.0-preview.1."
version: "0.1.0-preview.1"
---

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

`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.

```shell
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`:

```csharp
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

```shell
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; `chacha20-poly1305@openssh.com` 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](https://recursiveai.org/onecrypto/docs/load/). PKCS#8 and PKCS#12 details: [PKCS](https://recursiveai.org/onecrypto/docs/pkcs/).
