Skip to content
ecdsa.com

Verify a signature · Java

How to verify an ECDSA signature in Java

To verify an ECDSA signature in Java you need nothing but the JDK: KeyFactory loads the public key and java.security.Signature checks the signature. The one decision that matters is the algorithm name — SHA256withECDSA reads ASN.1 DER signatures, while SHA256withECDSAinP1363Format reads the raw r‖s form that JWS and WebCrypto produce.

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

Verify.java — verify a DER signature (what OpenSSL and the JDK itself produce)
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 {
public static void main(String[] args) throws Exception {
// "BEGIN PUBLIC KEY" is SPKI: drop the armor lines, decode the base64 body
String pem = Files.readString(Path.of("pub.pem"));
byte[] spki = Base64.getMimeDecoder().decode(pem.replaceAll("-----[^-]+-----", ""));
PublicKey publicKey = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(spki));
byte[] message = Files.readAllBytes(Path.of("message.txt"));
byte[] signature = Files.readAllBytes(Path.of("sig.der")); // ASN.1 DER, at most 72 bytes on P-256
Signature verifier = Signature.getInstance("SHA256withECDSA");
verifier.initVerify(publicKey);
verifier.update(message); // the message itself: the JDK hashes it
System.out.println(verifier.verify(signature) ? "valid" : "INVALID");
}
}
raw r‖s signatures: same key and message, P1363 algorithm name
// r‖s from a JWS or WebCrypto: exactly 64 bytes on P-256, no DER framing
byte[] raw = Files.readAllBytes(Path.of("sig.raw"));
Signature p1363 = Signature.getInstance("SHA256withECDSAinP1363Format");
p1363.initVerify(publicKey);
p1363.update(message);
System.out.println(p1363.verify(raw) ? "valid" : "INVALID");

How it works

  1. X509EncodedKeySpec is the JDK's name for SPKI, the body of a BEGIN PUBLIC KEY block. The MIME decoder skips line breaks, so only the armor lines need removing.
  2. KeyFactory.getInstance("EC") takes the curve from the key itself — nothing in the code says P-256.
  3. SHA256withECDSA fixes both the hash and the signature encoding (DER). Pass the original message to update; the JDK computes the SHA-256 digest itself.
  4. SHA256withECDSAinP1363Format (JDK 9 and later) reads the fixed-length r‖s form directly, so a JWS or WebCrypto signature needs no conversion.

Gotchas

  • A raw 64-byte signature passed to SHA256withECDSA throws SignatureException: Invalid encoding for signature instead of returning false. The opposite mistake is silent: a DER signature given to the P1363 algorithm simply verifies as false.
  • verify returns false for a wrong signature but throws on malformed input, so treat SignatureException as "invalid" in request handlers rather than letting it surface as a server error.
  • secp256k1 was removed from the JDK's SunEC provider in JDK 16 (Curve not supported: secp256k1). Verifying Bitcoin or Ethereum keys needs Bouncy Castle registered as a provider.
  • A BEGIN CERTIFICATE file is not an SPKI and fails in X509EncodedKeySpec. Load it with CertificateFactory and call getPublicKey() instead.

Related recipes