Java 将字节[]解密回字符串而不丢失数据
我已经编写了一个使用AES加密和解密字符串的小应用程序。代码如下:Java 将字节[]解密回字符串而不丢失数据,java,security,encryption,cryptography,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.NoSuchAlgorithmExcep
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的两种使用方式涵盖了这一点
如果有人看到我的代码中有任何其他危险信号,以及我是如何做到这一点的,我愿意接受批评/建议
非常感谢 您正在调用字节[]上的toString,这绝不是一个好主意。基本上它不会给你任何有用的信息
如果您想将任意二进制数据转换为字符串,我建议使用十六进制或base64,这两种格式在其他地方都有介绍。没有迹象表明您在加密/解密过程中丢失了任何信息-问题在于您显示的加密数据。只要你不试图把它当作简单的编码文本数据,因为它不是你应该很好。特别是,您的代码已经将UTF-8指定为从原始文本到未加密二进制数据的转换,反之亦然,所以这是安全的
如果不需要将字节数组转换为字符串,那么最简单的方法是首先避免这样做。例如,您可以非常简单地将其写入一个仍然以二进制形式存在的文件,然后稍后将其加载回字节数组。您调用的是在字节[]上字符串,这绝不是一个好主意。基本上它不会给你任何有用的信息
如果您想将任意二进制数据转换为字符串,我建议使用十六进制或base64,这两种格式在其他地方都有介绍。没有迹象表明您在加密/解密过程中丢失了任何信息-问题在于您显示的加密数据。只要你不试图把它当作简单的编码文本数据,因为它不是你应该很好。特别是,您的代码已经将UTF-8指定为从原始文本到未加密二进制数据的转换,反之亦然,所以这是安全的
如果不需要将字节数组转换为字符串,那么最简单的方法是首先避免这样做。例如,您可以非常简单地将其写入仍为二进制格式的文件,然后稍后将其加载回字节数组。确保转换不会丢失的方法是在来回转换时使用与您相同的字符集 然而,创建一个加密数据字符串对于进一步使用是不安全的;它可以包含任何和所有字节序列,并且可能不适合您最初使用的任何字符集。您没有犯这个错误,只是指出了它
您还在代码中间打印字节[]的哈希代码,而不是单个字节。确保转换不会丢失的方法是在来回转换时使用相同的字符集 然而,创建一个加密数据字符串对于进一步使用是不安全的;它可以包含任何和所有字节序列,并且可能不适合您最初使用的任何字符集。您没有犯这个错误,只是指出了它
您还将在代码中间打印字节[]的哈希代码,而不是单个字节。您要求其他红色标志,因此我将给您一些有关加密的提示: 通常,在使用算法名称时,不必提供提供者名称。指定提供程序会降低代码的可移植性 最好使用标准化的填充模式,例如/pkcs5p,在Java中添加与PKCS7填充相同的内容。如果要使用当前填充模式,可以配置Bouncy Castle提供程序并指定/ZeroBytePadding。对于以零值字节结尾的纯文本,此填充模式无法正常工作 将IV存储在与键相同的类变量中。我知道这只是测试代码,但通常需要在两侧发送或建立IV。在两侧使用同一密钥的最常见方法是在密文前面加上IV IV的大小取决于密码。AES的值始终为16,但您可能希望配置IV大小或使用Cipher.getBlockSize方法 如果您还需要真实性/完整性和针对oracle攻击的保护,请使用1.8加密后可用的GCM模式 您应该为每个加密使用一个新的、随机的IV,而不是只生成一次IV
您要求提供其他危险信号,因此我将给您一些有关加密的提示: 通常,在使用算法名称时,不必提供提供者名称。指定提供程序会降低代码的可移植性 最好使用标准化的填充模式,例如/pkcs5p,在Java中添加与PKCS7填充相同的内容。如果你愿意 要使用当前填充模式,可以配置Bouncy Castle提供程序并指定/ZeroBytePadding。对于以零值字节结尾的纯文本,此填充模式无法正常工作 将IV存储在与键相同的类变量中。我知道这只是测试代码,但通常需要在两侧发送或建立IV。在两侧使用同一密钥的最常见方法是在密文前面加上IV IV的大小取决于密码。AES的值始终为16,但您可能希望配置IV大小或使用Cipher.getBlockSize方法 如果您还需要真实性/完整性和针对oracle攻击的保护,请使用1.8加密后可用的GCM模式 您应该为每个加密使用一个新的、随机的IV,而不是只生成一次IV
回答得好,但我认为第一段是多余的/不正确的,或者充其量是令人困惑的。谢谢你的回答!啊,我没有意识到这是我正在显示的字节[]的哈希代码。谢谢你指出这一点。回答得好,但我认为第一段是多余的/不正确的,或者充其量是令人困惑的。谢谢你的回答!啊,我没有意识到这是我正在显示的字节[]的哈希代码。感谢您指出out.GCM仅在Java 8之后才可用。静态IV是一个危险信号。谢谢这些,非常好的观点。除了第5点之外,我已经将所有这些添加到了我的代码中。我只将IV的生成移到encrypt方法中,并将其作为ciper.init方法中的附加参数传递。然后我使用cipher.getParameters在decrypt方法中检索它@ntoskrnl,这个更改是否修复了红旗?谢谢@ntoskrnl,我会将其更改为1.8。静电IV在哪里?GenerateInitializationVector方法在我看来很随机…@owlstead很公平,这可能只是一个测试。主要问题是IV重用。确保同一个IV在生产环境中不用于多个加密操作。@ntoskrnl啊,是的,现在我明白了,它是随机的,很像,我甚至没有注意到:GCM只在Java 8之后才可用。静态IV是一个危险信号。谢谢这些,非常好的观点。除了第5点之外,我已经将所有这些添加到了我的代码中。我只将IV的生成移到encrypt方法中,并将其作为ciper.init方法中的附加参数传递。然后我使用cipher.getParameters在decrypt方法中检索它@ntoskrnl,这个更改是否修复了红旗?谢谢@ntoskrnl,我会将其更改为1.8。静电IV在哪里?GenerateInitializationVector方法在我看来很随机…@owlstead很公平,这可能只是一个测试。主要问题是IV重用。确保同一个IV在生产环境中不用于多个加密操作。@ntoskrnl啊,是的,现在我明白了,它是随机的,就像,我甚至没有注意到:谢谢你的回答!不幸的是,我必须有一个编码文本数据的表示,这是一个糟糕的设计,我被告知必须保留,并且散列不够强大。某些字符串需要进行编码和存储以供以后使用,然后需要进一步解码。我真的不觉得有必要,但是嘿。将原始字节[]数据再次编码为hex/base64并存储此表示,而不是原始二进制数据,这会有用吗?或者这会转移问题吗?您有一个编码文本数据的表示:它是字节数组。如果愿意,您可以使用base64将其转换为字符串,但不能只调用字节数组上的toString。谢谢您的回答!不幸的是,我必须有一个编码文本数据的表示,这是一个糟糕的设计,我被告知必须保留,并且散列不够强大。某些字符串需要进行编码和存储以供以后使用,然后需要进一步解码。我真的不觉得有必要,但是嘿。将原始字节[]数据再次编码为hex/base64并存储此表示,而不是原始二进制数据,这会有用吗?或者这会转移问题吗?您有一个编码文本数据的表示:它是字节数组。如果愿意,可以使用base64将其转换为字符串,但不能只调用字节数组上的toString。