Skip to content
ecdsa.com
Command line

OpenSSH (ssh-keygen)

OpenSSH 10, the SSH suite whose ssh-keygen also converts keys and signs files and git commits: Ed25519 by default, ML-KEM hybrid key exchange first, DSA gone — and ECDSA signatures in SSH's own mpint encoding.
License
SSH-OpenSSH
Checked version
10.3p1 ·
Source
github.com/openssh/openssh-portable
Documentation
www.openssh.com/manual.html

Compatibility at a glance

ECDSAEdDSAPost-quantum

ECDSA curves
P-256P-384P-521
EdDSA
Ed25519
Public key formats
spki-pemopensshpkcs1
Private key formats
pkcs8-pemencrypted-pkcs8sec1-pempkcs1openssh

Besides ssh and sshd, OpenSSH ships ssh-keygen, which generates and converts keys and, with -Y sign, produces SSHSIG signatures for files and git commits. The facts here were checked on the Alpine 3.24.2 packages, OpenSSH 10.3p1, linked against the image's OpenSSL 3.5.8. Without -t, ssh-keygen writes an Ed25519 key. ECDSA keys exist only for P-256, P-384 and P-521 (-b 224 is refused), there is no Ed448, and DSA is gone: ssh-keygen -t dsa answers "unknown key type dsa". Inspect any key with the SSH key inspector.

With no configuration file, the client offers the hybrid mlkem768x25519-sha256 key exchange first, then sntrup761x25519-sha512 — post-quantum protection for session keys. ssh -Q sig lists no post-quantum signature algorithm, so host and user authentication remain classical. RSA with SHA-1 (ssh-rsa) is still implemented but missing from the default host-key and public-key lists, so with default settings the client never uses it; a peer that offers nothing newer than ssh-rsa needs it re-enabled explicitly, for example with PubkeyAcceptedAlgorithms +ssh-rsa for user keys. Check what a server negotiates with the SSH server audit.

An ECDSA signature in SSH is neither DER nor raw r‖s: the blob is the string ecdsa-sha2-nistp256 followed by a string holding r and s as SSH mpints (RFC 5656). Converting signatures to or from X.509, JWS or WebAuthn therefore means re-encoding the two integers. Nonces are random — two signatures of one file differed — s is not normalized, and a signature with s replaced by n − s verified.

SSHSIG signatures (ssh-keygen -Y sign) hash the message with SHA-512 by default; -O hashalg=sha256 is the only alternative, and SHA-1 and SHA-384 are refused. The namespace given with -n is part of what is signed, and verifying a file signature as email fails with "namespace does not match". Failures are distinguishable on stderr — "invalid format" for a damaged blob, "incorrect signature" for a valid signature over other data — while stdout says "Could not verify signature." in both cases. Check a signature with the SSH signature verifier.

