在Java加密和解密中处理IV和盐 [英] Handling of IV and salt in Java encryption and decryption

查看:279
本文介绍了在Java加密和解密中处理IV和盐的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我试图用一个方法来解密一个消息,但是它不起作用,因为我需要在 cipher.init(Cipher.ENCRYPT_MODE,secret)之前我尝试添加新的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.getInstance(AES / CBC / PKCS5Padding);
cipher.init(Cipher.ENCRYPT_MODE,secret); //< ---在编写IvPerameterSpec之前需要这样做,
//但是我认为如果我用另外一种方法,这是不可能的。
byte [] encryptedText = cipher.doFinal(str.getBytes(UTF-8));

return new String(encryptedText);
} catch(Exception e){
e.printStackTrace();
}
返回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.getInstance(AES / CBC / PKCS5Padding);

AlgorithmParameters params = cipher.getParameters();
byte [] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
cipher.init(Cipher.DECRYPT_MODE,secret,new IvParameterSpec(iv));
// ^^^返回NullPointerException

byte [] ciphertext = cipher.doFinal(str.getBytes(UTF-8));
String decryptptedText = new String(cipher.doFinal(ciphertext),UTF-8);

return new String(decryptptedText);
} catch(Exception e){
e.printStackTrace();
}
返回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。< init>(EncryptionMain.java:36)
at me.Sansanvi.Encryption.EncryptionMain $ 1.run(EncryptionMain .java:23)
在java.awt.event.InvocationEvent.dispatch(未知源)
在java.awt.EventQueue.dispatchEventImpl(未知源)
在java.awt.EventQueue。 $ java.awt.EventQueue $ 3.run(Unknown Source)
.doPrivileged(Native Method)
在java.security.ProtectionDomain $ 1.doIntersectionPrivilege(Unknown Source)
在java.awt.EventQueue.dispatchEvent(U nknown Source)
在java.awt.EventDispatchThread.pumpOneEventForFilters(未知源)
在java.awt.EventDispatchThread.pumpEventsForFilter(未知源)
在java.awt.EventDispatchThread.pumpEventsForHierarchy(未知来源)
在java.awt.EventDispatchThread.pumpEvents(未知源)
在java.awt.EventDispatchThread.pumpEvents(未知源)
在java.awt.EventDispatchThread.run(未知来源)

我将方法改为以下,他们工作:

  private static final String ALGORITHM =AES; 
public static byte [] encrypt(byte [] str){
try {
SecretKeySpec secretKey = new SecretKeySpec(MZygpewJsCpRrfOr.getBytes(StandardCharsets.UTF_8),ALGORITHM);
密码密码= Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE,secretKey);

return cipher.doFinal(str);
} catch(Exception e){
e.printStackTrace();
}
返回null;
}
public static byte [] decrypt(byte [] str){
try {
SecretKeySpec secretKey = new SecretKeySpec(MZygpewJsCpRrfOr.getBytes(StandardCharsets.UTF_8),ALGORITHM );
密码密码= Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE,secretKey);

return cipher.doFinal(str);
} catch(Exception e){
e.printStackTrace();
}
返回null;
}

我没有收到任何错误/异常,但我得到了关闭我的应用程序时的控制台:

  [0x7FFC837C7430]异常:使用REX.w是无意义的(默认操作数大小为64)
[0x7FFC837C7430]异常:使用REX.w是无意义的(默认操作数大小为64)
[0x7FFC837C7430]异常:使用REX.w是无意义的(默认操作数大小是64)
[0x7FFC837C7430]异常:使用REX.w是无意义的(默认操作数大小为64)
[0x7FFC837C7430]异常:使用REX.w是无意义的(默认操作数大小为64)
[0x7FFC837C7430]异常:使用REX.w是无意义的(默认操作数大小为64)
[0x7FFC837C7430]异常:使用REX.w是无意义的(默认操作数大小为64)
[ 0x7FFC837C7430]异常:使用REX.w是无意义的(默认操作数大小为64)


解决方案

你至少有三个问题,你发现只有其中一个。


  1. IV是在 en 。在 de 加密期间需要使用完全相同的IV。 IV不应该是秘密的。您可以简单地发送/存储它与密文一起。通常,将IV存储在密文前面,并在解密之前将其切片。

    对于CBC模式,只需要不可预测(read:random)即可。对于CTR模式,它将需要是唯一的(当使用相同的键时)。


  2. 随机盐需要以与IV完全相同的方式对待:它不是秘密的,可以写在密文前面。 p>


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


所以生成的代码看起来像这样:

  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.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);

//正确编码完整的密文
return DatatypeConverter.printBase64Binary(outputStream.toByteArray());
} catch(Exception e){
e.printStackTrace();
}
返回null;
}

public static String decrypt(String str){
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(str.toCharArray(),salt,65536,256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(),AES);
密码密码= Cipher.getInstance(AES / CBC / PKCS5Padding);

cipher.init(Cipher.DECRYPT_MODE,secret,new IvParameterSpec(iv));
byte [] plaintext = cipher.doFinal(ct);

return new String(明文,UTF-8);
} catch(Exception e){
e.printStackTrace();
}
返回null;
}

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


So I'm trying to decrypt a message in a method but it doesn't work because I need to do cipher.init(Cipher.ENCRYPT_MODE, secret) before I try to add new IvParameterSpec(iv) to cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));. Otherwise, it just returns a NullPointerException I'm wondering if it's possible to do this in a method rather than writing it all the time. I can't really think of a solution so that's why I'm here. Encrypt works fine but not Decrypt.

Project Running: JRE 7

Encrypt Code:

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;
}

Decrypt Code:

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;
}

Exception:

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.<init>(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)

I changed the methods to the following and they work:

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;
}

I don't get any errors/exceptions anymore but I get this in the console when I close my application:

[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)

解决方案

You have at least three problems and you discovered only one of them.

  1. The IV is generated during encryption and used. The exact same IV needs to be used during decryption. The IV is not supposed to be secret. You can simply send/store it along with the ciphertext. Usually, the IV is stored in front of the ciphertext and sliced off before decryption.
    For CBC mode, it just needs to be unpredictable (read: random). For CTR mode, it would need to be unique (when the same key is used).

  2. The random salt needs to be treated in the exact same way as the IV: it is not secret and can be written in front of the ciphertext.

  3. String is not a container for binary data. When you use new String(encryptedText), you probably lose some unprintable bytes which breaks your ciphertext and makes the plaintext unrecoverable. You need to use something like Base64 or Hex encoding to represent binary data as printable text.

So the resulting code would look similar to this:

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);
        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) {
    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(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.DECRYPT_MODE, secret, new IvParameterSpec(iv));
        byte[] plaintext = cipher.doFinal(ct);

        return new String(plaintext, "UTF-8");
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

I've used this for Java 7 compatible Base64 encoding/decoding.

这篇关于在Java加密和解密中处理IV和盐的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