本文へスキップ
ecdsa.com
Java · Kotlin

Tink (Java)

Google の Tink for Java は、鍵セットとテンプレートを中心に設計された、誤用しにくい暗号 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 では、個々の鍵の代わりに鍵セット(keyset)を、アルゴリズムの代わりに ECDSA_P256 や ED25519 などの名前付きテンプレートを使います。鍵セットは ID 付きの鍵の集まりで、そのうち 1 つがプライマリに指定されています。プリミティブ(PublicKeySign、PublicKeyVerify)は鍵のローテーションを透過的に行います。これがこの設計の狙いであり、相互運用で思わぬ問題が起きる原因でもあります。鍵セットは PEM ではなく、Tink JSON またはバイナリの protobuf として保存されます。

デフォルトの ECDSA_P256 テンプレートは、出力プレフィックス(0x01 と 4 バイトの鍵 ID)を先頭に付け、その後に DER 署名を続けます。JDK はこのような署名を Invalid encoding for signature で拒否しました。Tink 以外の検証者はどれも同じように拒否します。名前が _WITHOUT_PREFIX または _RAW で終わるテンプレートはプレフィックスを付けませんが、名前からはエンコーディングもわかりません。ECDSA_P256_RAW が出力したのは DER ではなく、64 バイトの IEEE P1363 形式の r‖s でした。この署名は JDK の SHA256withECDSAinP1363Format では検証を通り、SHA256withECDSA では失敗しました。サンプルが ECDSA_P256_IEEE_P1363_WITHOUT_PREFIX を使っているように、相手側が想定するエンコーディングに正確に合わせてテンプレートを選び、DER ⇄ 生形式の変換ツールでバイト列を確認してください。

外部の公開鍵は SignaturePemKeysetReader を通してインポートしますが、こうして得た鍵で検証できたのは DER 署名だけでした。Tink で生成した P1363 形式の鍵が受け入れた生形式の署名も、PEM からインポートした鍵では、DER に再エンコードするまで検証に失敗しました。署名にはランダムな nonce が使われ、s は正規化されず、検証では high-S も受け入れられます。不正な入力に対しては GeneralSecurityException: invalid signature がスローされます。

Tink for Java 1.24.0 には、P-256、P-384、P-521 の ECDSA、Ed25519、RSA(PKCS#1 v1.5 と PSS)のテンプレートがあります。このリリースには ML-DSA や SLH-DSA のテンプレートは登録されていません(Name ML_DSA_65 does not exist)。現時点で JVM 上でポスト量子署名を使うには、Java JCA か Bouncy Castle を参照してください。

事実とその根拠となるチェック

以下の内容はドキュメントからの転記ではありません。2026年10月20日に、チェック content/libraries/verify/tink/run.sh がコンテナイメージ maven:3.9.16-eclipse-temurin-25 内で Tink (Java) 1.24.0 を実行しました。このページの内容はその出力と一致していなければならず、各事実には該当する出力行を示しています。

  • ECDSA 署名のエンコーディング

    der

    ECDSA_P256 の署名は、出力プレフィックスの後に続く DER です。

    チェックによる記録
    fact ecdsa-signature-encoding = der
  • 署名のプレフィックス

    tink-5-byte

    デフォルトのテンプレートは 5 バイトのプレフィックス(0x01 と鍵 ID)を先頭に付けます。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

    SignaturePemKeysetReader で PEM からインポートした公開鍵では、DER 署名しか検証できませんでした。生の r‖s 署名は、DER に再エンコードするまで検証に失敗しました。

    チェックによる記録
    fact pem-import-encoding = der
  • ECDSA の nonce

    random

    nonce はランダムです。同じメッセージへの 2 つの署名は異なります。

    チェックによる記録
    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 for 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

    SignaturePemKeysetReader で SPKI の PEM を読み込めます。それ以外の鍵は 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

ライブラリ