简体   繁体   English

如何使用 Tink 轻松加密和解密字符串?

[英]How to easily encrypt and decrypt a String using Tink?

Until now, I was using jasypt to encrypt a string before storing it on disk on app closing, and later when opening the app for decrypt the string after retrieving it from disk.到目前为止,我一直在使用 jasypt 加密字符串,然后在应用程序关闭时将其存储在磁盘上,稍后在从磁盘检索字符串后打开应用程序以解密字符串时。

It was super easy with jasypt, this was the code:使用 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);
}

It worked perfectly, but now, jasypt is deprecated and I'm trying to migrate to the Google Tink library, the problem is that Google Tink seems to be much more complex for just encrypt and decrypt a string as easily as with jasypt.它运行良好,但现在 jasypt 已被弃用,我正在尝试迁移到Google Tink库,问题是 Google Tink 似乎要复杂得多,因为它可以像使用 jasypt 一样轻松地加密和解密字符串。

I can't find in the Tink repo readme the simple way to encrypt and decrypt a string, just can find more complex operations which in fact I can't understand because my knowledge in encryption is totally empty.我在 Tink repo 自述文件中找不到加密和解密字符串的简单方法,只能找到更复杂的操作,实际上我无法理解,因为我的加密知识完全是空的。 Because of that I was using a very easy library like jasypt.因此,我使用了一个非常简单的库,比如 jasypt。

This is the Tink repo: https://github.com/Google/tink这是 Tink 存储库: https : //github.com/Google/tink

Is there an easy way, similar to my jasypt code, to encrypt and decrypt a string with Tink?有没有一种简单的方法,类似于我的 jasypt 代码,用 Tink 加密和解密字符串?

Note: The post refers to Tink version 1.2.2 .注意:该帖子指的是Tink 版本 1.2.2 The posted code is partially incompatible with later versions.发布的代码与更高版本部分不兼容。

The StrongTextEncryptor -class in your jasypt -example-code uses the PBEWithMD5AndTripleDES -algorithm. StrongTextEncryptor jasypt代码中的StrongTextEncryptor类使用PBEWithMD5AndTripleDES算法。 This algorithm uses the symmetric-key block cipher Triple DES and derives the key from the password using the MD5 hash function.该算法使用对称密钥分组密码Triple DES并使用MD5散列函数从密码中派生出密钥。 The latter is called password-based encryption and this isn't supported in Tink (at least as at 08/2018), see How to create symmetric encryption key with Google Tink?后者称为基于密码的加密Tink不支持功能(至少在 08/2018),请参阅如何使用 Google Tink 创建对称加密密钥? . . Thus, it's impossible in Tink to encrypt by means of a password and the concept used so far in the jasypt -code couldn't be implemented.因此,在Tink不可能通过密码进行加密,并且目前在jasypt代码中使用的概念无法实现。 If password-based encryption is to be used in any case that is a deal-breaker for Tink .如果在任何情况下都使用基于密码的加密,这对Tink来说是一个破坏者。

Another approach is to directly use a key.另一种方法是直接使用密钥。 Tink has the AesGcmJce -class which uses AES-GCM for encryption. TinkAesGcmJce类,它使用AES-GCM进行加密。 Here the key must have a length of either 128 Bit or 256 bit:这里密钥的长度必须为 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());

The use is simple and furthermore the used cipher ( AES-GCM ) is secure.使用简单,而且使用的密码( AES-GCM )是安全的。 However, the Tink -developers themselves don't recommend this approach because the AesGcmJce -class belongs to the com.google.crypto.tink.subtle -package which may change at any time without further notice , (see also here , section Important Warnings ).但是, Tink人员自己不推荐这种方法,因为AesGcmJce类属于com.google.crypto.tink.subtle ,该包可能随时更改,恕不另行通知,(另请参阅此处重要警告部分)。 Therefore, also this approach isn't optimal.因此,这种方法也不是最佳的。

