Skip to content
ecdsa.com

Ed25519 sign & verify · OpenSSL

How to sign and verify Ed25519 signatures with OpenSSL

The OpenSSL command line signs and verifies Ed25519 signatures with openssl pkeyutl rather than openssl dgst: Ed25519 (RFC 8032) hashes the message inside the algorithm, so OpenSSL runs it as a one-shot operation over the raw file. With -rawin, the same commands work on OpenSSL 3.0 and every later version.

Tested with OpenSSL 3.5.7 (Debian 13) and OpenSSL 3.0.13 (Ubuntu 24.04 (pkeyutl -rawin; dgst -sign refused)).

generate an Ed25519 key pair (PKCS#8 private key, SPKI public key)
# a new key pair: PKCS#8 private key and SPKI public key
openssl genpkey -algorithm ed25519 -out ed25519.pem
openssl pkey -in ed25519.pem -pubout -out ed25519.pub.pem
sign message.txt and print the 64-byte signature as hex
# -rawin: Ed25519 signs the message itself, there is no separate digest step
openssl pkeyutl -sign -inkey key.pem -rawin -in message.txt -out sig.bin
od -An -v -tx1 sig.bin | tr -d ' \n'; echo # 64 bytes, the same on every run
verify the signature with the public key
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in message.txt -sigfile sig.bin
# "Signature Verified Successfully" (exit 0) or "Signature Verification Failure" (exit 1)

How it works

  1. genpkey -algorithm ed25519 writes a PKCS#8 BEGIN PRIVATE KEY; pkey -pubout derives the SPKI BEGIN PUBLIC KEY from it.
  2. pkeyutl -sign -rawin signs the file contents as they are. -rawin is required before OpenSSL 3.5 and implied from 3.5 on, so keeping it makes the command portable.
  3. -pubin tells pkeyutl -verify that -inkey is a public key, and -sigfile names the signature. The exit code is 0 for a valid signature and 1 otherwise.
  4. The run checks the generated key, prints the same signature as every other recipe of this task, and shows a one-character change in the message failing verification.

Gotchas

  • Ed25519 refuses a digest: openssl dgst -sha256 -sign key.pem fails with Explicit digest not allowed with EdDSA operations. Without a digest, dgst -sign works on OpenSSL 3.5 but fails on 3.0 (Key type not supported for this operation); pkeyutl works on both.
  • Piping the message in fails: printf … | openssl pkeyutl -sign -rawin … stops with unable to determine file size for oneshot operation. Ed25519 is one-shot, so OpenSSL needs a regular file via -in and reads all of it into memory.
  • Without -pubin, pkeyutl reads -inkey as a private key, and a public key file fails with Could not find private key from pub.pem.
  • The signature file holds 64 raw bytes — not DER, not base64. Encode it (base64, od) for systems that expect text, and decode it back before -sigfile.

Related recipes