パスワードベースの暗号化アルゴリズムを実装しようとしていますが、この例外が発生します:
javax.crypto.BadPaddingException:最終ブロックが適切に埋め込まれていない
何が問題なのでしょうか? (私はJavaが初めてです。)
ここに私のコードがあります:
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.
}
}
}
間違ったキーでPKCS5でパディングされたデータを復号化してからパディングを解除しようとすると(Cipherクラスによって自動的に行われます)、BadPaddingExceptionが発生する可能性が高くなります(おそらく255/256をわずかに下回る99.61%程度) )、パディングはパディング解除時に検証される特別な構造を持ち、非常に少ないキーで有効なパディングが生成されるためです。
したがって、この例外が発生した場合は、キャッチして「間違ったキー」として扱います。
これは、間違ったパスワードを指定した場合にも発生する可能性があります。このパスワードは、キーストアからキーを取得するために使用されるか、キー生成関数を使用してキーに変換されます。
もちろん、転送中にデータが破損した場合にも、不適切なパディングが発生する可能性があります。
そうは言っても、あなたのスキームにはいくつかのセキュリティ上の注意があります。
パスワードベースの暗号化では、KeyGeneratorでSecureRandomを使用する代わりに、SecretKeyFactoryとPBEKeySpecを使用する必要があります。その理由は、SecureRandomがJava実装ごとに異なるアルゴリズムになり、異なるキーを提供できるためです。 SecretKeyFactoryは、定義された方法(および適切なアルゴリズムを選択した場合に安全であると見なされる方法)で鍵の導出を行います。
ECBモードを使用しないでください。各ブロックを個別に暗号化します。つまり、同一のプレーンテキストブロックも常に同一の暗号文ブロックを提供します。
CBC(暗号ブロックチェーン)やCTR(カウンター)のような安全な 動作モード を使用することをお勧めします。または、GCM(Galois-Counterモード)やCCM(CBC-MACを使用したカウンター)などの認証も含むモードを使用します。次のポイントを参照してください。
通常、機密性だけでなく、認証も必要とするため、メッセージが改ざんされないようにします。 (これにより、暗号に対する選択された暗号文攻撃も防止されます。つまり、機密性に役立ちます。)したがって、メッセージにMAC(メッセージ認証コード)を追加するか、認証を含む暗号モードを使用します(前のポイントを参照)。
DESの有効なキーサイズはわずか56ビットです。このキースペースは非常に小さく、専用の攻撃者によって数時間でブルートフォースされる可能性があります。パスワードでキーを生成すると、さらに高速になります。また、DESのブロックサイズは64ビットのみであるため、チェーンモードにさらに弱点があります。代わりに、128ビットのブロックサイズと128ビットのキーサイズ(標準バリアントの場合)を備えたAESのような最新のアルゴリズムを使用してください。
使用している暗号化アルゴリズムによっては、バイト配列の長さがブロックサイズの倍数になるように、バイト配列を暗号化する前に最後にパディングバイトを追加する必要がある場合があります。
具体的には、選択したパディングスキーマはPKCS5です。これについては、以下で説明します。 http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_CJ_ SYM__PAD.html
(暗号化しようとすると問題があると思います)
Cipherオブジェクトをインスタンス化するときに、埋め込みスキーマを選択できます。サポートされる値は、使用しているセキュリティプロバイダーによって異なります。
ところで、パスワードを暗号化するために対称暗号化メカニズムを使用してもよろしいですか?一方向のハッシュが良くないでしょうか?本当にパスワードを復号化する必要がある場合、DESは非常に弱いソリューションですが、対称アルゴリズムを維持する必要がある場合は、AESのような強力なものを使用することに興味があります。
この問題は、オペレーティングシステム、JREの実装に関するさまざまなプラットフォームに起因するものです。
new SecureRandom(key.getBytes())
windowsでは同じ値を取得しますが、Linuxでは異なります。だから、Linuxではに変更する必要があります
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes());
kgen.init(128, secureRandom);
「SHA1PRNG」は使用されるアルゴリズムです。アルゴリズムの詳細については、 here を参照してください。