Recursive AI

Generate an SSH key in C#

All onecrypto recipes

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.

Terminal
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:

C#
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:

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

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. [email protected] 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.

How OpenSSH keys and certificates are read and written: PKCS.