Перейти к содержимому
ecdsa.com
Java · Kotlin

Tink (Java)

Tink для Java от Google — устойчивый к неправильному использованию криптографический API, построенный на наборах ключей и шаблонах: подписи по умолчанию содержат 5-байтовый префикс Tink, который не понимает ни одна другая библиотека, а имена шаблонов скрывают, какую кодировку вы получите.
Лицензия
Apache-2.0
Проверенная версия
1.24.0 ·
Исходный код
github.com/tink-crypto/tink-java
Документация
developers.google.com/tink

Совместимость вкратце

ECDSAEdDSA

Кривые ECDSA
P-256P-384P-521
EdDSA
Ed25519

Tink заменяет отдельные ключи наборами ключей (keysets), а алгоритмы — именованными шаблонами, например ECDSA_P256 или ED25519; набор ключей — это коллекция ключей с идентификаторами, один из которых помечен как основной. Примитивы (PublicKeySign, PublicKeyVerify) прозрачно выполняют ротацию ключей: в этом смысл такой архитектуры и источник её неожиданностей при взаимодействии с другими библиотеками. Наборы ключей хранятся в формате Tink JSON или в двоичном protobuf, а не в PEM.

Стандартный шаблон ECDSA_P256 добавляет в начало подписи префикс вывода (output prefix): 0x01 и 4-байтовый идентификатор ключа, за которыми следует подпись DER. JDK отклонил такую подпись с ошибкой Invalid encoding for signature, и так же поступит любой код проверки, кроме Tink. Шаблоны, имена которых заканчиваются на _WITHOUT_PREFIX или _RAW, не добавляют префикс, но имена скрывают и кодировку: ECDSA_P256_RAW выдал 64 байта IEEE P1363 r‖s, а не DER, и такая подпись прошла проверку с SHA256withECDSAinP1363Format из JDK, но не прошла с SHA256withECDSA. Выбирайте шаблон по точной кодировке, которую ожидает другая сторона, как пример делает с ECDSA_P256_IEEE_P1363_WITHOUT_PREFIX, и проверяйте байты в конвертере DER ⇄ raw.

Сторонний открытый ключ импортируется через SignaturePemKeysetReader, и полученный ключ проверял только подписи DER: та же подпись raw, которую принимал ключ, созданный в Tink по шаблону P1363, не проходила проверку с ключом, импортированным из PEM, пока её не перекодировали в DER. При подписи используются случайные nonce, s не нормализуется, а проверка принимает high-S; некорректные входные данные вызывают GeneralSecurityException: invalid signature.

