1. 概述

在现代软件开发中,加密技术被广泛用于保护用户数据安全。

但加密实现中,一个小错误就可能带来严重后果。而正确掌握加密实现方法,对大多数开发者来说,既复杂又耗时。

Google Tink 是一个跨语言、跨平台的加密库,它帮助我们更安全、更高效地实现加密逻辑。

本篇文章将介绍 Tink 的核心概念、使用方法以及常见场景,适用于有经验的 Java 开发者,不会过多讲解加密基础概念。

2. 依赖引入

你可以使用 Maven 或 Gradle 来引入 Tink 的依赖。

Maven 示例:

<dependency>
    <groupId>com.google.crypto.tink</groupId>
    <artifactId>tink</artifactId>
    <version>1.2.2</version>
</dependency>

Gradle 示例:

dependencies {
    implementation 'com.google.crypto.tink:tink:latest'
}

⚠️ 注意:latest 不是实际版本号,使用时应替换为具体版本。

3. 初始化

在使用 Tink 的任何 API 之前,必须先注册其服务。

注册所有 Tink 实现:

TinkConfig.register();

如果你只需要使用 AEAD(Authenticated Encryption with Associated Data)功能,可以只注册对应的模块:

AeadConfig.register();

Tink 支持按需注册,你可以根据需要注册特定的加密模块,避免引入不必要的依赖。

4. 核心 Primitives(加密原语)

Tink 的核心是各种加密原语(Primitives),它们封装了不同的加密功能。每种原语可以有多个具体实现。

以下是常见的加密原语及其典型实现:

Primitive 实现示例
AEAD AES-EAX, AES-GCM, AES-CTR-HMAC, CHACHA20-POLY1305
Streaming AEAD AES-GCM-HKDF-STREAMING, AES-CTR-HMAC-STREAMING
Deterministic AEAD AES-SIV
MAC HMAC-SHA2
数字签名 ECDSA over NIST curves, ED25519
混合加密(Hybrid) ECIES with AEAD and HKDF, NaCl CryptoBox

获取原语对象的方法如下:

Aead aead = AeadFactory.getPrimitive(keysetHandle);

4.1 KeysetHandle

每个加密原语都需要一个密钥集(Keyset),而 Tink 提供了 KeysetHandle 类来封装密钥集及其元信息。

生成一个新密钥:

KeysetHandle keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.AES256_GCM);

保存密钥到文件:

String keysetFilename = "keyset.json";
CleartextKeysetHandle.write(keysetHandle, JsonKeysetWriter.withFile(new File(keysetFilename)));

从文件加载密钥:

String keysetFilename = "keyset.json";
KeysetHandle keysetHandle = CleartextKeysetHandle.read(JsonKeysetReader.withFile(new File(keysetFilename)));

⚠️ 注意:上面使用的是明文存储,适用于本地调试。生产环境应使用加密存储或 KMS(密钥管理系统)。

5. 加密操作

5.1 AEAD 加密

AEAD(带关联数据的身份验证加密)可以加密明文,并选择性地对附加数据进行身份验证。

示例:

AeadConfig.register();
KeysetHandle keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.AES256_GCM);

String plaintext = "baeldung";
String associatedData = "Tink";

Aead aead = AeadFactory.getPrimitive(keysetHandle);
byte[] ciphertext = aead.encrypt(plaintext.getBytes(), associatedData.getBytes());

// 解密
String decrypted = new String(aead.decrypt(ciphertext, associatedData.getBytes()));

✅ 附加数据(associatedData)不会被加密,但会参与完整性验证。

5.2 流式 AEAD 加密(Streaming AEAD)

当加密数据太大,无法一次性加载到内存时,可以使用流式加密。

加密大文件示例:

AeadConfig.register();
KeysetHandle keysetHandle = KeysetHandle.generateNew(
    StreamingAeadKeyTemplates.AES128_CTR_HMAC_SHA256_4KB);

StreamingAead streamingAead = StreamingAeadFactory.getPrimitive(keysetHandle);

