Сообщение об ошибке
java.security.SignatureException: Invalid encoding for signature
Подробности в `Caused by` различаются (например, «Invalid lenByte» или «Not the correct tag») в зависимости от того, на каком байте сломался разбор DER. Обратная ошибка проходит молча: подпись DER, переданная в `SHA256withECDSAinP1363Format`, не вызывает исключения, а просто даёт при проверке `false`.
Java (JDK) — Signature.verify with SHA256withECDSA (SunEC)
Что это означает
`SHA256withECDSA` ожидает подпись ECDSA в ASN.1 DER (`30 … 02 r 02 s`), а переданные вами байты — не DER. Почти всегда это другая стандартная кодировка: конкатенация r‖s фиксированной длины (IEEE P1363), которую используют JWS, WebCrypto и многие библиотеки не на Java; для P-256 это 64 байта. JDK умеет проверять и этот формат — под именем алгоритма `SHA256withECDSAinP1363Format`.
Почему это происходит
Подпись raw r‖s проверяется как DER
частоПодписи из JWT (ES256), из `crypto.subtle.sign`, из кода, который вручную склеивает r и s, или из инструментов для блокчейна — это 64/96/132 байта в формате raw. Стандартные имена алгоритмов ECDSA в Java рассчитаны на DER.
Байты подписи декодированы неправильно
иногдаСегмент JWS в base64url, декодированный базовым декодером (после неправильной замены символов), или hex, декодированный как base64, дают байты, не имеющие ничего общего с подписью.
Подпись RSA передана в код проверки ECDSA
иногдаПодпись RSA длиной 256 байт — тоже не DER. Прежде чем выбирать код проверки, посмотрите, каким алгоритмом подписан токен или документ.
Как исправить
- 1.
Проверяйте подписи в формате raw алгоритмом P1363
Такие алгоритмы есть в провайдере SunEC начиная с JDK 9: для SHA-1/224/256/384/512 с ECDSA. Преобразовывать ничего не нужно.
java // JWS (ES256) and WebCrypto subtle.sign() produce raw r||s; WebAuthn and X.509 use DER. Signature verifier = Signature.getInstance("SHA256withECDSAinP1363Format"); verifier.initVerify(publicKey); verifier.update(signedBytes); boolean ok = verifier.verify(rawSignature); // 64 bytes for P-256 - 2.
Или преобразуйте r‖s в DER
Если API требует именно DER (например, провайдер HSM), упакуйте r и s как минимальные DER INTEGER внутри SEQUENCE. Конвертер DER ⇄ raw проверит ваше преобразование.
java static byte[] p1363ToDer(byte[] raw) { int n = raw.length / 2; byte[] r = derInteger(java.util.Arrays.copyOfRange(raw, 0, n)); byte[] s = derInteger(java.util.Arrays.copyOfRange(raw, n, raw.length)); var out = new java.io.ByteArrayOutputStream(); out.write(0x30); out.write(r.length + s.length); // < 128 for P-256 and P-384 out.writeBytes(r); out.writeBytes(s); return out.toByteArray(); } static byte[] derInteger(byte[] v) { int i = 0; while (i < v.length - 1 && v[i] == 0) i++; boolean pad = (v[i] & 0x80) != 0; byte[] out = new byte[2 + (pad ? 1 : 0) + v.length - i]; out[0] = 0x02; out[1] = (byte) (out.length - 2); System.arraycopy(v, i, out, pad ? 3 : 2, v.length - i); return out; }