Skip to content
ecdsa.com

Generate a key pair · Java

How to generate an ECDSA key pair in Java

To generate an ECDSA key pair in Java, use KeyPairGenerator and an ECGenParameterSpec naming the curve — no external library. The encoded keys are already in the standard containers (PKCS#8 for the private key, SPKI for the public key), so writing PEM files takes only a Base64 encoder; keytool does the same job from the command line when the key should live in a keystore.

Tested with OpenJDK 25.0.4.1 (Eclipse Temurin 25.0.4.1+1, SunEC provider, keytool) and OpenSSL 3.0.13 (openssl pkey / pkcs12 cross-check).

GenerateKey.java — P-256 pair written as PKCS#8 and SPKI PEM
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermissions;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.ECGenParameterSpec;
import java.util.Base64;
public class GenerateKey {
public static void main(String[] args) throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
generator.initialize(new ECGenParameterSpec("secp256r1")); // P-256, aka prime256v1
KeyPair pair = generator.generateKeyPair();
// getEncoded() is PKCS#8 for the private key and SPKI for the public key
Path keyFile = Files.createFile(Path.of("key.pem"),
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-------")));
Files.writeString(keyFile, pem("PRIVATE KEY", pair.getPrivate().getEncoded()));
Files.writeString(Path.of("pub.pem"), pem("PUBLIC KEY", pair.getPublic().getEncoded()));
System.out.print(pem("PUBLIC KEY", pair.getPublic().getEncoded()));
}
static String pem(String label, byte[] der) {
Base64.Encoder base64 = Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.US_ASCII));
return "-----BEGIN " + label + "-----\n" + base64.encodeToString(der) + "\n-----END " + label + "-----\n";
}
}
password-protected private key (BEGIN ENCRYPTED PRIVATE KEY)
// "BEGIN ENCRYPTED PRIVATE KEY": PKCS#8 under PBES2 (PBKDF2-HMAC-SHA256 + AES-256-CBC)
char[] password = System.getenv("KEY_PASSWORD").toCharArray();
byte[] salt = new byte[16];
new SecureRandom().nextBytes(salt);
Cipher cipher = Cipher.getInstance("PBEWithHmacSHA256AndAES_256");
cipher.init(Cipher.ENCRYPT_MODE,
SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_256").generateSecret(new PBEKeySpec(password)),
new PBEParameterSpec(salt, 600_000));
byte[] encrypted = cipher.doFinal(pair.getPrivate().getEncoded());
// EncryptedPrivateKeyInfo only accepts the parameters under their standard name, PBES2
AlgorithmParameters pbes2 = AlgorithmParameters.getInstance("PBES2");
pbes2.init(cipher.getParameters().getEncoded());
Files.writeString(Path.of("key-encrypted.pem"),
pem("ENCRYPTED PRIVATE KEY", new EncryptedPrivateKeyInfo(pbes2, encrypted).getEncoded()));
the keytool route: a key pair in a PKCS#12 keystore
# a P-256 key pair plus a self-signed certificate in a password-protected PKCS#12 keystore
keytool -genkeypair -alias signing -keyalg EC -groupname secp256r1 \
-sigalg SHA256withECDSA -dname "CN=signing key" -validity 365 \
-keystore keys.p12 -storetype PKCS12 -storepass:env STORE_PASS
# the same private key as PKCS#8 PEM, for tools outside the JVM
openssl pkcs12 -in keys.p12 -passin env:STORE_PASS -nocerts -nodes | openssl pkey -out key-from-p12.pem

How it works

  1. KeyPairGenerator.getInstance("EC") plus ECGenParameterSpec("secp256r1") selects P-256; the JDK's own provider (SunEC) also supports secp384r1 and secp521r1.
  2. getPrivate().getEncoded() is PKCS#8 DER and getPublic().getEncoded() is SPKI DER. PEM is that DER in base64, wrapped at 64 columns between BEGIN/END lines — which is all the pem helper does.
  3. Files.createFile with rw------- creates the private key file with owner-only permissions in one step, and refuses to overwrite an existing key.
  4. For an encrypted file, PBEWithHmacSHA256AndAES_256 encrypts the PKCS#8 bytes with a PBKDF2-derived key, and EncryptedPrivateKeyInfo wraps them in the standard structure that OpenSSL opens with -passin.
  5. keytool -genkeypair stores the key next to a self-signed certificate, because a keystore entry for a private key always carries a certificate chain; openssl pkcs12 -nocerts -nodes extracts the key as PEM.

Gotchas

  • The JDK knows P-256 only as secp256r1, NIST P-256, X9.62 prime256v1 or its OID: new ECGenParameterSpec("P-256") and "prime256v1" fail with Unknown curve name.
  • secp256k1 is gone from SunEC since JDK 16 (Curve not supported: secp256k1). Bitcoin and Ethereum keys need Bouncy Castle as the provider.
  • Passing cipher.getParameters() straight to EncryptedPrivateKeyInfo throws NoSuchAlgorithmException: unrecognized algorithm name: PBEWithHmacSHA256AndAES_256. Re-reading the parameters as PBES2, as in the second snippet, is the fix.
  • JDK 25 ships java.security.PEMEncoder and PEMDecoder only as a preview API (JEP 470, --enable-preview). Until it is final, a Base64 helper like the one above is the portable way to write PEM.
  • PosixFilePermissions works on Linux and macOS only; on Windows Files.createFile with that attribute throws UnsupportedOperationException, so protect the key file with ACLs there.

Related recipes