Как легко зашифровать и расшифровать строку с помощью Tink?

До сих пор я использовал jasypt шифровать строку перед ее сохранением на диске при закрытии приложения, а затем при открытии приложения для дешифрования строки после извлечения ее с диска.

Это было очень легко с jasyptэто был код:

private static final String JASYPT_PWD = "mypassword";

public static String encryptString(String string) {
    StrongTextEncryptor textEncryptor = new StrongTextEncryptor();
    textEncryptor.setPassword(JASYPT_PWD);
    return textEncryptor.encrypt(string);
}

public static String decryptString(String string) {
    StrongTextEncryptor textEncryptor = new StrongTextEncryptor();
    textEncryptor.setPassword(JASYPT_PWD);
    return textEncryptor.decrypt(string);
}

Это работало отлично, но теперь jasypt устарел, и я пытаюсь перейти на библиотеку Google Tink, проблема в том, что Google Tink кажется гораздо сложнее просто зашифровать и расшифровать строку так же легко, как с jasypt,

Я не могу найти в Tink repo readme - простой способ шифрования и дешифрования строки, просто можно найти более сложные операции, которые на самом деле я не могу понять, потому что мои знания в области шифрования совершенно пусты. Из-за этого я использовал очень простую библиотеку, такую ​​как jasypt,

Это Tink РЕПО: https://github.com/Google/tink

Есть ли простой способ, похожий на мой jasypt код, чтобы зашифровать и расшифровать строку с Tink?

2 ответа

Решение

StrongTextEncryptor -класс в вашем jasypt -example-code использует PBEWithMD5AndTripleDES -алгоритм. Этот алгоритм использует блочный шифр с симметричным ключом Triple DES и получает ключ от пароля, используя MD5 хэш-функция Последнее называется шифрованием на основе пароля, и это не поддерживается в Tink (по крайней мере, по состоянию на 08/2018), см. Как создать симметричный ключ шифрования с помощью Google Tink?, Таким образом, это невозможно в Tink шифровать с помощью пароля и концепции, используемой до сих пор в jasypt -код не может быть реализован. Если шифрование на основе пароля должно использоваться в любом случае, который является нарушителем Tink,

Другой подход заключается в прямом использовании ключа. Tink имеет AesGcmJce -класс, который использует AES-GCM для шифрования. Здесь ключ должен иметь длину 128 бит или 256 бит:

String plainText = "This is a plain text which needs to be encrypted!";
String aad = "These are additional authenticated data (optional)";
String key = "ThisIsThe32ByteKeyForEncryption!"; // 256 bit

// Encryption
AesGcmJce agjEncryption = new AesGcmJce(key.getBytes());
byte[] encrypted = agjEncryption.encrypt(plainText.getBytes(), aad.getBytes());

// Decryption
AesGcmJce agjDecryption = new AesGcmJce(key.getBytes());
byte[] decrypted = agjDecryption.decrypt(encrypted, aad.getBytes());

Использование простое и, кроме того, используемый шифр (AES-GCM) безопасно. Тем не менее Tink Сами разработчики не рекомендуют этот подход, потому что AesGcmJce -класс принадлежит com.google.crypto.tink.subtle -пакет, который может быть изменен в любое время без предварительного уведомления (см. также здесь, раздел " Важные предупреждения"). Следовательно, и этот подход не является оптимальным.

Ну как же Tink обычно используют симметричное шифрование? Это показано в следующем фрагменте из:

String plainText = "This is a plain text which needs to be encrypted!";
String aad = "These are additional authenticated data (optional)";

AeadConfig.register();

KeysetHandle keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.AES256_GCM);
Aead aead = AeadFactory.getPrimitive(keysetHandle);

// Encryption
byte[] ciphertext = aead.encrypt(plainText.getBytes(), aad.getBytes());

// Decryption
byte[] decrypted = aead.decrypt(ciphertext, aad.getBytes());

generateNew -метод генерирует новый ключ. Однако создание не основано на пароле или последовательности байтов, и поэтому ключ, сгенерированный для шифрования, не может быть легко восстановлен для дешифрования. Следовательно, ключ, используемый для шифрования, должен быть сохранен в системе хранения, например, в файловой системе, чтобы его можно было использовать позже для расшифровки. Tink позволяет хранить ключи открытого текста (что, конечно, не рекомендуется). Более безопасный подход - это шифрование ключей с мастер-ключами, хранящимися в удаленной системе управления ключами (это более подробно объясняется Tink JAVA-HOWTO, разделы " Хранение наборов ключей и загрузка существующих наборов ключей").

