将byte []解密回String而不丢失数据

时间:2013-08-12 13:20:35

标签: java security encryption cryptography

我已经编写了一个小应用程序来使用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被双向使用,从而涵盖了这一点。

如果有人看到我的代码中有任何其他红旗以及我是如何做到这一点的,我会接受批评/建议。

非常感谢!

3 个答案:

答案 0 :(得分:5)

您在toString()上呼叫byte[],这绝不是一个好主意。基本上它没有给你任何有用的信息。

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

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

答案 1 :(得分:2)

你问过其他的红旗,所以我会给你一些关于加密的指示:

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

  2. 最好使用标准化的填充模式,例如"/PKCS5Padding"(与Java中的PKCS#7填充相同)。如果要使用当前填充模式,可以配置Bouncy Castle提供程序并指定"/ZeroBytePadding"。对于以零值字节结尾的明文,此填充模式无法正常工作。

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

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

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

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

答案 2 :(得分:1)

确保转换无损失的方法是在转换时使用相同的Charset。

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

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