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"); }} // r‖s from a JWS or WebCrypto: exactly 64 bytes on P-256, no DER framingbyte[] 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
X509EncodedKeySpecis the JDK's name for SPKI, the body of aBEGIN PUBLIC KEYblock. The MIME decoder skips line breaks, so only the armor lines need removing.KeyFactory.getInstance("EC")takes the curve from the key itself — nothing in the code says P-256.SHA256withECDSAfixes both the hash and the signature encoding (DER). Pass the original message toupdate; the JDK computes the SHA-256 digest itself.SHA256withECDSAinP1363Format(JDK 9 and later) reads the fixed-lengthr‖sform directly, so a JWS or WebCrypto signature needs no conversion.
Gotchas
- A raw 64-byte signature passed to
SHA256withECDSAthrowsSignatureException: Invalid encoding for signatureinstead of returningfalse. The opposite mistake is silent: a DER signature given to the P1363 algorithm simply verifies asfalse. verifyreturnsfalsefor a wrong signature but throws on malformed input, so treatSignatureExceptionas "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 CERTIFICATEfile is not an SPKI and fails inX509EncodedKeySpec. Load it withCertificateFactoryand callgetPublicKey()instead.