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

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

问题描述

所以我试图用一种方法解密一条消息,但它不起作用,因为我需要在尝试添加 cipher.init(Cipher.ENCRYPT_MODE, secret) 之前执行>new IvParameterSpec(iv) 到 cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));.否则,它只会返回 NullPointerException 我想知道是否可以在方法中执行此操作而不是一直编写它.我真的想不出解决方案,所以这就是我来这里的原因.加密可以正常工作,但不能解密.

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.

项目运行: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.<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 four problems and you discovered only one of them.

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

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

随机salt需要和IV一样处理:它不是秘密的,可以写在密文前面.

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.

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

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.

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

If you encrypt something, you need two things: a plaintext and a password (used for key derivation). You also need to two things during decryption: a ciphertext and a password (key). You're using the same string value during encryption. The ciphertext is then mangled in such a way that you would need the original string value during decryption. And that would defeat the purpose of encrypting it in the first place. Your encryption method is basically a hash function.
Thanks to wittyameta for pointing this out.

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

So the resulting code would look similar to this:

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 编码/解码.

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

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

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