Tink для Java 1.24.0 предлагает шаблоны ECDSA на P-256, P-384 и P-521, Ed25519 и RSA (PKCS#1 v1.5 и PSS); шаблонов ML-DSA и SLH-DSA в этом релизе не зарегистрировано (Name ML_DSA_65 does not exist). Для постквантовых подписей на JVM сейчас используйте Java JCA или Bouncy Castle.

Факты и проверки, на которых они основаны

Ничего из приведённого ниже не скопировано из документации. 20 октября 2026 г. проверка content/libraries/verify/tink/run.sh запустила Tink (Java) 1.24.0 в образе контейнера maven:3.9.16-eclipse-temurin-25; содержимое страницы должно совпадать с выводом проверки, а у каждого факта приведены соответствующие строки вывода.

  • Кодировка подписи ECDSA

    der

    Подписи ECDSA_P256 записаны в DER после префикса вывода.

    Что записала проверка
    fact ecdsa-signature-encoding = der
  • Префикс подписи

    tink-5-byte

    Стандартные шаблоны добавляют в начало 5-байтовый префикс (0x01 и идентификатор ключа), и JDK отклонил такую подпись с ошибкой Invalid encoding for signature.

    Что записала проверка
    fact output-prefix = tink-5-byte
    evidence output-prefix: ECDSA_P256 signature starts with 0x01 followed by the 4-byte key id, then the DER signature
    evidence output-prefix: ECDSA_P256_IEEE_P1363 signature: 69 bytes (5-byte prefix + 64)
    evidence output-prefix: ECDSA_P256 signature given to the JDK with the prefix: SignatureException: Invalid encoding for signature
  • Кодировка шаблона RAW

    ieee-p1363

    ECDSA_P256_RAW выдал 64 байта IEEE P1363 r‖s (без префикса, не DER); подпись прошла проверку с SHA256withECDSAinP1363Format.

    Что записала проверка
    fact raw-template-encoding = ieee-p1363
    evidence raw-template-encoding: ECDSA_P256_RAW signature: 64 bytes, parses as DER: false
    evidence raw-template-encoding: ECDSA_P256_RAW signature verifies in the JDK's SHA256withECDSAinP1363Format: true
  • Кодировка подписи после импорта PEM

    der

    Открытый ключ, импортированный из PEM через SignaturePemKeysetReader, проверял только подписи DER; подпись raw r‖s не проходила проверку, пока её не перекодировали в DER.

    Что записала проверка
    fact pem-import-encoding = der
  • Nonce в ECDSA

    random

    Случайный nonce: две подписи одного сообщения различаются.

    Что записала проверка
    fact ecdsa-nonce = random
  • Low-S при подписи

    either

    s не нормализуется: среди 64 подписей встретились значения high-S.

    Что записала проверка
    fact ecdsa-low-s-sign = either
  • High-S при проверке

    accepts-high-s

    Подпись, в которой s заменили на n − s, прошла проверку.

    Что записала проверка
    fact ecdsa-low-s-verify = accepts-high-s
  • Что хеширует вызов подписи

    hashes-message

    Примитивы подписывают само сообщение; API для подписи дайджеста нет.

    Что записала проверка
    fact ecdsa-prehash = hashes-message
  • Некорректная подпись ECDSA

    throws

    Некорректная подпись вызывает GeneralSecurityException: invalid signature.

    Что записала проверка
    fact ecdsa-malformed-signature = throws
    evidence ecdsa-malformed-signature: GeneralSecurityException: invalid signature
  • Кривые ECDSA

    P-256P-384P-521

    Шаблоны P-256, P-384 и P-521: подпись и проверка прошли успешно.

    Что записала проверка
    fact ecdsa-curves = P-256, P-384, P-521
  • EdDSA

    Ed25519

    Шаблоны Ed25519 с префиксом вывода и без него.

    Что записала проверка
    fact eddsa = Ed25519
  • Постквантовые алгоритмы

    none

    В Tink для Java 1.24.0 не зарегистрировано шаблонов ML-DSA и SLH-DSA.

    Что записала проверка
    fact pqc = none
    evidence pqc: ML_DSA_65: GeneralSecurityException: Name ML_DSA_65 does not exist
    evidence pqc: SLH_DSA_SHA2_128S: GeneralSecurityException: Name SLH_DSA_SHA2_128S does not exist
  • Форматы открытых ключей

    spki-pem

    SPKI в PEM через SignaturePemKeysetReader; в остальных случаях ключи хранятся в наборах ключей Tink.

    Что записала проверка
    fact public-key-formats = spki-pem
    evidence public-key-formats: SPKI PEM through SignaturePemKeysetReader, then verifying the RAW template's raw signature: GeneralSecurityException: invalid signature
    evidence public-key-formats: the same signature re-encoded as DER against the PEM-imported key: ok
    evidence public-key-formats: Tink JSON public keyset round trip: ok
  • Формат набора ключей

    tink-jsontink-binary

    Наборы ключей сериализуются в Tink JSON или в двоичный protobuf; набор открытых ключей без потерь прошёл сериализацию в JSON и обратно.

    Что записала проверка
    fact keyset-format = tink-json, tink-binary

Использование

Сравните стандартную подпись с префиксом и переносимую 64-байтовую подпись, созданную по шаблону без префикса.
import com.google.crypto.tink.KeyTemplates;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.PublicKeySign;
import com.google.crypto.tink.PublicKeyVerify;
import com.google.crypto.tink.RegistryConfiguration;
import com.google.crypto.tink.signature.SignatureConfig;
import java.nio.charset.StandardCharsets;

public class Usage {
    public static void main(String[] args) throws Exception {
        SignatureConfig.register();
        byte[] message = "order #1042 approved".getBytes(StandardCharsets.UTF_8);

        // Default templates prepend a 5-byte key-id prefix that no other library expects.
        KeysetHandle tinkOnly = KeysetHandle.generateNew(KeyTemplates.get("ECDSA_P256"));
        byte[] prefixed = tinkOnly.getPrimitive(RegistryConfiguration.get(), PublicKeySign.class).sign(message);

        // For signatures other systems verify, pick a *_WITHOUT_PREFIX / *_RAW template.
        KeysetHandle portable = KeysetHandle.generateNew(KeyTemplates.get("ECDSA_P256_IEEE_P1363_WITHOUT_PREFIX"));
        byte[] raw = portable.getPrimitive(RegistryConfiguration.get(), PublicKeySign.class).sign(message);
        portable.getPublicKeysetHandle().getPrimitive(RegistryConfiguration.get(), PublicKeyVerify.class).verify(raw, message);

        System.out.println("prefixed first byte: 0x0" + prefixed[0] + ", portable signature: " + raw.length + " bytes, verified");
    }
}

Тестовый стенд для фрагментов кода запускает каждый пример без изменений в образе контейнера maven:3.9.16-eclipse-temurin-25, и запуск завершается ошибкой, если вывод не совпадает.

Подготовка
mvn -q -B dependency:copy-dependencies -DoutputDirectory=lib >/dev/null
Команда
java -cp 'lib/*' Usage.java 2>/dev/null
Вывод должен совпадать (регулярное выражение)
prefixed first byte: 0x01, portable signature: 64 bytes, verified

Библиотеки