在Java加密和解密中处理IV和salt

所以我试图解密一个方法中的消息,但它不起作用,因为我需要做cipher.init(Cipher.ENCRYPT_MODE, secret)然后我尝试将new IvParameterSpec(iv)添加到cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); 。 否则,它只返回一个NullPointerException我想知道是否可以在方法中执行此操作而不是一直写入它。 我真的不能想到一个解决方案,这就是为什么我在这里。 加密工作正常但不解密。

项目运行: JRE 7

加密代码:

 public static String encrypt(String str) { try { SecureRandom random = new SecureRandom(); byte[] salt = new byte[16]; random.nextBytes(salt); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); KeySpec spec = new PBEKeySpec(str.toCharArray(), salt, 65536, 256); SecretKey tmp = factory.generateSecret(spec); SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secret); //<--- Need to do this before writing IvPerameterSpec, // But I think that it's not possible if I have it in another method. byte[] encryptedText = cipher.doFinal(str.getBytes("UTF-8")); return new String(encryptedText); } catch (Exception e) { e.printStackTrace(); } return null; } 

解密代码:

 public static String decrypt(String str) { try { SecureRandom random = new SecureRandom(); byte[] salt = new byte[16]; random.nextBytes(salt); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); KeySpec spec = new PBEKeySpec(str.toCharArray(), salt, 65536, 256); SecretKey tmp = factory.generateSecret(spec); SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); AlgorithmParameters params = cipher.getParameters(); byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV(); cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); // ^^^ Returns NullPointerException byte[] ciphertext = cipher.doFinal(str.getBytes("UTF-8")); String decryptedText = new String(cipher.doFinal(ciphertext), "UTF-8"); return new String(decryptedText); } catch (Exception e) { e.printStackTrace(); } return null; } 

例外:

 java.lang.NullPointerException at me.Sansanvi.Encryption.api.ComputerAPI.decrypt(ComputerAPI.java:149) at me.Sansanvi.Encryption.EncryptionMain.initializeFiles(EncryptionMain.java:46) at me.Sansanvi.Encryption.EncryptionMain.(EncryptionMain.java:36) at me.Sansanvi.Encryption.EncryptionMain$1.run(EncryptionMain.java:23) at java.awt.event.InvocationEvent.dispatch(Unknown Source) at java.awt.EventQueue.dispatchEventImpl(Unknown Source) at java.awt.EventQueue.access$200(Unknown Source) at java.awt.EventQueue$3.run(Unknown Source) at java.awt.EventQueue$3.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source) at java.awt.EventQueue.dispatchEvent(Unknown Source) at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.run(Unknown Source) 

我将方法更改为以下内容并且它们有效:

 private static final String ALGORITHM = "AES"; public static byte[] encrypt(byte[] str) { try { SecretKeySpec secretKey = new SecretKeySpec("MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8), ALGORITHM); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, secretKey); return cipher.doFinal(str); } catch (Exception e) { e.printStackTrace(); } return null; } public static byte[] decrypt(byte[] str) { try { SecretKeySpec secretKey = new SecretKeySpec("MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8), ALGORITHM); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, secretKey); return cipher.doFinal(str); } catch (Exception e) { e.printStackTrace(); } return null; } 

我不再有任何错误/exception,但是当我关闭我的应用程序时,我在控制台中得到了这个:

 [0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64) [0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64) [0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64) [0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64) [0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64) [0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64) [0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64) [0x7FFC837C7430] ANOMALY: use of REX.w is meaningless (default operand size is 64) 

您至少有四个问题,而您只发现其中一个问题。

  1. IV在加密期间生成并使用。 在解密期间需要使用完全相同的IV。 IV不应该是秘密的。 您只需将其与密文一起发送/存储即可。 通常,IV存储在密文的前面并在解密之前切掉。
    对于CBC模式,它只需要是不可预测的(读:随机)。 对于CTR模式,它需要是唯一的(当使用相同的密钥时)。

  2. 随机盐需要以与IV完全相同的方式处理:它不是秘密的,可以写在密文之前。

  3. String不是二进制数据的容器。 当您使用new String(encryptedText) ,您可能会丢失一些不可打印的字节,从而破坏您的密文并使明文无法恢复。 您需要使用类似Base64或Hex编码的内容将二进制数据表示为可打印文本。

  4. 如果你加密某些东西,你需要两件事:明文和密码(用于密钥派生)。 解密过程中还需要两件事:密文和密码(密钥)。 您在加密期间使用相同的字符串值。 然后,密文会以一种在解密过程中需要原始字符串值的方式进行修改。 这首先会破坏加密它的目的。 您的加密方法基本上是一个哈希函数。
    感谢wittyameta指出这一点。

因此,生成的代码看起来类似于:

 public static String encrypt(String str, String password) { try { SecureRandom random = new SecureRandom(); byte[] salt = new byte[16]; random.nextBytes(salt); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256); SecretKey tmp = factory.generateSecret(spec); SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secret); AlgorithmParameters params = cipher.getParameters(); byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV(); byte[] encryptedText = cipher.doFinal(str.getBytes("UTF-8")); // concatenate salt + iv + ciphertext ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); outputStream.write(salt); outputStream.write(iv); outputStream.write(encryptedText); // properly encode the complete ciphertext return DatatypeConverter.printBase64Binary(outputStream.toByteArray()); } catch (Exception e) { e.printStackTrace(); } return null; } public static String decrypt(String str, String password) { try { byte[] ciphertext = DatatypeConverter.parseBase64Binary(str); if (ciphertext.length < 48) { return null; } byte[] salt = Arrays.copyOfRange(ciphertext, 0, 16); byte[] iv = Arrays.copyOfRange(ciphertext, 16, 32); byte[] ct = Arrays.copyOfRange(ciphertext, 32, ciphertext.length); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256); SecretKey tmp = factory.generateSecret(spec); SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); byte[] plaintext = cipher.doFinal(ct); return new String(plaintext, "UTF-8"); } catch (Exception e) { e.printStackTrace(); } return null; } 

我已经将它用于兼容Java 7的Base64编码/解码。

您没有正确初始化密码首先需要实例(您正确地执行此操作):

 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 

然后你需要初始化它有多个init方法:

 public void init(int opmode, Key key); public void init(int opmode, Certificate certificate); public void init(int opmode, Key key, SecureRandom random); public void init(int opmode, Certificate certificate, SecureRandom random); public void init(int opmode, Key key, AlgorithmParameterSpec params); public void init(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random); public void init(int opmode, Key key, AlgorithmParameters params); public void init(int opmode, Key key, AlgorithmParameters params, SecureRandom random); 

在你的情况下,你想使用这个:

 public void init(int opmode, Key key, AlgorithmParameterSpec params); 

但是调用cipher.getParameters(); 在init之前总是返回null。 有你的NullPointerException。

此外,代码中存在更多错误,例如使用数据加密作为密钥。