FileChannel cipherTextDestination = new FileOutputStream("cipherTextFile").getChannel();
WritableByteChannel encryptingChannel =
    streamingAead.newEncryptingChannel(cipherTextDestination, associatedData.getBytes());

ByteBuffer buffer = ByteBuffer.allocate(CHUNK_SIZE);
InputStream in = new FileInputStream("plainTextFile");

while (in.available() > 0) {
    in.read(buffer.array());
    encryptingChannel.write(buffer);
}

encryptingChannel.close();
in.close();

解密示例:

FileChannel cipherTextSource = new FileInputStream("cipherTextFile").getChannel();
ReadableByteChannel decryptingChannel =
    streamingAead.newDecryptingChannel(cipherTextSource, associatedData.getBytes());

OutputStream out = new FileOutputStream("plainTextFile");
int cnt = 1;
do {
    buffer.clear();
    cnt = decryptingChannel.read(buffer);
    out.write(buffer.array());
} while (cnt > 0);

decryptingChannel.close();
out.close();

6. 混合加密(Hybrid Encryption)

混合加密结合了对称加密和非对称加密的优点:

  • 对称加密速度快,用于加密数据;
  • 非对称加密用于加密对称密钥。

示例:

TinkConfig.register();

KeysetHandle privateKeysetHandle = KeysetHandle.generateNew(
    HybridKeyTemplates.ECIES_P256_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256);
KeysetHandle publicKeysetHandle = privateKeysetHandle.getPublicKeysetHandle();

String plaintext = "baeldung";
String contextInfo = "Tink";

HybridEncrypt hybridEncrypt = HybridEncryptFactory.getPrimitive(publicKeysetHandle);
HybridDecrypt hybridDecrypt = HybridDecryptFactory.getPrimitive(privateKeysetHandle);

byte[] ciphertext = hybridEncrypt.encrypt(plaintext.getBytes(), contextInfo.getBytes());
byte[] plaintextDecrypted = hybridDecrypt.decrypt(ciphertext, contextInfo.getBytes());

contextInfo 是可选的上下文信息,用于增强加密的安全性。

7. 消息认证码(MAC)

Tink 支持使用 MAC(Message Authentication Code)对消息进行完整性验证。

示例:

TinkConfig.register();

KeysetHandle keysetHandle = KeysetHandle.generateNew(MacKeyTemplates.HMAC_SHA256_128BITTAG);

String data = "baeldung";

Mac mac = MacFactory.getPrimitive(keysetHandle);

byte[] tag = mac.computeMac(data.getBytes());
mac.verifyMac(tag, data.getBytes());

⚠️ 如果数据被篡改,verifyMac() 会抛出 GeneralSecurityException

8. 数字签名

Tink 支持数字签名,使用 PublicKeySign 进行签名,PublicKeyVerify 进行验签。

示例:

TinkConfig.register();

KeysetHandle privateKeysetHandle = KeysetHandle.generateNew(SignatureKeyTemplates.ECDSA_P256);
KeysetHandle publicKeysetHandle = privateKeysetHandle.getPublicKeysetHandle();

String data = "baeldung";

PublicKeySign signer = PublicKeySignFactory.getPrimitive(privateKeysetHandle);
PublicKeyVerify verifier = PublicKeyVerifyFactory.getPrimitive(publicKeysetHandle);

byte[] signature = signer.sign(data.getBytes());
verifier.verify(signature, data.getBytes());

⚠️ 如果签名无效,verify() 方法会抛出异常。

9. 总结

本文介绍了 Google Tink 库的基本使用方式,包括:

  • 依赖引入
  • 初始化
  • 密钥管理
  • 常见加密原语的使用:AEAD、流式加密、混合加密、MAC、数字签名

Tink 提供了安全、易用的加密接口,适合需要在项目中安全实现加密功能的 Java 开发者。

完整示例代码已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/libraries-security


原始标题:Guide to Google Tink | Baeldung