sshcompat

A supplement to ecc.nim, for importing and exporting OpenSSH compatible keys and signatures.

It follows the formats described in OpenSSH's PROTOCOL, PROTOCOL.key and PROTOCOL.sshsig with the following limitations:

  • Only the ecdsa-sha2 format on the nistp256 curve is supported.
  • Only unencrypted private keys are supported.
  • Only the sha512 algorithm (default in OpenSSH) is supported for hashing messages.

Copyright (c) 2026 Christian Zietz <czietz@gmx.net>

Licensed under the MIT license.

Procs

proc ecDsaHashAndSshSign(Q: ECPrivateKey; message: openArray[char];
                         namespace: string): string {....raises: [ECCError],
    tags: [], forbids: [].}

Hashes a message with SHA-512 and creates an SSH signature (sshsig format).

This is a convenience wrapper around ecDsaSshSign that hashes message with SHA-512 before signing. See ecDsaSshSign for full details on the output format.

Parameters:

  • Q - the EC private key to sign with
  • message - the raw message to hash and sign
  • namespace - the sshsig namespace string (e.g. "file", "git")

Returns the PEM-encoded SSH signature string.

proc ecDsaHashAndSshVerify(S: string; message: openArray[char];
                           P: var ECPublicKey; namespace: var string): bool {.
    ...raises: [ECCError], tags: [], forbids: [].}

Hashes a message with SHA-512 and verifies an SSH signature (sshsig format).

This is a convenience wrapper around ecDsaSshVerify that hashes message with SHA-512 before verification. See ecDsaSshVerify for full details on parameters and behaviour.

Parameters:

  • S - the PEM-encoded SSH signature to verify
  • message - the raw message to hash and verify against
  • P - output: the public key extracted from the signature
  • namespace - in/out: expected namespace (empty = accept any); set to the actual namespace found in the signature on return

Returns true if the signature is cryptographically valid, false otherwise.

proc ecDsaSshSign(Q: ECPrivateKey; messagehash: ShaDigest_512; namespace: string): string {.
    ...raises: [ECCError], tags: [], forbids: [].}

Creates an SSH signature (sshsig format) over a pre-computed SHA-512 message hash.

The signature follows the SSHSIG protocol and is returned as a PEM block delimited by -----BEGIN SSH SIGNATURE----- / -----END SSH SIGNATURE-----. The hash algorithm recorded in the signature is sha512; the signing algorithm is ecdsa-sha2-nistp256.

Only the nistp256 (secp256r1) curve is supported; an ECCError is raised for any other curve.

Parameters:

  • Q - the EC private key to sign with
  • messagehash - the SHA-512 digest of the message to be signed
  • namespace - the sshsig namespace string (e.g. "file", "git")

Returns the PEM-encoded SSH signature string.

proc ecDsaSshVerify(S: string; messagehash: ShaDigest_512; P: var ECPublicKey;
                    namespace: var string): bool {....raises: [ECCError], tags: [],
    forbids: [].}

Verifies an SSH signature (sshsig format) against a pre-computed SHA-512 hash.

The signature S must be a PEM-encoded sshsig block. The public key and namespace embedded in the signature are extracted and written to P and namespace respectively, so the caller can inspect them after the call.

If namespace is non-empty on entry it is used as an expected value; false is then returned when the signature's namespace does not match.

Typically, only specific trusted public keys are allowed to make signatures. The caller must then verify the returned public key P against the list of allowed signers.

Parameters:

  • S - the PEM-encoded SSH signature to verify
  • messagehash - the SHA-512 digest of the original message
  • P - output: the public key extracted from the signature
  • namespace - in/out: expected namespace (empty = accept any); set to the actual namespace found in the signature on return

Returns true if the signature is cryptographically valid, false otherwise.

proc loadSshKeyPair(K: string): ECKeyPair {.
    ...raises: [ECCError, ECCError, ECCError], tags: [], forbids: [].}

Parses an OpenSSH private key PEM block and returns the EC key pair.

The input K must contain a PEM-encoded openssh-key-v1 private key, delimited by -----BEGIN OPENSSH PRIVATE KEY----- / -----END OPENSSH PRIVATE KEY-----. Only unencrypted keys (cipher none) and the ecdsa-sha2-nistp256 key type are supported. If the file contains multiple keys only the first is loaded.

The parsed public and private keys are cross-validated; an ECCError is raised if they do not correspond to the same key pair.

Raises ECCError if:

  • the PEM structure or base64 encoding is invalid
  • the key is encrypted
  • the key type is not ecdsa-sha2-nistp256
  • internal consistency checks fail

Parameters:

  • K - the PEM string containing the OpenSSH private key

Returns an ECKeyPair with public and private fields.

proc loadSshPublicKey(K: string): ECPublicKey {....raises: [ECCError, ECCError],
    tags: [], forbids: [].}

Parses an OpenSSH public key string and returns the corresponding EC public key.

The input K must be a single-line OpenSSH public key of the form: ecdsa-sha2-nistp256 <base64-encoded-key> [comment]

Raises ECCError if:

  • the string is malformed or cannot be base64-decoded
  • the key type is not ecdsa-sha2-nistp256
  • the encoded key data is invalid

Parameters:

  • K - the OpenSSH public key string to parse

Returns the decoded ECPublicKey.

proc sshFingerPrint(P: ECPublicKey): string {....raises: [ECCError], tags: [],
    forbids: [].}

Computes the OpenSSH SHA-256 fingerprint of an EC public key.

The fingerprint is calculated by hashing the SSH wire-format encoding of the public key with SHA-256, then base64-encoding the digest (without padding). The result has the form SHA256:<base64> and matches the output of ssh-keygen -l -E sha256.

Parameters:

  • P - the EC public key to fingerprint

Returns the fingerprint string, e.g. SHA256:abc123....

proc toSshKey(P: ECPublicKey; comment = ""): string {....raises: [ECCError],
    tags: [], forbids: [].}

Encodes an EC public key as an OpenSSH public key string.

The result is a single-line string of the form: ecdsa-sha2-nistp256 <base64-encoded-key> <comment> which can be written directly to an authorized_keys file or a .pub file.

Only the nistp256 (secp256r1) curve is supported; an ECCError is raised for any other curve.

Parameters:

  • P - the EC public key to encode
  • comment - optional comment appended to the key line (default: empty string)

Returns the OpenSSH public key string.

proc toSshKey(Q: ECPrivateKey; comment = ""): string {....raises: [ECCError],
    tags: [], forbids: [].}

Encodes an EC private key as an OpenSSH private key PEM block.

The result is a multi-line string delimited by -----BEGIN OPENSSH PRIVATE KEY----- and -----END OPENSSH PRIVATE KEY-----, using the openssh-key-v1 format. The key is stored unencrypted (cipher none); encryption is not supported.

Only the nistp256 (secp256r1) curve is supported; an ECCError is raised for any other curve.

Parameters:

  • Q - the EC private key to encode
  • comment - optional comment embedded in the key file (default: empty string)

Returns the PEM-encoded OpenSSH private key string.