Well, how does Tink typically use symmetric encryption?那么, Tink通常如何使用对称加密? This is shown in the following snippet from :这显示在以下片段

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

The generateNew -method generates a new key. generateNew - 方法生成一个密钥。 However, the creation isn't based on a password or a byte-sequence and because of this, a key generated for the encryption can't be easily reconstructed for decryption.但是,创建不是基于密码或字节序列,因此,为加密生成的密钥无法轻松重建以进行解密。 Therefore, the key used for encryption has to be persisted to a storage system, eg the file system, so it can be used later for decryption.因此,用于加密的密钥必须保存在存储系统中,例如文件系统,以便稍后用于解密。 Tink allows the storing of cleartext keys (which is of course not recommended). Tink允许存储明文密钥(当然不推荐这样做)。 A more secure approach is the encryption of keys with master keys stored in a remote key management system (this is in more detail explained Tink 's JAVA-HOWTO , sections Storing Keysets and Loading Existing Keysets ).一个更安全的方法与存储在远程密钥管理系统的主密钥的密钥的加密(这是更详细地解释TinkJAVA-HOWTO ,部分贮藏密钥集加载现有密钥集)。

Tink 's key management concept (with the aim of avoiding accidental leakage of sensitive key material) makes it also somehow cumbersome (this may change in later versions). Tink的密钥管理概念(目的是避免敏感密钥材料的意外泄漏)也使其在某种程度上变得繁琐(这可能会在以后的版本中改变)。 That's why I said in my comment that I'm not sure if Tink fits your ideas concerning simplicity.这就是为什么我在评论中说我不确定Tink是否符合您关于简单性的想法。

Disclaimer: I'm Tink's lead developer.免责声明:我是 Tink 的首席开发人员。

If you're working on an Android app, you can check out AndroidKeysetManager .如果您正在开发 Android 应用程序,则可以查看AndroidKeysetManager There's a hello world example that you can copy from.一个你可以复制的 hello world 示例

In general whenever you want to encrypt something, the first question you should ask yourself is where you're going to store keys.一般来说,无论何时你想加密一些东西,你应该问自己的第一个问题是你将在哪里存储密钥。 It doesn't make a lot of senses to store keys in the same place (and with the same ACL) where you store your encrypted data.将密钥存储在您存储加密数据的同一位置(并使用相同的 ACL)并没有多大意义。 Keys should be stored in a different location (or with different ACL).密钥应该存储在不同的位置(或使用不同的 ACL)。

Tink's Key Management API is a bit more complex because we want to steer users to storing keys in the right location . Tink 的密钥管理 API 有点复杂,因为我们希望引导用户将密钥存储在正确的位置

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.我一直在寻找一种简单的方法来使用基于密码的加密 (PBE) 加密短文本消息,并使用 Google Tink 进行加密部分,发现 Tink 本身不提供 PBE。 To solve this problem I made a simple class that does all the work with PBE, keyhandling and encryption/decryption.为了解决这个问题,我创建了一个简单的类,它可以完成 PBE、密钥处理和加密/解密的所有工作。

The usage in a program is very simple and you need only 4 lines of code to use it:在程序中的使用非常简单,只需要4行代码即可使用:

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在我的 Github 上,您可以找到两个示例程序来展示如何实现该类(有和没有 GUI): https : //github.com/java-crypto/H-Google-Tink/tree/master/H%20Tink%20Textencryption% 20PBE

Here is the sourcecode of the TinkPbe.java-class:这是 TinkPbe.java 类的源代码:

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.请记住,使用明文字符串意味着您的明文在您的堆中是不可变和不可删除的,直到垃圾收集器销毁它们。

A more detailed description is vailable on my website: http://javacrypto.bplaced.net/h-tink-string-encryption-using-pbe-and-gui/我的网站上有更详细的描述: http : //javacrypto.bplaced.net/h-tink-string-encryption-using-pbe-and-gui/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM