Как легко зашифровать и расшифровать строку с помощью 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
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/