Java EdDSA Example: Sign and Verify With Ed25519 and Ed448

Java EdDSA example with Ed25519 and Ed448. Generate keys, sign and verify messages, save and load keys, and compare EdDSA with ECDSA, RSA and ML-DSA.

The sender signs the message bytes with the Ed25519 private key and gets a 64-byte signature; the receiver verifies the message and the signature with the public key and gets true, or false if the message was changed

EdDSA (Edwards-Curve Digital Signature Algorithm) is a digital signature algorithm, and Java EdDSA support covers the Ed25519 and Ed448 curves through the standard KeyPairGenerator and Signature classes, with no extra library. A signature proves that a message comes from the holder of a private key and that nobody changed it afterwards.

We use EdDSA to sign license files, webhook payloads, software updates and tokens, wherever the receiver must check who sent the data. Compared with RSA, Ed25519 keys and signatures are small, and signing gives the same result every time, so there is no random number to get wrong.

The following example generates an Ed25519 key pair, signs a message and verifies the signature with the JDK only.

KeyPair keyPair = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
byte[] message = "order 42 paid".getBytes(StandardCharsets.UTF_8);

Signature signer = Signature.getInstance("Ed25519");
signer.initSign(keyPair.getPrivate());
signer.update(message);
byte[] signature = signer.sign();

Signature verifier = Signature.getInstance("Ed25519");
verifier.initVerify(keyPair.getPublic());
verifier.update(message);
boolean valid = verifier.verify(signature);   // true
int signatureBytes = signature.length;        // 64

Notice that the private key signs and the public key verifies. We cover how EdDSA works, how to store and load the keys, Ed448, a license file example, and how EdDSA compares with ECDSA, RSA and the new ML-DSA algorithm.

1. How EdDSA Signing and Verification Work

A digital signature uses a key pair. The sender keeps the private key secret and signs the bytes of a message with it. Everyone who has the matching public key can verify the signature, and the check fails if a single byte of the message or the signature changed.

The sender signs the message bytes with the Ed25519 private key and gets a 64-byte signature; the receiver verifies the message and the signature with the public key and gets true, or false if the message was changed
The private key creates the signature, and the public key only checks it, so the public key can be shared freely

EdDSA signs data, it does not encrypt it. The message travels in plain text next to its signature. When the content must stay secret as well, we encrypt it separately, for example with AES-256-GCM.

Java has three standard algorithm names for EdDSA. The specific names fix the curve, and the generic name EdDSA accepts keys of both curves.

Algorithm nameCurvePublic key (X.509)SignatureSecurity level
Ed25519Curve2551944 bytes64 bytesabout 128 bits
Ed448Curve44869 bytes114 bytesabout 224 bits
EdDSAeither, from the key or NamedParameterSpecas aboveas aboveas above

2. Generating an Ed25519 Key Pair

The KeyPairGenerator for Ed25519 needs no key size, because the curve fixes it. Calling initialize(256) as we would for EC keys throws an InvalidParameterException.

KeyPairGenerator generator = KeyPairGenerator.getInstance("Ed25519");
KeyPair pair = generator.generateKeyPair();

String algorithm = pair.getPublic().getAlgorithm();      // "EdDSA"
String publicFormat = pair.getPublic().getFormat();      // "X.509"
String privateFormat = pair.getPrivate().getFormat();    // "PKCS#8"
int publicSize = pair.getPublic().getEncoded().length;   // 44
int privateSize = pair.getPrivate().getEncoded().length; // 48
KeyPairGenerator sized = KeyPairGenerator.getInstance("Ed25519");
sized.initialize(256);                                   // InvalidParameterException: Unsupported size: 256

The algorithm of the generated keys is the family name EdDSA, not Ed25519. To find the curve of a key, we cast it to EdECKey and read getParams().getName(), as section 5 shows.

3. Signing and Verifying a Message

In an app, signing and verifying live in two different places, so we wrap them in two small methods. Both take the message as a String and convert it to UTF-8 bytes, because the signature covers bytes, and the two sides must use the same encoding.

