8

编辑

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

此外,迭代计数被忽略并且从未在加密本身中使用,仅在密钥生成时使用。根据所选择的算法,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;
    }

}
4

3 回答 3

3

你运气不好。init如果您每次都选择新的盐,这意味着您每次都使用新的密钥进行加密/解密,这意味着您每次都需要调用。

如果您想要更快的速度,只需添加您的信息:

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

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

ps 迭代次数 == 120000?怪不得这么慢。

于 2012-09-21T00:14:27.573 回答
3

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

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

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

于 2012-09-25T14:30:00.130 回答
1
  1. 如果您只是在传输数据并需要在飞行中对其进行加密,请使用 TLS/SSL。它的方式更快,不会中断。

  2. 确保您对密文使用了一些身份验证。在 GCM 或 CCM 模式下使用 MAC 或更好地使用 AES。否则你的加密是不安全的。

至于你的问题:是的,它是可以解决的。只需生成一次密钥并重用它/ 。AES 可以安全地使用相同的密钥发送多条消息。所以只需派生一次密钥/密码并继续使用它。

只需确保为每条消息使用新的 IV。

于 2012-09-21T02:13:12.187 回答