1

I am writing a cryptographie program and want to use several cipher block- and stream modes together with hashing mechanisms. I do not have any problems with encrypting, decrypting and verifying the message with stream modes like OFB, but I have problems with decrypting and verifying the message with blockcipher moder, when they use padding.

For example I use ECB (I know it is not very good) with PKCS7Padding and SHA-256. After I decrypt the message, it has some chars at the end. Besides that I am getting the message, that the hash-digest is not equal to the original digest.

This problem does not happen, when I do not use padding.

Here is my code:

@Override
public byte[] encrypt(byte[] input) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/ECB/" + getPadding(), "BC");
    cipher.init(Cipher.ENCRYPT_MODE, getKey());
    byte[] output = getBytesForCipher(cipher, input);
    int ctLength = cipher.update(input, 0, input.length, output, 0);
    updateHash(input);
    cipher.doFinal(getDigest(), 0, getDigest().length, output, ctLength);
    return output;
}

protected byte[] getBytesForCipher(Cipher cipher, byte[] input) {
    return new byte[cipher.getOutputSize(input.length + hash.getDigestLength())];
}

protected void updateHash(byte[] input) {
    hash.update(input);
}


public byte[] decrypt(byte[] input) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/ECB/" + getPadding(), "BC");
    cipher.init(Cipher.DECRYPT_MODE, getKey());
    byte[] output = new byte[cipher.getOutputSize(input.length)];
    int ctLength = cipher.update(input, 0, input.length, output, 0);
    cipher.doFinal(output, ctLength);
    return removeHash(output);
}

protected byte[] removeHash(byte[] output) {
    int messageLength = output.length - hash.getDigestLength();
    hash.update(output, 0, output.length - hash.getDigestLength());;
    byte[] realOutput = new byte[messageLength];
    System.arraycopy(output, 0, realOutput, 0, messageLength);
    messageValid = isValid(output);
    return realOutput;
}

private boolean isValid(byte[] output) {
    int messageLength = output.length - hash.getDigestLength();
    byte[] messageHash = new byte[hash.getDigestLength()];
    System.arraycopy(output, messageLength, messageHash, 0, messageHash.length);
    return MessageDigest.isEqual(hash.digest(), messageHash);
}

I am using the bouncycastle provider.

Andrej Tihonov
  • 649
  • 2
  • 9
  • 21
  • *Why* are you using the BC provider for this kind of functionality? You don't like hardware speedups? This is basic functionality that should be performed on the default providers. – Maarten Bodewes Jul 24 '17 at 17:07

1 Answers1

2

If you take a look at getOutputSize method of Cipher you will get the following from the documentation:

The actual output length of the next update or doFinal call may be smaller than the length returned by this method.

And this is exactly what is biting you. As the cipher instance has no way to determine the amount of padding before decrypting, it will assume that the output / plaintext size is the same size as the plaintext size. Actually, as PKCS#7 padding is always performed, it may assume one byte too much in the JCE implementation.

So you cannot just ignore the response of doFinal; you need to resize the array (using the Arrays class for instance) or grab the plaintext and hash from the right location in the buffer.

Obviously a stream cipher will not have this issue as the plaintext size and ciphertext size are identical.


Usually a keyed hash (i.e. MAC or HMAC) or authenticated cipher is used to make sure that the ciphertext is not altered. Using a hash over the plaintext may not fully protect your plaintext.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263