static byte[] sign(PrivateKey key, String message) throws GeneralSecurityException {
    Signature signature = Signature.getInstance("Ed25519");
    signature.initSign(key);
    signature.update(message.getBytes(StandardCharsets.UTF_8));
    return signature.sign();
}
static boolean verify(PublicKey key, String message, byte[] sig) throws GeneralSecurityException {
    Signature signature = Signature.getInstance("Ed25519");
    signature.initVerify(key);
    signature.update(message.getBytes(StandardCharsets.UTF_8));
    return signature.verify(sig);
}
KeyPair keys = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
byte[] sig = sign(keys.getPrivate(), "refund 15.00 EUR");

boolean original = verify(keys.getPublic(), "refund 15.00 EUR", sig);    // true
boolean tampered = verify(keys.getPublic(), "refund 95.00 EUR", sig);    // false
boolean same = Arrays.equals(sig, sign(keys.getPrivate(), "refund 15.00 EUR"));   // true

A changed amount makes verify() return false, so we treat false as a rejected message and never as a warning. The last line shows that EdDSA is deterministic, which means that the same key and message always give the same signature. ECDSA mixes a random value into every signature, and a weak random value there can leak the private key.

4. Saving and Loading Ed25519 Keys

Keys are useful only if we can store them and load them again. The encoded public key uses the X.509 SubjectPublicKeyInfo format, and the private key uses PKCS#8, which are the same formats that OpenSSL writes in PEM files between the BEGIN and END lines. For text storage, we encode the bytes with Base64.

KeyPair original = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
String publicText = Base64.getEncoder().encodeToString(original.getPublic().getEncoded());
String privateText = Base64.getEncoder().encodeToString(original.getPrivate().getEncoded());

KeyFactory factory = KeyFactory.getInstance("Ed25519");
PublicKey publicKey = factory.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicText)));
PrivateKey privateKey = factory.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateText)));

boolean restored = publicKey.equals(original.getPublic());   // true
int publicTextLength = publicText.length();                  // 60

The public key can go into a config file or the app itself. The private key belongs in a key store, a secrets manager or an environment variable, never in source control. A Java KeyStore of type PKCS12 can hold an EdDSA private key together with its certificate.

5. Ed448 and the Generic EdDSA Name

Ed448 works the same way with a larger curve, longer keys and a 114-byte signature. We use it when a standard or a partner requires a higher security level, while Ed25519 is the common default.

KeyPair ed448 = KeyPairGenerator.getInstance("Ed448").generateKeyPair();
Signature ed448Signer = Signature.getInstance("Ed448");
ed448Signer.initSign(ed448.getPrivate());
ed448Signer.update("firmware 2.1".getBytes(StandardCharsets.UTF_8));
int ed448Bytes = ed448Signer.sign().length;     // 114

With the generic name EdDSA, we pick the curve through NamedParameterSpec. A Signature created with EdDSA accepts keys of either curve, while a Signature for Ed25519 rejects an Ed448 key.

KeyPairGenerator generic = KeyPairGenerator.getInstance("EdDSA");
generic.initialize(NamedParameterSpec.ED448);
KeyPair genericPair = generic.generateKeyPair();
String curve = ((EdECKey) genericPair.getPublic()).getParams().getName();   // "Ed448"

Signature strict = Signature.getInstance("Ed25519");
strict.initVerify(genericPair.getPublic());     // InvalidKeyException: Parameters must be Ed25519

6. Signing a License File

Say a desktop app sells yearly licenses. The license server signs a small text file with the customer name and expiry date, and the app ships with the public key only. At startup, the app verifies the file before it unlocks paid features, so a user who edits the expiry date gets a rejected license.

static String issueLicense(PrivateKey key, String content) throws GeneralSecurityException {
    String sig = Base64.getEncoder().encodeToString(sign(key, content));
    return content + "\nsignature=" + sig;
}
static boolean isLicenseValid(PublicKey key, String file) throws GeneralSecurityException {
    int split = file.lastIndexOf("\nsignature=");
    if (split < 0) {
        return false;
    }
    String content = file.substring(0, split);
    byte[] sig = Base64.getDecoder().decode(file.substring(split + 11));
    return verify(key, content, sig);
}
KeyPair vendor = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
String license = issueLicense(vendor.getPrivate(), "customer=Ana\nexpires=2027-10-10");

