パスワードベースの暗号化アルゴリズムを実装しようとしていますが、次の例外が発生します:
javax.crypto.BadPaddingException: 指定された最終ブロックが適切にパディングされていません
何が問題なのでしょうか?
これが私のコードです:
public class PasswordCrypter {
private Key key;
public PasswordCrypter(String password) {
try{
KeyGenerator generator;
generator = KeyGenerator.getInstance("DES");
SecureRandom sec = new SecureRandom(password.getBytes());
generator.init(sec);
key = generator.generateKey();
} catch (Exception e) {
e.printStackTrace();
}
}
public byte[] encrypt(byte[] array) throws CrypterException {
try{
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(array);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public byte[] decrypt(byte[] array) throws CrypterException{
try{
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(array);
} catch(Exception e ){
e.printStackTrace();
}
return null;
}
}
(JUnit テスト)
public class PasswordCrypterTest {
private static final byte[] MESSAGE = "Alpacas are awesome!".getBytes();
private PasswordCrypter[] passwordCrypters;
private byte[][] encryptedMessages;
@Before
public void setUp() {
passwordCrypters = new PasswordCrypter[] {
new PasswordCrypter("passwd"),
new PasswordCrypter("passwd"),
new PasswordCrypter("otherPasswd")
};
encryptedMessages = new byte[passwordCrypters.length][];
for (int i = 0; i < passwordCrypters.length; i++) {
encryptedMessages[i] = passwordCrypters[i].encrypt(MESSAGE);
}
}
@Test
public void testEncrypt() {
for (byte[] encryptedMessage : encryptedMessages) {
assertFalse(Arrays.equals(MESSAGE, encryptedMessage));
}
assertFalse(Arrays.equals(encryptedMessages[0], encryptedMessages[2]));
assertFalse(Arrays.equals(encryptedMessages[1], encryptedMessages[2]));
}
@Test
public void testDecrypt() {
for (int i = 0; i < passwordCrypters.length; i++) {
assertArrayEquals(MESSAGE, passwordCrypters[i].decrypt(encryptedMessages[i]));
}
assertArrayEquals(MESSAGE, passwordCrypters[0].decrypt(encryptedMessages[1]));
assertArrayEquals(MESSAGE, passwordCrypters[1].decrypt(encryptedMessages[0]));
try {
assertFalse(Arrays.equals(MESSAGE, passwordCrypters[0].decrypt(encryptedMessages[2])));
} catch (CrypterException e) {
// Anything goes as long as the above statement is not true.
}
try {
assertFalse(Arrays.equals(MESSAGE, passwordCrypters[2].decrypt(encryptedMessages[1])));
} catch (CrypterException e) {
// Anything goes as long as the above statement is not true.
}
}
}
ベストアンサー1
PKCS5 でパディングされたデータを間違ったキーで復号化してパディング解除しようとすると (これは Cipher クラスによって自動的に実行されます)、パディングにはパディング解除中に検証される特殊な構造があり、有効なパディングを生成するキーはほとんどないため、BadPaddingException (おそらく 255/256 よりわずかに小さい、約 99.61%) が発生する可能性が高くなります。
したがって、この例外が発生した場合は、それをキャッチして「間違ったキー」として扱います。
また、間違ったパスワードを入力し、そのパスワードがキーストアからキーを取得するために使用されたり、キー生成関数を使用してキーに変換されたりした場合にも、この現象が発生することがあります。
もちろん、転送中にデータが破損した場合にも、不正なパディングが発生する可能性があります。
そうは言っても、あなたの計画についてはセキュリティに関する注意点がいくつかあります。
パスワードベースの暗号化では、SecureRandom を KeyGenerator とともに使用するのではなく、SecretKeyFactory と PBEKeySpec を使用する必要があります。その理由は、SecureRandom は Java 実装ごとに異なるアルゴリズムになる可能性があり、異なるキーが生成される可能性があるためです。SecretKeyFactory は、定義された方法 (適切なアルゴリズムを選択した場合は安全であるとみなされる方法) でキー派生を実行します。
ECB モードは使用しないでください。各ブロックを個別に暗号化するため、同一のプレーン テキスト ブロックからは、常に同一の暗号文ブロックも生成されます。
できれば安全な動作モードCBC (暗号ブロック チェーン) や CTR (カウンター) などの認証モードを使用します。または、GCM (ガロア カウンター モード) や CCM (CBC-MAC 付きカウンター) などの認証も含まれるモードを使用します (次のポイントを参照)。
通常、機密性だけでなく、メッセージが改ざんされていないことを確認する認証も必要です。(これにより、暗号に対する選択暗号文攻撃も防止され、機密性が向上します。) したがって、メッセージに MAC (メッセージ認証コード) を追加するか、認証を含む暗号モードを使用します (前のポイントを参照)。
DES の有効なキー サイズは 56 ビットのみです。このキー スペースは非常に小さいため、熱心な攻撃者であれば数時間でブルート フォース攻撃を仕掛けることができます。パスワードでキーを生成すると、さらに高速になります。また、DES のブロック サイズは 64 ビットしかないため、連鎖モードにさらに弱点が加わります。代わりに、ブロック サイズが 128 ビット、キー サイズが 128 ビットの AES などの最新のアルゴリズムを使用してください (最も一般的なバリアントでは、196 と 256 のバリアントも存在します)。