8

編集

実際、暗号の再初期化はそれほど遅くはありません。キー自体の作成は、反復回数のために時間がかかります。

また、反復回数は無視され、暗号化自体では使用されず、鍵の生成時にのみ使用されます。選択したアルゴリズムによっては、JCE API が誤解を招く可能性があります。

元の投稿

Java の暗号化は非常に... 暗号的であるため、いくつかの最適化を行うのに苦労しています。機能面では、このクラスは非常にうまく機能し、AES 暗号化の使用例として役立つことを願っています

BouncyCastle の AES 実装を使用してデータを暗号化および復号化するときにパフォーマンスの問題があります (比較していません。テストした実装は 1 つだけです)。実際、この問題は、私が使用することに決めたどの暗号にも一般的です。

主な問題は次のとおりです。暗号化/復号化呼び出しごとに 2 つの暗号全体の再初期化を回避できますか? 彼らは高すぎる

簡単にするために、次のコードでは例外処理が削除されており、問題に焦点を当てるために多くの単純化が行われていることに注意してください。同期されたブロックは、スレッドの安全性を保証するためにあります

ところで、コードのどの部分についてのフィードバックも大歓迎です

どうも

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

実際、暗号の再初期化はそれほど遅くはありません。キー自体の作成は、反復回数のために時間がかかります。

また、反復回数は無視され、暗号化自体では使用されず、鍵の生成時にのみ使用されます。JCE API は、選択したアルゴリズムによっては誤解を招く可能性があります

ソルトについて: プレーン メッセージにソルトを追加する必要はまったくありません。各暗号化でランダム性を実現するために実際に使用する必要があるのは、ランダムな初期化ベクトルを使用することです。これは、ソルトと同様に、暗号化後に暗号文に追加または先頭に追加できます。

于 2012-09-25T14:30:00.130 に答える
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 に答える
1
  1. データを送信するだけで、転送中に暗号化する必要がある場合は、TLS/SSL を使用してください。その方法はより速く、壊れません。

  2. 暗号文で何らかの認証を使用していることを確認してください。MAC を使用するか、GCM または CCM モードで AES を使用することをお勧めします。そうしないと、暗号化は安全ではありません。

あなたの質問について:はい、修正可能です。キーを一度生成して再利用するだけです。AES は、同じキーで複数のメッセージを送信するために安全に使用できます。したがって、キー/暗号を一度派生させて、それを使い続けてください。

メッセージごとに新しい IV を使用するようにしてください。

于 2012-09-21T02:13:12.187 に答える