---
title: "Generate an SSH key in C#"
url: https://recursiveai.org/onecrypto/cookbook/generate-ssh-key/
description: "Generate an SSH key pair in C#: an Ed25519 OpenSSH private key protected with a passphrase and the matching .pub line for authorized_keys, read back by ssh-keygen. Runnable code with OneCrypto 0.1.0-preview.1."
version: "0.1.0-preview.1"
---

# Generate an SSH key in C#

Make an Ed25519 key, then write it with `SshPrivateKey.Create` in the `openssh-key-v1` format that `ssh-keygen` writes, encrypted with a passphrase, and the public key as an `authorized_keys` line. OpenSSH reads both. No `ssh-keygen` process is needed.

```shell
dotnet new console -o SshKeyDemo && cd SshKeyDemo
dotnet add package RecursiveAI.OneCrypto --version 0.1.0-preview.1
```

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

```csharp
using RecursiveAI.OneCrypto.Pkcs;
using RecursiveAI.OneCrypto.Primitives;
using RecursiveAI.OneCrypto.Wire.Ssh;

using Ed25519PrivateKey ed = Ed25519PrivateKey.Generate();
using PrivateKeyInfo key = PrivateKeyInfo.FromEd25519(ed);

// aes256-ctr with 16 bcrypt rounds, the ssh-keygen defaults
SshPrivateKey sshKey = SshPrivateKey.Create(key, "deploy@build-01",
    "a long passphrase");
File.WriteAllText("id_ed25519", sshKey.ToPem());
if (!OperatingSystem.IsWindows())   // ssh refuses keys others can read
    File.SetUnixFileMode("id_ed25519", UnixFileMode.UserRead | UnixFileMode.UserWrite);

string pub = sshKey.PublicKey.ToAuthorizedKey("deploy@build-01");
File.WriteAllText("id_ed25519.pub", pub + "\n");

Console.WriteLine($"Private key: {sshKey.CipherName}, {sshKey.KdfName}, " +
    $"encrypted: {sshKey.IsEncrypted}");
Console.WriteLine($"Fingerprint: {sshKey.PublicKey.Fingerprint}");
```

```text
Private key: aes256-ctr, bcrypt, encrypted: True
Fingerprint: SHA256:gn7ABggzAYXIw2oNXML8oHgQDVnDT4tR2Fbg4Xlboww
```

Put the `.pub` line in a server’s `~/.ssh/authorized_keys`, and the private key file where `ssh` looks for keys. `ToPem()` writes the `-----BEGIN OPENSSH PRIVATE KEY-----` form; pass an empty passphrase for an unencrypted key. RSA and ECDSA keys work the same way: build the `PrivateKeyInfo` from .NET’s `RSA` or `ECDsa` with `PrivateKeyInfo.FromAsymmetricAlgorithm`.

## Check it with ssh-keygen

`ssh-keygen -y` decrypts the private key and prints its public key, which must equal the `.pub` file; `-l` prints the fingerprint:

```shell
ssh-keygen -y -P "a long passphrase" -f id_ed25519 | cut -d' ' -f1-2 > from-private.pub
cut -d' ' -f1-2 id_ed25519.pub | cmp - from-private.pub \
  && echo "ssh-keygen reads the key"
ssh-keygen -l -f id_ed25519.pub | cut -d' ' -f2
```

```text
ssh-keygen reads the key
SHA256:gn7ABggzAYXIw2oNXML8oHgQDVnDT4tR2Fbg4Xlboww
```

## Reading keys

`SshPrivateKey.Parse` (or `FromPem`) reads a key file and `Open(passphrase)` decrypts it; `SshPublicKey.ParseAuthorizedKey` reads a `.pub` or `authorized_keys` line. `Crypto.Load` reads all of these without being told the format and returns keys ready to sign: see [Load a PEM or PKCS#8 private key](https://recursiveai.org/onecrypto/cookbook/load-pem-private-key/).

## Limits

- **Not audited.** This library has not been independently audited. It is a preview: 0.1.0-preview.1, and the API can change.
- **Key files only.** OneCrypto reads and writes keys and certificates; it is not an SSH client, and it doesn’t talk to `ssh-agent` yet. Signing files with SSH keys (SSHSIG) comes in a later package.
- **Ciphers.** `chacha20-poly1305@openssh.com` key files are not read. `bcrypt-pbkdf`, which protects the passphrase, looks up tables by data; the project says why it accepts that in [Password-based key derivation](https://recursiveai.org/onecrypto/docs/kdf/).

How OpenSSH keys and certificates are read and written: [PKCS](https://recursiveai.org/onecrypto/docs/pkcs/).
