Javaでの安全なAESキーの生成
1. 概要
この記事では、AESまたは暗号一般のキーの目的について詳しく説明します。 ベストプラクティスを生成する際に留意すべきベストプラクティスについて説明します。
最後に、生成するさまざまな方法を見て、ガイドラインと比較検討します。
2. AES
Advanced Encryption Standard(AES)は、米国国立標準技術研究所(NIST)によって2001年に公開されたData Encryption Standard(DES)の後継です。 対称ブロック暗号として分類されます。
対称暗号は、暗号化と復号化の両方に同じ秘密鍵を使用します。 ブロック暗号とは、入力平文の128ビットブロックで機能することを意味します。
2.1. AESバリアント
キーサイズに基づいて、AESはAES-128(128ビット)、AES-192(192ビット)、およびAES-256(256ビット)の3つのバリアントをサポートします。 キーサイズを大きくすると、可能なキーの数が増えるため、暗号化の強度が高まります。 その結果、アルゴリズムの実行中に実行されるラウンドの数も増加するため、計算が必要になります。
キーサイズ | ブロックサイズ | #ラウンド |
---|---|---|
128 | 128 | 10 |
192 | 128 | 12 |
256 | 128 | 14 |
2.2. AESはどの程度安全ですか?
AESアルゴリズムは公開情報です。秘密であり、正常に解読するには既知である必要があるのはAESキーです。 つまり、AESキーを解読することになります。 キーが安全に保存されていると仮定すると、攻撃者はキーを推測しようとする必要があります。
キーを推測する際にブルートフォースアプローチがどのように機能するかを見てみましょう。
AES-128キーは128ビットです。つまり、 2^128の可能な値があります。 これを検索するには、膨大で実行不可能な時間とお金が必要です。 したがって、AESは、ブルートフォースアプローチでは実質的に破壊されません。
いくつかの非ブルートフォースアプローチがありましたが、これらは可能なキールックアップスペースを数ビット減らすことしかできませんでした。
つまり、キーに関する知識がまったくない場合、AESを破ることは事実上不可能です。
3. 良い鍵の特性
次に、AESキーを生成する際に従うべき重要なガイドラインのいくつかを見てみましょう。
3.1. キーサイズ
AESは3つのキーサイズをサポートしているため、ユースケースに適したキーサイズを選択する必要があります。 AES-128は、商用アプリケーションで最も一般的な選択肢です。 セキュリティと速度のバランスを提供します。 各国政府は通常、AES-192およびAES-256を使用して最大限のセキュリティを確保します。 さらに高いレベルのセキュリティが必要な場合は、AES-256を使用できます。
量子コンピューターは、大きなキースペースに必要な計算を減らすことができるという脅威をもたらします。 したがって、AES-256キーを使用する方が将来性がありますが、現時点では、商用アプリケーションの脅威アクターの手の届かないところにあります。
3.2. エントロピ
エントロピーとは、キーのランダム性を指します。 生成されたキーが十分にランダムではなく、たとえば、時間依存、マシン依存、または辞書の単語と何らかの相関関係がある場合、それは脆弱になります。 攻撃者はキー検索スペースを絞り込み、AESの強みを奪う可能性があります。 したがって、キーが本当にランダムであることが最も重要です。
4. AESキーの生成
ここで、AESキーを生成するためのガイドラインを用意して、それらを生成するためのさまざまなアプローチを見てみましょう。
すべてのコードスニペットについて、暗号を次のように定義します。
private static final String CIPHER = "AES";
4.1. ランダム
JavaのRandomクラスを使用して、キーを生成してみましょう。
private static Key getRandomKey(String cipher, int keySize) {
byte[] randomKeyBytes = new byte[keySize / 8];
Random random = new Random();
random.nextBytes(randomKeyBytes);
return new SecretKeySpec(randomKeyBytes, cipher);
}
目的のキーサイズのバイト配列を作成し、 random.nextBytes()から取得したランダムバイトで埋めます。 次に、ランダムバイト配列を使用してSecretKeySpecを作成します。
Java Random クラスは、 Pseudo-Random Number Generator (PRNG)であり、 Deterministic Random Number Generator (DRNG)とも呼ばれます。 これは、それが本当にランダムではないことを意味します。 PRNGのランダム番号のシーケンスは、そのシードに基づいて完全に決定できます。 Javaは、暗号化アプリケーションにRandomを使用することを推奨していません。
そうは言っても、キーの生成にランダムを使用しないでください。
4.2. SecureRandom
次に、Javaの SecureRandom クラスを使用して、キーを生成します。
private static Key getSecureRandomKey(String cipher, int keySize) {
byte[] secureRandomKeyBytes = new byte[keySize / 8];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(secureRandomKeyBytes);
return new SecretKeySpec(secureRandomKeyBytes, cipher);
}
前の例と同様に、目的のキーサイズのバイト配列をインスタンス化します。 ここで、 Randomを使用する代わりに、 SecureRandom を使用して、バイト配列のランダムバイトを生成します。 SecureRandomは、暗号化アプリケーションのランダム番号を生成するためにJavaによって推奨されています。 FIPS 140-2、暗号化モジュールのセキュリティ要件に最低限準拠しています。
明らかに、Javaでは、SecureRandomはランダム性を取得するためのデファクトスタンダードです。 しかし、それはキーを生成するための最良の方法ですか? 次のアプローチに移りましょう。
4.3. KeyGenerator
次に、KeyGeneratorクラスを使用してキーを生成しましょう。
private static Key getKeyFromKeyGenerator(String cipher, int keySize) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance(cipher);
keyGenerator.init(keySize);
return keyGenerator.generateKey();
}
使用している暗号のKeyGeneratorのインスタンスを取得します。 次に、 keyGeneratorオブジェクトを目的のkeySizeで初期化します。最後に、generateKeyメソッドを呼び出して秘密鍵を生成します。 では、ランダムおよび SecureRandom アプローチとはどう違うのですか?
強調する価値のある2つの重要な違いがあります。
1つは、RandomアプローチもSecureRandomアプローチも、暗号仕様に従って適切なサイズのキーを生成しているかどうかを判断できないことです。 キーがサポートされていないサイズである場合に例外が発生するのは、暗号化を行う場合のみです。
無効なkeySizeでSecureRandomを使用すると、暗号化のために暗号を初期化するときに例外がスローされます。
encrypt(plainText, getSecureRandomKey(CIPHER, 111));
java.security.InvalidKeyException: Invalid AES key length: 13 bytes
at java.base/com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:90)
at java.base/com.sun.crypto.provider.GaloisCounterMode.init(GaloisCounterMode.java:321)
at java.base/com.sun.crypto.provider.CipherCore.init(CipherCore.java:592)
at java.base/com.sun.crypto.provider.CipherCore.init(CipherCore.java:470)
at java.base/com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:322)
at java.base/javax.crypto.Cipher.implInit(Cipher.java:867)
at java.base/javax.crypto.Cipher.chooseProvider(Cipher.java:929)
at java.base/javax.crypto.Cipher.init(Cipher.java:1299)
at java.base/javax.crypto.Cipher.init(Cipher.java:1236)
at com.baeldung.secretkey.Main.encrypt(Main.java:59)
at com.baeldung.secretkey.Main.main(Main.java:51)
一方、 KeyGenerator の使用は、キー生成自体の実行中に失敗するため、より適切に処理できます。
encrypt(plainText, getKeyFromKeyGenerator(CIPHER, 111));
java.security.InvalidParameterException: Wrong keysize: must be equal to 128, 192 or 256
at java.base/com.sun.crypto.provider.AESKeyGenerator.engineInit(AESKeyGenerator.java:93)
at java.base/javax.crypto.KeyGenerator.init(KeyGenerator.java:539)
at java.base/javax.crypto.KeyGenerator.init(KeyGenerator.java:516)
at com.baeldung.secretkey.Main.getKeyFromKeyGenerator(Main.java:89)
at com.baeldung.secretkey.Main.main(Main.java:58)
もう1つの重要な違いは、SecureRandomのデフォルトの使用法です。 KeyGenerator クラスは、Javaの暗号パッケージ java x.crypto の一部であり、SecureRandomをランダムに使用できるようにします。 KeyGeneratorクラスのinitメソッドの定義を確認できます。
public final void init(int keysize) {
init(keysize, JCAUtil.getSecureRandom());
}
したがって、練習として KeyGenerator を使用すると、キー生成にRandomクラスオブジェクトを使用しないようになります。
4.4. パスワードベースのキー
これまで、ランダムであまり人間に優しいバイト配列からキーを生成してきました。 パスワードベースのキー(PBK)は、人間が読み取れるパスワードに基づいてSecretKeyを生成する機能を提供します。
private static Key getPasswordBasedKey(String cipher, int keySize, char[] password) throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] salt = new byte[100];
SecureRandom random = new SecureRandom();
random.nextBytes(salt);
PBEKeySpec pbeKeySpec = new PBEKeySpec(password, salt, 1000, keySize);
SecretKey pbeKey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(pbeKeySpec);
return new SecretKeySpec(pbeKey.getEncoded(), cipher);
}
ここではかなりのことが起こっています。 分解してみましょう。
人間が読めるパスワードから始めます。これは秘密であり、保護する必要があります。 最小長8文字、特殊文字の使用、大文字と小文字の組み合わせ、数字など、パスワードのガイドラインに従う必要があります。 さらに、 OWASPガイドラインは、すでに公開されているパスワードと照合することを提案しています。
ユーザーフレンドリーなパスワードには十分なエントロピーがありません。 したがって、推測を困難にするために、ソルトと呼ばれるランダムに生成されたバイトを追加します。ソルトの最小長は128ビットである必要があります。 SecureRandomを使用してソルトを生成しました。 ソルトは秘密ではなく、プレーンテキストとして保存されます。 各パスワードとペアでソルトを生成し、同じソルトをグローバルに使用しないようにする必要があります。 これにより、 Rainbow Table 攻撃から保護されます。この攻撃では、事前に計算されたハッシュテーブルからのルックアップを使用してパスワードを解読します。
反復回数は、秘密の生成アルゴリズムが変換関数を適用する回数です。 可能な限り大きくする必要があります。 推奨される最小反復回数は1,000です。 反復回数が多いほど、攻撃者は複雑さを増し、考えられるすべてのパスワードに対してブルートフォースチェックを実行します。
キーサイズは前に説明したものと同じで、AESの場合は128、192、または256になります。
上記の4つの要素すべてをPBEKeySpecオブジェクトにラップしました。 次に、 SecretKeyFactory を使用して、PBKDF2WithHmacSHA256アルゴリズムのインスタンスを取得してキーを生成します。
最後に、PBEKeySpecを使用してgenerateSecretを呼び出すと、人間が読み取れるパスワードに基づいてSecretKeyが生成されます。
5. 結論
キーを生成するための2つの主要なベースがあります。 ランダムキーまたは人間が読み取れるパスワードに基づくキーの場合があります。 ランダムキーを生成するための3つのアプローチについて説明しました。 その中で、 KeyGenerator は真のランダム性を提供し、チェックとバランスも提供します。 したがって、KeyGeneratorの方が優れたオプションです。
人間が読めるパスワードに基づくキーの場合、 SecretKeyFactory を、SecureRandomと高い反復回数を使用して生成されたソルトとともに使用できます。
いつものように、完全なコードはGitHubでから入手できます。