ssh-keygen -e -m PKCS8 exports a public key as SPKI PEM and -i -m PKCS8 imports it back; -m PEM also gives SPKI for ECDSA but PKCS#1 for RSA, and -m RFC4716 gives the "SSH2 PUBLIC KEY" format. Ed25519 public keys cannot be exported with -e -m PKCS8 ("unsupported key type ED25519"), although -p -m PKCS8 rewrites an Ed25519 private key as PKCS#8. Private keys are stored as OPENSSH PRIVATE KEY by default; -p -m PEM rewrites ECDSA keys as SEC1 and RSA keys as PKCS#1, and -m PKCS8 with a passphrase writes encrypted PKCS#8.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/openssh/run.sh ran OpenSSH (ssh-keygen) 10.3p1 in the container image alpine:3.24.2 on October 20, 2026; the page must match what it printed, and each fact lists those lines.

  • Default key type

    ed25519

    ssh-keygen without -t wrote an ssh-ed25519 key.

    Recorded by the check
    fact default-key-type = ed25519
    evidence default-key-type: ssh-keygen without -t wrote a ssh-ed25519 key
  • ECDSA curves

    P-256P-384P-521

    -b 256, 384 and 521; -b 224 is refused with "valid lengths are 256, 384 or 521 bits".

    Recorded by the check
    fact ecdsa-curves = P-256, P-384, P-521
    evidence ecdsa-curves: ssh -Q key: ssh-ed25519 ssh-ed25519-cert-v01@openssh.com sk-ssh-ed25519@openssh.com sk-ssh-ed25519-cert-v01@openssh.com ecdsa-sha2-nistp256 ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp384 ecdsa-sha2-nistp384-cert-v01@openssh.com ecdsa-sha2-nistp521 ecdsa-sha2-nistp521-cert-v01@openssh.com sk-ecdsa-sha2-nistp256@openssh.com sk-ecdsa-sha2-nistp256-cert-v01@openssh.com ssh-rsa ssh-rsa-cert-v01@openssh.com
  • EdDSA

    Ed25519

    ssh-ed25519 and its FIDO variant are listed by ssh -Q key; there is no Ed448 key type.

    Recorded by the check
    fact eddsa = Ed25519
  • DSA keys

    removed

    ssh-keygen -t dsa fails with "unknown key type dsa", and ssh-dss is not in ssh -Q key.

    Recorded by the check
    fact dsa-keys = removed
  • Default key exchange

    mlkem768x25519-sha256

    First entry of kexalgorithms in ssh -F none -G.

    Recorded by the check
    fact default-kex = mlkem768x25519-sha256
    evidence default-kex: ssh -F none -G: kexalgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256
  • Post-quantum algorithms

    ML-KEMsntrup761

    mlkem768x25519-sha256 and sntrup761x25519-sha512 hybrid key exchanges; no post-quantum signature algorithm in ssh -Q sig.

    Recorded by the check
    fact pqc = ML-KEM, sntrup761
    evidence pqc: ssh -Q kex: diffie-hellman-group1-sha1 diffie-hellman-group14-sha1 diffie-hellman-group14-sha256 diffie-hellman-group16-sha512 diffie-hellman-group18-sha512 diffie-hellman-group-exchange-sha1 diffie-hellman-group-exchange-sha256 ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 curve25519-sha256 curve25519-sha256@libssh.org sntrup761x25519-sha512 sntrup761x25519-sha512@openssh.com mlkem768x25519-sha256
    evidence pqc: ssh -Q sig: ssh-ed25519 sk-ssh-ed25519@openssh.com ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 sk-ecdsa-sha2-nistp256@openssh.com webauthn-sk-ecdsa-sha2-nistp256@openssh.com ssh-rsa rsa-sha2-256 rsa-sha2-512
  • ssh-rsa signatures with SHA-1

    disabled-by-default

    ssh-rsa is in ssh -Q sig but not in the default pubkeyacceptedalgorithms or hostkeyalgorithms.

    Recorded by the check
    fact ssh-rsa-sha1 = disabled-by-default
    evidence ssh-rsa-sha1: ssh-rsa in ssh -Q sig: yes; in the default pubkeyacceptedalgorithms or hostkeyalgorithms: no
    evidence ssh-rsa-sha1: with -o PubkeyAcceptedAlgorithms=+ssh-rsa it is in the list: yes
  • ECDSA signature encoding

    ssh-mpint

    The SSHSIG signature blob holds the type string and r and s as minimal SSH mpints.

    Recorded by the check
    fact ecdsa-signature-encoding = ssh-mpint
    evidence ecdsa-signature-encoding: ssh-keygen -Y sign: hash=sha512 type=ecdsa-sha2-nistp256 mpints=yes
  • ECDSA nonce

    random

    Two signatures of one file were different.

    Recorded by the check
    fact ecdsa-nonce = random
    evidence ecdsa-nonce: two signatures of one file are different
  • Low-S when signing

    either

    s is not normalized: high-S values appeared among 32 signatures.

    Recorded by the check
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: signatures with s > n/2 among 32: yes
  • High-S when verifying

    accepts-high-s

    A signature with s replaced by n − s verified as "Good".

    Recorded by the check
    fact ecdsa-low-s-verify = accepts-high-s
    evidence ecdsa-low-s-verify: s replaced by n - s: ok
  • Verification output on failure

    distinguishable

    stderr says "invalid format" for raw r‖s and for 3 bytes, "incorrect signature" for a wrong message; the exit status is 255 for both.

    Recorded by the check
    fact cli-verify-output = distinguishable
  • SSHSIG default hash

    sha512

    The SSHSIG header of a default signature names sha512.

    Recorded by the check
    fact sshsig-default-hash = sha512
    evidence sshsig-default-hash: hash=sha512 type=ecdsa-sha2-nistp256 mpints=yes
  • SSHSIG hash algorithms

    sha256sha512

    -O hashalg= accepted sha256 and sha512 and refused sha1 and sha384.

    Recorded by the check
    fact sshsig-hash-algorithms = sha256, sha512
    evidence sshsig-hash-algorithms: ssh-keygen -Y sign -O hashalg= accepted: sha256, sha512
  • SSHSIG namespace

    enforced

    A signature made with -n file failed with -n email: "namespace does not match".

    Recorded by the check
    fact sshsig-namespace = enforced
  • Public key formats

    spki-pemopensshpkcs1

    SPKI PEM (-e -m PKCS8, imported back with -i), the OpenSSH line, and PKCS#1 for RSA (-e -m PEM).

    Recorded by the check
    fact public-key-formats = spki-pem, openssh, pkcs1
    evidence public-key-formats: P-256 -e -m PKCS8: -----BEGIN PUBLIC KEY-----, -i back to the same key: yes
    evidence public-key-formats: P-256 -e -m PEM: -----BEGIN PUBLIC KEY-----; -e -m RFC4716: ---- BEGIN SSH2 PUBLIC KEY ----
    evidence public-key-formats: RSA -e -m PEM: -----BEGIN RSA PUBLIC KEY-----, -i back to the same key: yes
  • Private key formats

    pkcs8-pemencrypted-pkcs8sec1-pempkcs1openssh

    PKCS#8, encrypted PKCS#8 and SEC1 for ECDSA and PKCS#1 for RSA, each read back by ssh-keygen -y, besides the default OpenSSH format.

    Recorded by the check
    fact private-key-formats = pkcs8-pem, encrypted-pkcs8, sec1-pem, pkcs1, openssh
    evidence private-key-formats: P-256 default: -----BEGIN OPENSSH PRIVATE KEY-----; -m PKCS8: -----BEGIN PRIVATE KEY----- (read back: yes); -m PKCS8 with a passphrase: -----BEGIN ENCRYPTED PRIVATE KEY----- (read back: yes); -m PEM: -----BEGIN EC PRIVATE KEY----- (read back: yes)
    evidence private-key-formats: RSA -m PEM: -----BEGIN RSA PRIVATE KEY----- (read back: yes); Ed25519 -m PKCS8: -----BEGIN PRIVATE KEY----- (read back: yes); Ed25519 -m PEM: refused

Usage

Generate an Ed25519 key, sign a file in the `file` namespace and verify it against an allowed_signers file.
# Without -t, ssh-keygen generates an Ed25519 key.
ssh-keygen -q -N "" -C alice@example.com -f id_ed25519
printf 'order #1042 approved\n' > order.txt

# -n names what the signature is for; a different namespace fails verification.
ssh-keygen -q -Y sign -f id_ed25519 -n file order.txt   # writes order.txt.sig

# Verifiers trust keys through allowed_signers: a principal, then the public key.
echo "alice@example.com $(cut -d' ' -f1,2 id_ed25519.pub)" > allowed_signers
ssh-keygen -Y verify -f allowed_signers -I alice@example.com -n file -s order.txt.sig < order.txt

The snippet harness runs every sample verbatim in the container image alpine:3.24.2, and the run fails unless the output matches.

Setup
apk add --no-cache -q openssh-keygen=10.3_p1-r1 >/dev/null
Command
sh sign.sh
Output must match (regular expression)
Good "file" signature for alice@example\.com with ED25519 key SHA256:

Libraries