«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. Инициализация

Перед использованием любого из API Tink, который нам нужен для их инициализации.

Если нам нужно использовать все реализации всех примитивов в Tink, мы можем использовать метод TinkConfig.register():

TinkConfig.register();

Хотя, например, если нам нужен только примитив AEAD, мы можем использовать AeadConfig. Метод register():

AeadConfig.register();

Для каждой реализации также предусмотрена настраиваемая инициализация.

4. Примитивы Tink

Основные объекты, используемые библиотекой, называются примитивами, которые, в зависимости от типа, содержат различные криптографические функции.

Примитив может иметь несколько реализаций:

Primitive Implementations
AEAD AES-EAX, AES-GCM, AES-CTR-HMAC, KMS Envelope, CHACHA20-POLY1305
Streaming AEAD AES-GCM-HKDF-STREAMING, AES-CTR-HMAC-STREAMING
Deterministic AEAD AEAD: AES-SIV
MAC HMAC-SHA2
Digital Signature ECDSA over NIST curves, ED25519
Hybrid Encryption ECIES with AEAD and HKDF, (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 обеспечивает аутентифицированное шифрование со связанными данными, что означает, что мы можем шифровать открытый текст и, при необходимости, предоставлять связанные данные, которые должны быть аутентифицированы, но не зашифрованы.

Обратите внимание, что этот алгоритм обеспечивает подлинность и целостность связанных данных, но не их секретность.

Чтобы зашифровать данные с помощью одной из реализаций AEAD, как мы видели ранее, нам нужно инициализировать библиотеку и создать keysetHandle:

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());

Далее мы можем расшифровать зашифрованный текст с помощью метода 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 реализует несколько примитивов для гибридного шифрования.

С гибридным шифрованием мы можем получить эффективность симметричных ключей и удобство асимметричных ключей.

Проще говоря, мы будем использовать симметричный ключ для шифрования открытого текста и открытый ключ для шифрования только симметричного ключа.

Обратите внимание, что он обеспечивает только секретность, а не подлинность личности отправителя.

Итак, давайте посмотрим, как использовать HybridEncrypt и HybridDecrypt. Вход «CtxInfo» для HKDF.

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. Код аутентификации сообщений

Tink также поддерживает коды аутентификации сообщений или MAC-адреса.

MAC — это блок из нескольких байтов, который мы можем использовать для аутентификации сообщения.

Давайте посмотрим, как мы можем создать MAC, а затем проверить его подлинность:

«

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. Цифровая подпись

Помимо API-интерфейсов шифрования, 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());

Аналогично предыдущему методу шифрования, когда подпись недействительна, мы получим GeneralSecurityException .

9. Заключение

В этой статье мы представили библиотеку Google Tink, используя ее реализацию на Java.

Мы увидели, как использовать для шифрования и расшифровки данных и как защитить их целостность и подлинность. Более того, мы увидели, как подписывать данные с помощью API цифровой подписи.

Как всегда, пример кода доступен на GitHub.