1

我编写了一个小应用程序来使用 AES 加密和解密字符串。这是代码:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class AesEncryptionTest {
    static IvParameterSpec initialisationVector = generateInitialisationVector();
    static SecretKey encryptionKey = generateKey();
    static String plainText = "test text 123\0\0\0";

    public static void main(String [] args) {
        try {
            System.out.println("Initial Plain Text = " + plainText);

            byte[] encryptedText = encrypt(plainText, encryptionKey);
            System.out.println("Encrypted Text     = " + encryptedText);

            String decryptedText = decrypt(encryptedText, encryptionKey);
            System.out.println("Decrypted Text     = " + decryptedText);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static byte[] encrypt(String plainText, SecretKey encryptionKey) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
        cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, initialisationVector);
        return cipher.doFinal(plainText.getBytes("UTF-8"));
    }

    public static String decrypt(byte[] encryptedText, SecretKey encryptionKey) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
        cipher.init(Cipher.DECRYPT_MODE, encryptionKey, initialisationVector);
        return new String(cipher.doFinal(encryptedText),"UTF-8");
    }

    public static SecretKey generateKey() {
        SecretKey secretKey = null;
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(128);
            secretKey = keyGenerator.generateKey();
        } catch (NoSuchAlgorithmException ex) {
           // Whine a little
        }
        return secretKey;
    }

    public static IvParameterSpec generateInitialisationVector() {
        byte[] initVector = new byte[16];
        SecureRandom secureRandom = new SecureRandom();
        secureRandom.nextBytes(initVector);

        return new IvParameterSpec(initVector);
   }
}

输出:

Initial Plain Text = test text 123
Encrypted Text     = [B@407dcb32
Decrypted Text     = test text 123

我主要关注的领域是加密成字节数组并解密回字符串。我知道这会引入意外行为和数据丢失。虽然在我的测试中没有观察到这一点,但任何人都可以提出任何有助于解决这个问题的改变吗?我认为通过确保双向使用 UTF-8 来解决这个问题。

如果有人看到我的代码有任何其他危险信号以及我是如何做到的,我愿意接受批评/建议。

非常感谢!

4

3 回答 3

5

你打电话toString()给一个byte[]永远不是一个好主意的人。基本上它没有给你任何有用的信息。

如果您想将任意二进制数据转换为字符串,我建议使用 hex 或 base64,这两者都在其他地方介绍过。没有迹象表明您实际上在加密/解密中丢失了任何信息 - 问题是您显示的加密数据。只要您尝试将其视为简单的编码文本数据(因为它不是),您应该没问题。特别是,您的代码已经将 UTF-8 指定为从原始文本到未加密二进制数据的转换,反之亦然 - 所以这是安全的。

如果您不需要将字节数组转换为字符串,最简单的方法是首先避免这样做。(例如,您可以非常简单地将其写入仍为二进制形式的文件,然后稍后将其加载回字节数组。)

于 2013-08-12T13:23:29.897 回答
2

你要求其他危险信号,所以我会给你一些关于加密的指示:

  1. 通常,当您使用算法名称时,您不必提供提供者名称。指定提供程序会使您的代码的可移植性降低。

  2. 最好使用标准化的填充模式,例如"/PKCS5Padding"(与 Java 中的 PKCS#7 填充相同)。如果您想使用当前的填充模式,您可以配置 Bouncy Castle 提供程序并指定"/ZeroBytePadding". 此填充模式不适用于以零值字节结尾的纯文本。

  3. 您将 IV 存储在与键相同的类变量中。我知道这只是测试代码,但通常需要在双方发送或建立 IV。在双方使用相同密钥的最常见方法是在密文前加上 IV。

  4. IV 的大小取决于密码。AES 始终为 16,但您可能希望使 IV 大小可配置或使用该Cipher.getBlockSize()方法。

  5. 如果您还想要真实性/完整性和防止填充预言攻击,请使用 GCM 模式(自 1.8 起可用)加密。

  6. 您应该为每次加密使用新的随机 IV,而不是只生成一次 IV。

于 2013-08-12T14:15:01.677 回答
1

确保转换没有丢失的方法是在来回转换时使用相同的字符集。

然而,创建一串加密数据对于进一步使用并不安全;它可以包含任何和所有字节序列,并且可能不适合您最初使用的任何字符集(您没有犯此错误,只是指出它)。

您还在代码中间打印 byte[] 的哈希码,而不是单个字节。

于 2013-08-12T13:26:44.523 回答