有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java当每次加密使用随机盐时,我可以避免每次加密/解密调用的密码重新初始化吗?

编辑

实际上,重新初始化密码并没有那么慢。由于迭代计数,创建密钥本身的速度很慢

此外,迭代计数被忽略,并且从未在加密本身中使用,仅在密钥生成中使用。根据选择的算法,JCE api有点误导

原帖

因为Java中的加密技术非常。。。我正在努力做一些优化。在功能方面,这个类工作得很好,我希望它可以作为AES加密使用的一个例子

在使用BouncyCastle的AES实现加密和解密数据时,我遇到了一个性能问题(我没有比较,这是我测试的唯一一个实现)。实际上,这个问题对于我决定使用的任何密码都是通用的

主要问题是:每次加密/解密调用时,我能否避免两个密码全部重新初始化?它们太贵了

为了简单起见,请记住以下代码删除了异常处理,并进行了大量简化,以保持对问题的关注。同步块用于保证线程安全

顺便说一下,欢迎对代码的任何部分进行反馈

Thx

import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class AES {

    private static final int ITERATIONS = 120000;
    private static final int SALT_SIZE_IN_BYTES = 8;
    private static final String algorithm = "PBEWithSHA256And128BitAES-CBC-BC";
    private static final byte[] KEY_SALT = "a fixed key salt".getBytes(Charset.forName("UTF-8"));

    private Cipher encryptCipher;
    private Cipher decryptCipher;
    private SecretKey key;
    private RandomGenerator randomGenerator = new RandomGenerator();

    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null)
            Security.addProvider(new BouncyCastleProvider());
    }

    public AES(String passphrase) throws Exception {
        encryptCipher = Cipher.getInstance(algorithm);
        decryptCipher = Cipher.getInstance(algorithm);
        PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), KEY_SALT, ITERATIONS);
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
        key = keyFactory.generateSecret(keySpec);
    }

    public byte[] encrypt(byte[] data) throws Exception {
        byte[] salt = randomGenerator.generateRandom(SALT_SIZE_IN_BYTES);
        PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, ITERATIONS);
        data = DataUtil.append(data, salt);

        byte[] encrypted;
        synchronized (encryptCipher) {
            // as a security constrain, it is necessary to use different salts per encryption
            // core issue: want to avoid this reinitialization to change the salt that will be used. Its quite time consuming
            encryptCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key, parameterSpec);
            encrypted = encryptCipher.doFinal(data);
        }
        return DataUtil.append(encrypted, salt);
    }

    public byte[] decrypt(byte[] data) throws Exception {
        byte[] salt = extractSaltPart(data);
        data = extractDataPart(data);

        PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, ITERATIONS);

        byte[] decrypted;

        synchronized (decryptCipher) {
            // as a security constrain, it is necessary to use different salts per encryption
            // core issue: want to avoid this reinitialization to change the salt that will be used. Its quite time consuming
            decryptCipher.init(javax.crypto.Cipher.DECRYPT_MODE, key, parameterSpec); 
            decrypted = decryptCipher.doFinal(data);
        }

        byte[] decryptedSalt = extractSaltPart(decrypted);

        if (Arrays.equals(salt, decryptedSalt))
            return extractDataPart(decrypted);
        else
            throw new IllegalArgumentException("Encrypted data is corrupted: Bad Salt");
    }

    protected byte[] extractDataPart(byte[] bytes) {
        return DataUtil.extract(bytes, 0, bytes.length - SALT_SIZE_IN_BYTES);
    }

    protected byte[] extractSaltPart(byte[] bytes) {
        return DataUtil.extract(bytes, bytes.length - SALT_SIZE_IN_BYTES, SALT_SIZE_IN_BYTES);
    }

    // main method to basic check the code execution
    public static void main(String[] args) throws Exception {
        String plainText = "some plain text, have fun!";
        String passphrase = "this is a secret";

        byte[] data = plainText.getBytes(Charset.forName("UTF-8"));

        AES cipher = new AES(passphrase);
        byte[] encrypted = cipher.encrypt(data);
        byte[] decrypted = cipher.decrypt(encrypted);

        System.out.println("expected: true, actual: " + Arrays.equals(data, decrypted));
    }
}

// Utility class
class RandomGenerator {

    private SecureRandom random = new SecureRandom();

    public RandomGenerator() {
        random = new SecureRandom();
        random.nextBoolean();
    }

    public synchronized byte[] generateRandom(int length) {
        byte[] data = new byte[length];
        random.nextBytes(data);
        return data;
    }
}

// Utility class
class DataUtil {

    public static byte[] append(byte[] data, byte[] append) {
        byte[] merged = new byte[data.length + append.length];
        System.arraycopy(data, 0, merged, 0, data.length);
        System.arraycopy(append, 0, merged, data.length, append.length);
        return merged;
    }

    public static byte[] extract(byte[] data, int start, int length) {
        if (start + length > data.length)
            throw new IllegalArgumentException("Cannot extract " + length + " bytes starting from index " + start + " from data with length " + data.length);

        byte[] extracted = new byte[length];
        System.arraycopy(data, start, extracted, 0, length);
        return extracted;
    }

}

共 (1) 个答案

  1. # 1 楼答案

    你真倒霉。如果您每次都选择一个新的salt,这意味着您每次都在使用一个新密钥进行加密/解密,这意味着您每次都需要调用init

    如果你想要更快的东西,只需在你的信息中加盐:

    byte[] salt = randomGenerator.generateRandom(SALT_SIZE_IN_BYTES);
    encryptCipher.update(salt);
    encrypted = encryptCipher.doFinal(data);
    

    这样,您每次都使用相同的键,因此无需重新初始化它。(不要使用PBE,只使用128位AES/CBC)。如果不知道您计划如何在现实世界中应用这种加密,就很难知道这是否足以满足您的需求

    p.s.迭代次数=120000次?难怪这么慢