Tink Концепция управления ключами (с целью избежать случайной утечки чувствительного ключевого материала) также делает ее несколько громоздкой (это может измениться в более поздних версиях). Вот почему я сказал в своем комментарии, что я не уверен, если Tink соответствует вашим идеям относительно простоты.

Отказ от ответственности: ведущий разработчик Тинка.

(У меня недостаточно репутации, чтобы комментировать, поэтому я добавлю ответ)

Питер, если ты работаешь над приложением для Android, ты можешь проверить AndroidKeysetManager [2]. Есть пример Hello World [3], из которого вы можете скопировать.

В общем, когда вы хотите что-то зашифровать, первый вопрос, который вы должны задать себе, - это где вы будете хранить ключи. Не имеет смысла хранить ключи в том же месте (и с тем же ACL), где вы храните свои зашифрованные данные. Ключи должны храниться в другом месте (или с другим ACL).

API управления ключами в Tink немного сложнее, потому что мы хотим, чтобы пользователи хранили ключи в нужном месте [1].

[1] https://github.com/google/tink/blob/master/docs/JAVA-HOWTO.md#storing-keysets

[2] http://google.github.io/tink/javadoc/tink-android/1.2.2/com/google/crypto/tink/integration/android/AndroidKeysetManager.html

[3] https://github.com/google/tink/blob/master/examples/helloworld/android/app/src/main/java/com/helloworld/TinkApplication.java

I was looking for a simple way to encrypt a short text message with a Password Based Encryption (PBE) and use Google Tink for the cryptographic part and found that Tink doesn't provide PBE natively. To solve this problem I made a simple class that does all the work with PBE, keyhandling and encryption/decryption.

The usage in a program is very simple and you need only 4 lines of code to use it:

AeadConfig.register(); // tink initialisation
TinkPbe tpbe = new TinkPbe(); // tink pbe initialisation
String ciphertextString = tpbe.encrypt(passwordChar, plaintextString); // encryption
String decryptedtextString = tpbe.decrypt(passwordChar, ciphertextString); // decryption

On my Github you find two sample programms to show how to implement the class (with and without GUI): https://github.com/java-crypto/H-Google-Tink/tree/master/H%20Tink%20Textencryption%20PBE

Here is the sourcecode of the TinkPbe.java-class:

package tinkPbe;

/*
*  
* Diese Klasse gehört zu diesen beiden Hauptklassen
* This class belongs to these main classes:
* TinkPbeConsole.java | TinkPbeGui.java 
* 
* Herkunft/Origin: http://javacrypto.bplaced.net/
* Programmierer/Programmer: Michael Fehr
* Copyright/Copyright: frei verwendbares Programm (Public Domain)
* Copyright: This is free and unencumbered software released into the public domain.
* Lizenttext/Licence: <http://unlicense.org>
* getestet mit/tested with: Java Runtime Environment 8 Update 191 x64
* getestet mit/tested with: Java Runtime Environment 11.0.1 x64
* Datum/Date (dd.mm.jjjj): 20.11.2019
* Funktion: verschlüsselt und entschlüsselt einen Text mittels Google Tink
*           im Modus AES GCM 256 Bit. Der Schlüssel wird mittels PBE
*           (Password based encryption) erzeugt.
* Function: encrypts and decrypts a text message with Google Tink.
*           Used Mode is AES GCM 256 Bit. The key is generated with PBE
*           (Password based encryption).
*
* Sicherheitshinweis/Security notice
* Die Programmroutinen dienen nur der Darstellung und haben keinen Anspruch auf eine korrekte Funktion, 
* insbesondere mit Blick auf die Sicherheit ! 
* Prüfen Sie die Sicherheit bevor das Programm in der echten Welt eingesetzt wird.
* The program routines just show the function but please be aware of the security part - 
* check yourself before using in the real world !
* 
* Das Programm benötigt die nachfolgenden Bibliotheken (siehe Github Archiv):
* The programm uses these external libraries (see Github Archive):
* jar-Datei/-File: tink-1.2.2.jar
* https://mvnrepository.com/artifact/com.google.crypto.tink/tink/1.2.2
* jar-Datei/-File: protobuf-java-3.10.0.jar
* https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java/3.10.0
* jar-Datei/-File: json-20190722.jar
* https://mvnrepository.com/artifact/org.json/json/20190722
*  
*/

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import com.google.crypto.tink.Aead;
import com.google.crypto.tink.CleartextKeysetHandle;
import com.google.crypto.tink.JsonKeysetReader;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.aead.AeadFactory;

public class TinkPbe {

