Alex Amiryan
Alex Amiryan

Reputation: 1382

Java AES decryption detect incorrect key

I am writing android app that makes AES encryption/decryption of files. I want to be able to detect if incorrect password is specified and thus not matching key is derived for decryption. I am using AES/CBC/PKCS7Padding with 256 bit key. If I do cipher.doFinal() I can try/catch the BadPaddingException and it tells me that something is wrong and probably key was incorrect. But if I use CipherInputStream to read encrypted file, I get no feedback on correctness of padding. So if I deliberately specify incorrect password it decrypts file, then reports that everything is ok, however decrypted file is a total junk. So my question is how to detect bad padding when using CipherInputStream?

Upvotes: 4

Views: 4258

Answers (6)

user2505009
user2505009

Reputation: 113

I had the same issue, how to know if the key used to encrypt is the same that used to decrypt, because in my case I could decrypt the strings but it returned some garbage, and i need to know the encrypted string(its random) to get the correct value.

So what i have done is;

Decrypt the encrypted string.
Encrypt the String again using the correct key.
Decrypt the previous encrypted string.
Match if the original decrypted key equals the new decrypted key.

    Cipher c = Cipher.getInstance(algorithm);
    c.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
    byte[] decValue = c.doFinal(encryptedData.getBytes());
    decryptedValue = new String(decValue,"UTF-8");

  //now i have the string decrypted in decryptedValue

  byte[] encryptAgain = encrypt(decryptedValue);
  String encryptAgaindecripted = new String(c.doFinal(encryptAgain),"UTF-8");

  //if keys match then it uses the same key and string is valid
 if (decryptedValue.equals(encryptAgaindecripted)){
  //return valid
 }

hope this helps someone.

Upvotes: 2

Jilles van Gurp
Jilles van Gurp

Reputation: 8314

I ran into this as well. I had a test that reliably fails a couple of times out of a thousand runs with AES/CBC/PKCS5Padding that is supported in Java.

To fix, you can do as suggested above and use bouncy castle.

However, I did a different fix and simply added a md5 content hash to the plain text before encrypting that I verify on decrypt. Simply append the content to the md5 hash and on decrypt grab the first 22 characters of the md5 hash and verify that the rest of the string has the same hash and throw an exception if it doesn't or return the plaintext (without md5 hash) if it does match. This will work regardless of the encryption algorithm. Probably with GCM mode this is indeed not needed though. Anyway, this way you can avoid extra dependencies on bouncy castle.

Upvotes: 0

Alex Amiryan
Alex Amiryan

Reputation: 1382

Here is modified version of getMoreData() method in CipherInputStream, it maybe useful for someone who faced my problem:

private int getMoreData() throws IOException {
    if (done) return -1;
    int readin = input.read(ibuffer);
    if (readin == -1) {
        done = true;
        try {
            obuffer = cipher.doFinal();
        }
        catch (IllegalBlockSizeException e) {
            throw new IOException(e);
        }
        catch (BadPaddingException e) {
            throw new IOException(e);
        }
        if (obuffer == null)
            return -1;
        else {
            ostart = 0;
            ofinish = obuffer.length;
            return ofinish;
        }
    }
    try {
        obuffer = cipher.update(ibuffer, 0, readin);
    } catch (IllegalStateException e) {obuffer = null;};
    ostart = 0;
    if (obuffer == null)
        ofinish = 0;
    else ofinish = obuffer.length;
    return ofinish;
}

Upvotes: 1

Maarten Bodewes
Maarten Bodewes

Reputation: 94038

Try and use GCM mode instead (Java 7 or Bouncy Castle provider). The trick with padding is that sometimes it is correct after the message has been altered (once in 256 times, approximately). GCM mode will add intergrity protection, so any alteration will result in an exception derived from BadPaddingException.

One thing though: you should prepend a (random) nonce when encrypting with GCM (actually a rule for CBC mode too, but the implications of using a non-random IV in CBC are less severe).

Note that you need to perform the final calculation to get a badpaddingexception, so don't forget to close or end the underlying stream. This is probably your current issue.

[EDIT]: this is not the answer, but the input could be used to generate a better CipherInputStream, see my other answer on this question.

Upvotes: 2

Maarten Bodewes
Maarten Bodewes

Reputation: 94038

I think bad padding is caught for some reason, this is from the CipherInputStream source:

private int getMoreData() throws IOException {
    if (done) return -1;
    int readin = input.read(ibuffer);
    if (readin == -1) {
        done = true;
        try {
            obuffer = cipher.doFinal();
        }
        catch (IllegalBlockSizeException e) {obuffer = null;}
        catch (BadPaddingException e) {obuffer = null;}
        if (obuffer == null)
            return -1;
        else {
            ostart = 0;
            ofinish = obuffer.length;
            return ofinish;
        }
    }
    try {
        obuffer = cipher.update(ibuffer, 0, readin);
    } catch (IllegalStateException e) {obuffer = null;};
    ostart = 0;
    if (obuffer == null)
        ofinish = 0;
    else ofinish = obuffer.length;
    return ofinish;
}

Upvotes: 1

Nikolay Elenkov
Nikolay Elenkov

Reputation: 52956

Prepend some known header to your data. Decrypt it first and if it doesn't match what you expected, stop and return error.

Upvotes: 3

Related Questions