最終ブロックが適切にパディングされていない場合


115

パスワードベースの暗号化アルゴリズムを実装しようとしていますが、次の例外が発生します。

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.
        }
    }
}

回答:


197

PKCS5でパディングされたデータを間違ったキーで復号化してからパディングを解除しようとすると(Cipherクラスによって自動的に行われます)、BadPaddingExceptionが発生する可能性があります(おそらく255/256よりわずかに少なく、約99.61%)。 )、パディングには特別な構造があり、パディング解除中に検証され、有効なパディングを生成するキーはほとんどありません。

したがって、この例外が発生した場合は、それをキャッチして「間違ったキー」として扱います。

これは、間違ったパスワードを指定した場合にも発生する可能性があります。このパスワードは、キーストアからキーを取得するために使用されるか、キー生成関数を使用してキーに変換されます。

もちろん、データがトランスポートで破損している場合も、パディングが不良になる可能性があります。

そうは言っても、あなたの計画についていくつかのセキュリティの発言があります:

  • パスワードベースの暗号化では、KeyRenderomとKeyGeneratorを使用する代わりに、SecretKeyFactoryとPBEKeySpecを使用する必要があります。その理由は、SecureRandomがJava実装ごとに異なるアルゴリズムであり、異なるキーを与える可能性があるためです。SecretKeyFactoryは、定義された方法(および適切なアルゴリズムを選択した場合に安全であると見なされる方法)で鍵を導出します。

  • ECBモードを使用しないでください。各ブロックを個別に暗号化します。つまり、同一のプレーンテキストブロックは、常に同一の暗号文ブロックも提供します。

    CBC(Cipher block chaining)やCTR(Counter)のような安全な動作モードを使用することをお勧めします。または、GCM(Galois-Counterモード)またはCCM(CBC-MACを備えたカウンター)などの認証も含むモードを使用します。次のポイントを参照してください。

  • 通常、機密性だけでなく認証も必要ありません。これにより、メッセージが改ざんされないようになります。(これにより、暗号に対する選択された暗号文攻撃も防止されます。つまり、機密性が向上します。)したがって、メッセージにMAC(メッセージ認証コード)を追加するか、認証を含む暗号モードを使用します(前のポイントを参照)。

  • DESの有効キーサイズはわずか56ビットです。このキースペースは非常に小さく、専用の攻撃者によって数時間でブルートフォース攻撃を受ける可能性があります。パスワードでキーを生成すると、さらに速くなります。また、DESのブロックサイズは64ビットしかないため、連鎖モードにいくつかの弱点があります。代わりに、ブロックサイズが128ビット、キーサイズが128ビットのAESなどの最新のアルゴリズムを使用します(標準バリアントの場合)。


1
確認したいだけです。私は暗号化に不慣れで、これが私のシナリオです。AES暗号化を使用しています。私の暗号化/復号化関数では、暗号化キーを使用しています。復号化で間違った暗号化キーを使用し、これを取得しましたjavax.crypto.BadPaddingException: Given final block not properly padded。これを間違ったキーとして扱うべきですか?
ケニッキー2015

明確にするために、これは、.p12ファイルなどのキーストアファイルに間違ったパスワードを指定した場合にも発生する可能性があります。
Warren Dew

2
@WarrenDew「キーストアファイルの間違ったパスワード」は「間違ったキー」の特別なケースです。
–PaŭloEbermann、2015

@kenicky申し訳ありませんが、私は今あなたのコメントを見ました...はい、間違ったキーはほとんどいつもこの影響を引き起こします (もちろん、破損したデータは別の可能性である。)
パウロEbermann

@PaŭloEbermann私は同意しますが、プログラマーがキーと復号化を制御する元の投稿の状況とは異なるため、必ずしもすぐに明白であるとは思いません。しかし、私はあなたの答えがそれを賛成するのに十分役立つと思いました。
Warren Dew

1

使用している暗号化アルゴリズムによっては、バイト配列の長さをブロックサイズの倍数にするために、バイト配列を暗号化する前に最後にパディングバイトを追加する必要がある場合があります。

具体的にあなたのケースでは、選択したパディング・スキーマは、ここで説明されているPKCS5です: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_ CJ _SYM__PAD.html

(暗号化しようとしたときに問題があると思います)

Cipherオブジェクトをインスタンス化するときに、パディングスキーマを選択できます。サポートされる値は、使用しているセキュリティプロバイダーによって異なります。

ところで、対称暗号化メカニズムを使用してパスワードを暗号化してもよろしいですか?一方向ハッシュの方が良いのではないでしょうか?本当にパスワードを復号化する必要がある場合、DESは非常に弱いソリューションです。対称アルゴリズムを維持する必要がある場合は、AESなどの強力なものを使用することに関心があるかもしれません。


1
暗号化/復号化を試みるコードを投稿していただけませんか?(そして、解読しようとするバイト配列がブロックサイズより大きくないことを確認してください)
fpacifici

1
私はJavaと暗号化にも非常に慣れていないので、暗号化を行うためのより良い方法がまだわかりません。私はおそらくこれを実装するためのより良い方法を探すよりも、これをやりたいだけです。
Altrim

@fpacificiが機能しないためリンクを更新できますか。暗号化と復号化をテストするJUnitテストを含めて投稿を更新しました
Altrim

修正されました(申し訳ありませんがコピーペーストエラー)。とにかく、パウロで説明されているように、暗号化に使用されたものとは異なるキーで復号化するため、実際に問題が発生します。これは、junitの@Beforeアノテーションが付けられたメソッドがすべてのテストメソッドの前に実行され、毎回キーが再生成されるために発生します。キーはランダムに初期化されるため、毎回異なります。
fpacifici

1

この問題は、オペレーティングシステムが原因で発生しました。JREの実装については、プラットフォームが異なります。

new SecureRandom(key.getBytes())

Windowsでは同じ値になりますが、Linuxでは異なります。Linuxでは次のように変更する必要があります

SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes());
kgen.init(128, secureRandom);

「SHA1PRNG」は使用されるアルゴリズムです。アルゴリズムの詳細については、ここを参照してください。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.