    public static String encrypt(char[] passwordChar, String plaintextString)
            throws GeneralSecurityException, IOException {
        byte[] keyByte = pbkdf2(passwordChar);
        String valueString = buildValue(keyByte);
        String jsonKeyString = writeJson(valueString);
        KeysetHandle keysetHandleOwn = CleartextKeysetHandle.read(JsonKeysetReader.withString(jsonKeyString));
        // initialisierung
        Aead aead = AeadFactory.getPrimitive(keysetHandleOwn);
        // verschlüsselung
        byte[] ciphertextByte = aead.encrypt(plaintextString.getBytes("utf-8"), null); // no aad-data
        return Base64.getEncoder().encodeToString(ciphertextByte);
    }

    public static String decrypt(char[] passwordChar, String ciphertextString)
            throws GeneralSecurityException, IOException {
        byte[] keyByte = pbkdf2(passwordChar);
        String valueString = buildValue(keyByte);
        String jsonKeyString = writeJson(valueString);
        KeysetHandle keysetHandleOwn = CleartextKeysetHandle.read(JsonKeysetReader.withString(jsonKeyString));
        // initialisierung
        Aead aead = AeadFactory.getPrimitive(keysetHandleOwn);
        // verschlüsselung
        byte[] plaintextByte = aead.decrypt(Base64.getDecoder().decode(ciphertextString), null); // no aad-data
        return new String(plaintextByte, StandardCharsets.UTF_8);
    }

    private static byte[] pbkdf2(char[] passwordChar)
            throws NoSuchAlgorithmException, InvalidKeySpecException, UnsupportedEncodingException {
        final byte[] passwordSaltByte = "11223344556677881122334455667788".getBytes("UTF-8");
        final int PBKDF2_ITERATIONS = 10000; // anzahl der iterationen, höher = besser = langsamer
        final int SALT_SIZE_BYTE = 256; // grösse des salts, sollte so groß wie der hash sein
        final int HASH_SIZE_BYTE = 256; // größe das hashes bzw. gehashten passwortes, 128 byte = 512 bit
        byte[] passwordHashByte = new byte[HASH_SIZE_BYTE]; // das array nimmt das gehashte passwort auf
        PBEKeySpec spec = new PBEKeySpec(passwordChar, passwordSaltByte, PBKDF2_ITERATIONS, HASH_SIZE_BYTE);
        SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
        passwordHashByte = skf.generateSecret(spec).getEncoded();
        return passwordHashByte;
    }

    private static String buildValue(byte[] gcmKeyByte) {
        // test for correct key length
        if ((gcmKeyByte.length != 16) && (gcmKeyByte.length != 32)) {
            throw new NumberFormatException("key is not 16 or 32 bytes long");
        }
        // header byte depends on keylength
        byte[] headerByte = new byte[2]; // {26, 16 }; // 1A 10 for 128 bit, 1A 20 for 256 Bit
        if (gcmKeyByte.length == 16) {
            headerByte = new byte[] { 26, 16 };
        } else {
            headerByte = new byte[] { 26, 32 };
        }
        byte[] keyByte = new byte[headerByte.length + gcmKeyByte.length];
        System.arraycopy(headerByte, 0, keyByte, 0, headerByte.length);
        System.arraycopy(gcmKeyByte, 0, keyByte, headerByte.length, gcmKeyByte.length);
        String keyBase64 = Base64.getEncoder().encodeToString(keyByte);
        return keyBase64;
    }

    private static String writeJson(String value) {
        int keyId = 1234567; // fix
        String str = "{\n";
        str = str + "    \"primaryKeyId\": " + keyId + ",\n";
        str = str + "    \"key\": [{\n";
        str = str + "        \"keyData\": {\n";
        str = str + "            \"typeUrl\": \"type.googleapis.com/google.crypto.tink.AesGcmKey\",\n";
        str = str + "            \"keyMaterialType\": \"SYMMETRIC\",\n";
        str = str + "            \"value\": \"" + value + "\"\n";
        str = str + "        },\n";
        str = str + "        \"outputPrefixType\": \"TINK\",\n";
        str = str + "        \"keyId\": " + keyId + ",\n";
        str = str + "        \"status\": \"ENABLED\"\n";
        str = str + "    }]\n";
        str = str + "}";
        return str;
    }
}

Please keep in mind that using a plaintext-String means that your plaintext is inmutable and undeletable in your heap until the Garbage Collector destroys them.

Более подробное описание доступно на моем веб-сайте: http://javacrypto.bplaced.net/h-tink-string-encryption-using-pbe-and-gui/

Другие вопросы по тегам