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

Bru*_*ado 8 java performance cryptography aes salt

编辑

实际上重新初始化密码并不是那么慢.由于迭代计数,创建密钥本身很慢.

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

原帖

由于Java中的加密技术非常...加密,我正在努力做一些优化.在功能方面,这个类非常有效,我希望它可以作为AES加密使用的一个例子

使用BouncyCastle的AES实现加密和解密数据时出现性能问题(我不比较,这是我测试过的唯一一个实现).实际上这个问题对于我决定使用的任何密码都是通用的.

主要问题是:我可以避免两个密码完全重新初始化每个加密/解密调用吗?它们太贵了

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

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

谢谢

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

}
Run Code Online (Sandbox Code Playgroud)

Bru*_*ado 3

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

此外,迭代计数被忽略,并且从未在加密本身中使用,仅在密钥生成中使用。JCE API 具有一定的误导性,具体取决于所选算法

关于盐:在纯消息中添加盐是完全没有必要的。我真正应该用来在每次加密中实现随机性的是使用随机初始化向量,它可以像盐一样在加密后附加或添加到密文中。