Skip to content
ecdsa.com

Ed25519 sign & verify · Java

How to sign and verify Ed25519 signatures in Java

Java signs and verifies Ed25519 signatures with the JDK alone — the SunEC provider has implemented EdDSA (RFC 8032) since Java 15. KeyFactory.getInstance("Ed25519") reads PKCS#8 and SPKI keys, and Signature.getInstance("Ed25519") signs the message itself, with no SHA-256 prefix in the algorithm name.

Tested with OpenJDK 25.0.4.1 (Eclipse Temurin 25.0.4.1+1, SunEC provider).

Sign.java — load a PKCS#8 key and sign message.txt
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.HexFormat;
public class Sign {
public static void main(String[] args) throws Exception {
String pem = Files.readString(Path.of("key.pem"));
byte[] pkcs8 = Base64.getMimeDecoder().decode(pem.replaceAll("-----[^-]+-----", ""));
PrivateKey privateKey = KeyFactory.getInstance("Ed25519").generatePrivate(new PKCS8EncodedKeySpec(pkcs8));
Signature signer = Signature.getInstance("Ed25519"); // no "SHA…with": the hash is part of Ed25519
signer.initSign(privateKey);
signer.update(Files.readAllBytes(Path.of("message.txt")));
byte[] signature = signer.sign();
Files.write(Path.of("sig.bin"), signature);
System.out.println("signature: " + HexFormat.of().formatHex(signature));
}
}
Verify.java — load an SPKI public key and verify
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class Verify {
static boolean verify(PublicKey key, byte[] message, byte[] signature) throws Exception {
Signature verifier = Signature.getInstance("Ed25519");
verifier.initVerify(key);
verifier.update(message);
return verifier.verify(signature); // false for a wrong signature, no exception
}
public static void main(String[] args) throws Exception {
String pem = Files.readString(Path.of("pub.pem"));
byte[] spki = Base64.getMimeDecoder().decode(pem.replaceAll("-----[^-]+-----", ""));
PublicKey publicKey = KeyFactory.getInstance("Ed25519").generatePublic(new X509EncodedKeySpec(spki));
byte[] message = Files.readAllBytes(Path.of("message.txt"));
byte[] signature = Files.readAllBytes(Path.of("sig.bin"));
System.out.println("valid: " + verify(publicKey, message, signature));
System.out.println("tampered message valid: " + verify(publicKey, "hello ed25520".getBytes(StandardCharsets.UTF_8), signature));
}
}

How it works

  1. PKCS8EncodedKeySpec takes the decoded body of BEGIN PRIVATE KEY; X509EncodedKeySpec takes the body of BEGIN PUBLIC KEY (the JDK calls SPKI the "X.509 encoding").
  2. Signature.getInstance("Ed25519") — or the family name "EdDSA" — hashes with SHA-512 internally, so update receives the plain message bytes.
  3. sign() returns the 64-byte signature; verify() returns false for a wrong signature instead of throwing.
  4. Both files run directly with java Sign.java (single-file source launch, no build step), and the output matches every other recipe of this task.

Gotchas

  • There is no hash-prefixed name: Signature.getInstance("SHA256withEd25519") throws NoSuchAlgorithmException: SHA256withEd25519 Signature not available.
  • The EC factory does not read Ed25519 keys: KeyFactory.getInstance("EC") throws InvalidKeySpecException: java.security.InvalidKeyException: Invalid EC private key. In JCA, Ed25519 is an EdEC key — use "Ed25519" or "EdDSA".
  • A raw 32-byte seed loads with new EdECPrivateKeySpec(NamedParameterSpec.ED25519, seed), but a raw public key needs an EdECPoint (a little-endian y plus the x-parity bit). Prepending the 12 bytes 302a300506032b6570032100 and using X509EncodedKeySpec is simpler and harder to get wrong.
  • Java 8 and 11 have no Ed25519 in the JDK; there, use Bouncy Castle's Ed25519Signer or Google Tink.

Related recipes