Contents

Google Tink 库简介

1. 简介

如今,许多开发人员使用加密技术来保护用户数据。

在密码学中,小的实现错误可能会产生严重的后果,了解如何正确实施密码学是一项复杂且耗时的任务。

在本教程中,我们将描述Tink ——一个多语言、跨平台的加密库,可以帮助我们实现安全的加密代码。

2. 依赖

我们可以使用 Maven 或 Gradle 来导入 Tink。

对于我们的教程,我们将只添加Tink 的 Maven 依赖 项:

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

虽然我们可以使用 Gradle 来代替:

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

3. 初始化

在使用任何 Tink API 之前,我们需要初始化它们。

如果我们需要使用 Tink 中所有原语的所有实现,我们可以使用*TinkConfig.register()*方法:

TinkConfig.register();

例如,如果我们只需要 AEAD 原语,我们可以使用*AeadConfig.register()*方法:

AeadConfig.register();

还为每个实现提供了可定制的初始化。

4. Tink原语

库使用的主要对象称为原语,根据类型,它们包含不同的加密功能。

一个原语可以有多个实现:

原始 实现
AEAD AES-EAX、AES-GCM、AES-CTR-HMAC、KMS Envelope、CHACHA20-POLY1305
流式传输 AEAD AES-GCM-HKDF-STREAMING,AES-CTR-HMAC-STREAMING
确定性 AEAD AEAD:AES-SIV
MAC HMAC-SHA2
电子签名 ECDSA over NIST curves, ED25519
混合加密 带有 AEAD 和 HKDF 的 ECIES,(NaCl CryptoBox)

我们可以通过调用相应工厂类的方法getPrimitive()并传递一个KeysetHandle来获得一个原语:

Aead aead = AeadFactory.getPrimitive(keysetHandle);

4.1. KeysetHandle

为了提供加密功能,每个原语都需要一个包含所有密钥材料和参数的密钥结构。

Tink 提供了一个对象——KeysetHandle——它用一些额外的参数和元数据包装了一个键集。

所以,在实例化一个原语之前,我们需要创建一个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)));

5. 加密

Tink 提供了多种应用 AEAD 算法的方法。让我们来看看。

5.1. AEAD

AEAD 提供 Authenticated Encryption with Associated Data,这意味着我们可以加密明文,并且可以选择提供应该经过身份验证但不加密的关联数据

请注意,此算法可确保相关数据的真实性和完整性,但不能确保其保密性。

如前所述,要使用 AEAD 实现之一加密数据,我们需要初始化库并创建一个keysetHandle

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

完成后,我们可以获取原语并加密所需的数据:

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

接下来,我们可以使用*decrypt()*方法解密密文:

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

5.2. 流式传输 AEAD

同样,当要加密的数据太大而无法一步处理时,我们可以使用流式 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();

基本上,我们需要WriteableByteChannel来实现这一点。

因此,要解密cipherTextFile,我们需要使用 ReadableByteChannel

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.混合加密

除了对称加密,Tink 还为混合加密实现了几个原语。

通过混合加密,我们可以获得对称密钥的效率和非对称密钥的便利性。

简而言之,我们将使用对称密钥加密明文**,使用公钥加密对称密钥。**

请注意,它仅提供保密性,而不是发件人的身份真实性。

那么,让我们看看如何使用HybridEncryptHybridDecrypt

TinkConfig.register();
KeysetHandle privateKeysetHandle = KeysetHandle.generateNew(
  HybridKeyTemplates.ECIES_P256_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256);
KeysetHandle publicKeysetHandle = privateKeysetHandle.getPublicKeysetHandle();
String plaintext = "blogdemo";
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 是来自上下文的隐式公共数据,可以为 *null *或空,或用作 AEAD 加密的“关联数据”输入或用作 HKDF 的“CtxInfo”输入。

ciphertext允许检查contextInfo的完整性,但不能检查其保密性或真实性。

7. 消息验证码

Tink 还支持消息验证码或 MAC。

MAC 是一个由几个字节组成的块,我们可以使用它来验证消息。

让我们看看如何创建 MAC,然后验证其真实性:

TinkConfig.register();
KeysetHandle keysetHandle = KeysetHandle.generateNew(
  MacKeyTemplates.HMAC_SHA256_128BITTAG);
String data = "blogdemo";
Mac mac = MacFactory.getPrimitive(keysetHandle);
byte[] tag = mac.computeMac(data.getBytes());
mac.verifyMac(tag, data.getBytes());

如果数据不真实,verifyMac()方法会抛出GeneralSecurityException

8. 数字签名

除了加密 API,Tink 还支持数字签名。

为了实现数字签名,该库使用PublicKeySign原语进行数据签名,并使用PublickeyVerify进行验证:

TinkConfig.register();
KeysetHandle privateKeysetHandle = KeysetHandle.generateNew(SignatureKeyTemplates.ECDSA_P256);
KeysetHandle publicKeysetHandle = privateKeysetHandle.getPublicKeysetHandle();
String data = "blogdemo";
PublicKeySign signer = PublicKeySignFactory.getPrimitive(privateKeysetHandle);
PublicKeyVerify verifier = PublicKeyVerifyFactory.getPrimitive(publicKeysetHandle);
byte[] signature = signer.sign(data.getBytes()); 
verifier.verify(signature, data.getBytes());

与之前的加密方式类似,当签名无效时,我们会得到一个GeneralSecurityException