boolean genuine = isLicenseValid(vendor.getPublic(), license);                                       // true
boolean edited = isLicenseValid(vendor.getPublic(), license.replace("2027", "2099"));               // false
boolean unsigned = isLicenseValid(vendor.getPublic(), "customer=Ana\nexpires=2099-01-01");          // false

The file stays readable for support staff, and the 88-character Base64 signature is short enough for an email. The same pattern works for webhook payloads, where the receiver verifies the body before it processes the event.

7. EdDSA vs ECDSA vs RSA vs ML-DSA

The JDK offers several signature algorithms, and each fits a different situation. The key and signature sizes in the table are those of the default JDK 25 providers.

PropertyEd25519ECDSA P-256RSA 2048ML-DSA-65
Signature nameEd25519SHA256withECDSASHA256withRSAML-DSA
Public key (X.509)44 bytes91 bytes294 bytes1,974 bytes
Signature64 bytes70 to 72 bytes256 bytes3,309 bytes
Same signature every timeYesNoYesNo (hedged by default)
In the JDK sinceJava 15Java 7Java 5 or earlierJava 24
Safe against future quantum computersNoNoNoYes

Ed25519 is a good default for new systems that control both sides. ECDSA and RSA are still needed when a partner, a hardware token or an older certificate chain requires them. ML-DSA is a post-quantum signature that Java added in Java 24, and its large keys and signatures are the price of that protection.

8. EdDSA Support Across Java Releases

Java 15 added EdDSA to the SunEC provider with JEP 339, based on RFC 8032. The implementation is written in Java and runs in constant time with respect to the secret values, so the time a signature takes does not reveal the key. Our Java features list shows the other security changes per release.

Java versionChange
Java 15EdDSA with Ed25519 and Ed448 (JEP 339)
Java 24ML-DSA post-quantum signatures (JEP 497)
Java 25PEM encoding and decoding API in preview (JEP 470)

9. Java EdDSA FAQs

Ed25519 in Java raises a few practical questions about libraries, encryption, PEM files and JWTs.

9.1. Do We Need Bouncy Castle for Ed25519 in Java?

No. Since Java 15, the built-in SunEC provider supports Ed25519 and Ed448. Bouncy Castle is needed only on Java 14 and older, or for features the JDK does not have.

9.2. Can EdDSA Encrypt Data?

No. EdDSA only signs and verifies. For encryption, we use a cipher such as AES, and for key agreement with Curve25519 the JDK offers the separate XDH algorithm, also called X25519.

9.3. How Do We Load an Ed25519 Public Key From a PEM File?

We remove the BEGIN and END lines, decode the Base64 text in between, and pass the bytes to X509EncodedKeySpec, as in section 4. Java 25 also has a PEMDecoder class, but it is a preview API that needs –enable-preview.

9.4. Can We Sign a JWT With Ed25519?

Yes. The JOSE standards define the algorithm name EdDSA for JSON Web Tokens, and libraries such as JJWT sign and verify such tokens with the JDK keys.

10. Conclusion

Java signs and verifies with EdDSA through KeyPairGenerator, Signature and KeyFactory, using the names Ed25519, Ed448 or EdDSA. Keys need no size, the public key encodes to 44 bytes in X.509 format, and every Ed25519 signature is 64 bytes long.

Signing is deterministic, verification returns false for any changed byte, and the keys load back with X509EncodedKeySpec and PKCS8EncodedKeySpec. For new code that controls both sides, Ed25519 is a small and fast default, and ML-DSA is the option when quantum safety matters.

11. References

Happy Learning !!

Source Code on Github

Leave a Comment

  1. Getting an exception
    java.security.NoSuchAlgorithmException: Ed25519 KeyPairGenerator not available

    What java version is used and is there a specific maven dependency required?

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.