Java のコードは java.security.Signature、KeyPairGenerator、KeyFactory を通じて署名し、実際の処理は JDK 自身のプロバイダーが行います。楕円曲線と EdDSA は SunEC が担い、さらに新しい ML-KEM(javax.crypto.KEM、JDK 24)と ML-DSA のプロバイダーもあります。Android、Kotlin、そしてあらゆる JVM フレームワークが同じ API の上に成り立っているため、そのデフォルトの挙動は Spring や Keycloak、無数の SDK に現れます。
エンコーディングはアルゴリズム名で決まります。SHA256withECDSA は ASN.1 DER の署名を生成し、検証でも DER を受け付けます。SHA256withECDSAinP1363Format は、サンプルのように、JWS と WebCrypto が必要とする 64 バイトの生の r‖s を使います。両者を取り違えたときの挙動は非対称です。生の署名を SHA256withECDSA に渡すと SignatureException がスローされますが、DER の署名を P1363 のバリアントに渡すと単に false が返ります。NONEwithECDSA は自分で計算したダイジェストに署名します(その署名は、メッセージに対して SHA256withECDSA で検証を通りました)。また、ハッシュの名前は曲線とは無関係なので、P-384 の鍵で SHA256withECDSA を使うことも正当ですが、SHA-384 を期待する相手側では検証に失敗します。
JDK 25 の SunEC が ECDSA 向けに提供する曲線は secp256r1、secp384r1、secp521r1 だけです。P-224、secp256k1、Brainpool 曲線はエラーになりました。JVM 上の Bitcoin や Ethereum のコードが Bouncy Castle や web3j を使うのはこのためです。nonce はランダムで、s は正規化されず、検証は low-S と high-S の両方を受け入れます。そのため、low-S を厳格に要求する相手に渡す署名では、s を n − s に置き換える必要があります。
鍵は X.509 SPKI DER(X509EncodedKeySpec)、PKCS#8 DER(PKCS8EncodedKeySpec)、または生の構成要素(ECPrivateKeySpec)として読み込みます。PEM のサポートは JDK 25 でプレビュー API として追加されました(java.security.PEMDecoder、JEP 470)。チェックでは --enable-preview を付けたときにだけ公開鍵をデコードできたため、本番のコードでは今もアーマー(ヘッダー行とフッター行)を取り除いてから base64 デコードしています。ポスト量子暗号への対応は実用レベルです。ML-KEM-512/768/1024 でカプセル化ができ、ML-DSA-44/65/87 で署名と検証ができました。SLH-DSA はこの JDK にはありません。
署名のエンコーディングの比較には DER ⇄ 生形式の変換ツールを、署名付き JAR の解析には JAR 署名インスペクターを使ってください。