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; public class Sign { public static void main(String[] args) throws Exception { // "BEGIN PRIVATE KEY" is PKCS#8; the JDK has no reader for "BEGIN EC PRIVATE KEY" String pem = Files.readString(Path.of("key.pem")); byte[] pkcs8 = Base64.getMimeDecoder().decode(pem.replaceAll("-----[^-]+-----", "")); PrivateKey privateKey = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(pkcs8)); Signature signer = Signature.getInstance("SHA256withECDSA"); signer.initSign(privateKey); // nonce drawn from the default SecureRandom signer.update(Files.readAllBytes(Path.of("message.txt"))); byte[] der = signer.sign(); // ASN.1 DER Files.write(Path.of("sig.der"), der); System.out.println("DER signature: " + der.length + " bytes"); // at most 72 on P-256 }} Signature p1363 = Signature.getInstance("SHA256withECDSAinP1363Format");p1363.initSign(privateKey);p1363.update(Files.readAllBytes(Path.of("message.txt")));byte[] raw = p1363.sign(); // r‖s, always 64 bytes on P-256 Files.write(Path.of("sig.raw"), raw);System.out.println("raw signature: " + raw.length + " bytes"); How it works
PKCS8EncodedKeySpecreads the base64 body of aBEGIN PRIVATE KEYblock; the curve comes from the key, so the same code signs with P-256, P-384 or P-521 keys.initSign(privateKey)seeds the per-signature nonce from the defaultSecureRandom— there is nothing to configure.updatetakes the message itself.SHA256withECDSAcomputes the SHA-256 digest internally; for a digest computed elsewhere useNONEwithECDSA, which signs the bytes it is given as the digest.sign()returns DER forSHA256withECDSAand fixed-lengthr‖sforSHA256withECDSAinP1363Format. The run above checks the DER output withopenssl dgst -verifyand the raw output with the P1363 verifier.
Gotchas
- Every call produces different bytes: the JDK draws a random nonce and does not implement RFC 6979 deterministic signing. Tests must verify signatures, never compare them with a stored value.
- Hashing the message yourself and passing the digest to
SHA256withECDSAhashes twice. The result is a valid signature over the digest — which no verifier given the original message will accept. - A
BEGIN EC PRIVATE KEY(SEC 1) file fails withInvalidKeySpecExceptioncaused byalgid parse error, not a sequence. Convert it once withopenssl pkcs8 -topk8 -nocrypt -in ec.pem -out key.pem. - The JDK does not normalize
s: about half of its signatures are high-S. Generic verifiers accept both forms, but Bitcoin-style verifiers that require low-S reject them — replace s with n − s when s > n/2 before handing the signature over.