7

到目前为止,我一直在使用 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 自述文件中找不到加密和解密字符串的简单方法,只能找到更复杂的操作,实际上我无法理解,因为我在加密方面的知识完全是空的。因此,我使用了一个非常简单的库,比如 jasypt。

这是 Tink 仓库:https ://github.com/Google/tink

有没有一种简单的方法,类似于我的 jasypt 代码,用 Tink 加密和解密字符串?

4

3 回答 3

14

注意:帖子指的是Tink 版本 1.2.2。发布的代码与更高版本部分不兼容。

-example-code中的StrongTextEncryptor-class使用-algorithm。该算法使用对称密钥分组密码,并使用散列函数从密码中导出密钥。后者称为基于密码的加密,并且不支持(至少在 08/2018 时),请参阅如何使用 Google Tink 创建对称加密密钥?. 因此,不可能通过密码进行加密,并且迄今为止在-code中使用的概念无法实现。如果要在任何情况下使用基于密码的加密,这对.jasyptPBEWithMD5AndTripleDESTriple DESMD5TinkTinkjasyptTink

另一种方法是直接使用密钥。Tink具有用于加密的AesGcmJce-class 。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-developers 自己不推荐这种方法,因为AesGcmJce-class 属于com.google.crypto.tink.subtle-package可能随时更改,恕不另行通知,(另请参见此处重要警告部分)。因此,这种方法也不是最优的。

那么,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允许存储明文密钥(当然不推荐)。一种更安全的方法是使用存储在远程密钥管理系统中的主密钥对密钥进行加密(这在JAVA-HOWTOTink存储密钥集和加载现有密钥集部分有更详细的解释)。

Tink的密钥管理概念(旨在避免敏感密钥材料的意外泄漏)使其在某种程度上也很麻烦(这可能会在以后的版本中改变)。这就是为什么我在评论中说我不确定是否Tink符合您关于简单性的想法。

于 2019-03-10T08:56:28.990 回答
6

免责声明:我是 Tink 的首席开发人员。

如果您正在开发 Android 应用程序,您可以查看AndroidKeysetManager. 您可以从中复制一个 hello world 示例

通常,每当您想加密某些东西时,您应该问自己的第一个问题是您将在哪里存储密钥。将密钥存储在存储加密数据的同一位置(并使用相同的 ACL)并没有多大意义。密钥应存储在不同的位置(或使用不同的 ACL)。

Tink 的密钥管理 API 有点复杂,因为我们希望引导用户将密钥存储在正确的位置

于 2019-03-19T01:07:02.997 回答
0

我一直在寻找一种简单的方法来使用基于密码的加密 (PBE) 对短文本消息进行加密,并使用 Google Tink 作为加密部分,但发现 Tink 本身并不提供 PBE。为了解决这个问题,我创建了一个简单的类,它使用 PBE、密钥处理和加密/解密来完成所有工作。

在程序中的使用非常简单,只需要 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

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

以下是 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;
    }
}

请记住,使用纯文本字符串意味着您的纯文本在您的堆中是不可变且不可删除的,直到垃圾收集器销毁它们。

我的网站上有更详细的描述:http: //javacrypto.bplaced.net/h-tink-string-encryption-using-pbe-and-gui/

于 2019-11-21T10:04:15.330 回答