C2SP / wycheproof

Project Wycheproof tests crypto libraries against known attacks.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

AES/CBC/PKCS5Padding tests

SalusaSecondus opened this issue · comments

AES-CBC is still (unfortunately) very popular. What do people think about tests targeting AES/CBC/PKCS5Padding. Possible test cases would include:

  • Various bad paddings
    • > 16 bytes
    • Not all padding bytes the same
    • Last byte is 0
  • Non-constant time unpadding (how much do we care?)
  • Properly generates a random IV if no AlgorithmParameterSpec provided to init()
  • Properly rejects IVs of incorrect length
  • ???

This could possibly be done, though the result might be somewhat unsatisfactory, since even
a good implementation leaves a lot of opportunities for mistakes to the caller.
If the caller uses encrypt-then-mac, and more importantly verify-first-then-decrypt then
it doesn't matter that much whether decryption leaks information through paddings.

Anyway, I'm currently generating test vectors in JSON format for various algorithms, so
it would be possible to do the same for AES/CBC/PKCS5Padding.

Non-constant time unpadding is difficult to detect with the current setup. Even much
larger timing differences (i.e. number of point addition in ECDSA etc) are already difficult
to detect because of noise in typical unit testing.

AES/CBC/PKCS5Padding is one of the first algorithms padded to JCE. Its API is badly broken.
I.e. after a doFinal the Cipher instance is reinitialized with the previous IV. It seems difficult to
actually change this behavior, since implementation might be using it. E.g. I've seen one
implementation that sets the IV to all zero, then encrypts plaintexts by prepending
a random block, claiming that this gives the same result, but faster since an explicit init is slow.
Oracle is fully aware of this design problem, e.g. AES-GCM throws an exception if one tries
to reuse GCM without setting a new IV, but it seems they can't fix the old API.

More ideal for testing here are encryption modes, that by themselves are secure against
chosen message attacks, so that the implementation of the primitive already guarantees
to protect the plaintext and key, and so that testing the primitive can detect flaws, without
having to look at the caller. An example was ECIESWithAES-CBC, which includes an HMAC.
Here, in fact BouncyCastle failed to implement this primitive properly, and checked the
AES padding before checking the HMAC.

Fair enough. I was "inspired" to float this idea after seeing two different AES/CBC implementations (one in Java, one in another language) which make many of these mistakes. They are low bars, but that doesn't stop people from tripping over them.

I didn't want to claim that there is no value in testing this,
even though it is a bit unclear what the requirements are.
E.g. I can't find no definition what an acceptable padding is
(I'm using https://tools.ietf.org/html/rfc2315, which is more general than PKCS #5 with respect to block size).
Since padding attacks typically indicate that the whole protocol is badly designed and not just the
PKCS5Padding is weak it is a bit difficult to define acceptable and non-acceptable behavior.
Can an implementation be forgiving when checking the padding? Would it be acceptable
if invalid paddings were accepted because of legacy reasons?

If there is a protocol that promises security against chosen ciphertext attacks
e.g. something like https://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05
then it is much easier to nitpick and for example flag exceptions with too much information
as errors.

To give a concrete example:
A random search for aes-cbc hmac gives me this:
https://github.com/glkz/aes-cbc-hmac-sha2/blob/master/lib/Cipher.js
I'm using this library just a illustration. Fortunately, it seems unused.

The last step of the decryption is this:

 decipher.final = function(outputEncoding) {
    var ret = _final.call(decipher, outputEncoding);

    decipher._hmac.update(decipher._aadLength);
    decipher._authTag = decipher._hmac.digest().slice(0, decipher._tagLength);
    decipher._cipherFinalized = true;

    if (!decipher._expectedAuthTag || decipher._authTag.toString('hex') !== decipher._expectedAuthTag.toString('hex')) {
      throw new Error('Authentication failed.');
    }

    return ret;
  };

where the first step _final.call(.) decrypts the last chunk, which of course should remove padding.
Of course this call should be done after checking the HMAC. As written here, the code is incorrect
no matter how the padding check is done and it would be correct no matter how the padding
is checked if the order of HMAC and final were switched (and unrelated bugs were fixed such as
deriving _taglength from the tag passed into the decryption).

PKCS#7 padding is woefully underspecified in that standard. (Let's avoid the whole issue of Java misnaming it as PKCS5Padding). I think that we can safely infer proper padding (pad length must be no greater than the block-size and all bytes must be checked) by looking at a multitude of agreeing sources.

  • RFC 2315/PKCS#7: Section 10.3. Note 2 specifically defines the padding length relative to 'k' (the block-size) and states that the padding will consist of k - (l mod k) octets (which must clearly range from 1 to k).
  • BouncyCastle enforces that PKCS#7 Padding must be minimal
  • The default Java JCE requires that padding be minimal in PKCS5Padding.unpad()
  • OpenSSL also requires that padding be minimal in EVP_DecryptFinal_ex(). b is set to the block-size on line 513.

So, I really do think we have enough of a standard here to enforce. I will agree that I don't immediately see a serious risk of accepting bad padding (which wouldn't exist due to problems elsewhere in the protocol). It at least increases the risk of and simplifies various attacks.

Thanks.
So one thing that certainly can be done is provide a number of inputs (key, iv, message) and request that the ciphertext is deterministic. Also requiring that invalid paddings are rejected is reasonable.
Security against padding attacks still seems a bit unclear. E.g. OpenSSL would be faulty if one were strict: EVPerr is a macro that also includes line numbers into the error, hence the lines that you point out
would leak more information than necessary.
At the moment I think padding attacks should be mainly tested on higher levels. Not sure what relevant protocols are: E.g. https://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05
is a proposal, but this seems rather unused/dead.

I have to question the testcase for pad-length must be less than block-size.
From my understanding, pad-length can be less than or equal to block-size. This is to handle the edge-case where the input-length is a multiple of the blocksize and the possibility that the last character of plaintext could be a 0x01 (or less likely 0x02, 0x02 etc..). The RFC's solution to this is create a whole new block made of only padding characters equal to the blocksize.

I believe that's what's meant by the line in rfc2315:
k k ... k k -- if l mod k = 0

I do have to postface this whole question/comment with the background I'm coming from a place of beginner's mind and could be totally missing something. I do not have the multiple codebase references to back up my question. Just wikipedia (PKCS#7) and a budding interest, but I figured it was a question worth asking.

@jordanmews You're correct. Me saying "pad length must be less than block-size" was a mistake and should have been "no greater than the block-size". There is one other place where I incorrectly said that the minimum block-size is 0 rather than 1. Both have been fixed.

As I keep seeing places I wish had these tests, I think that I may draft and submit them.

I think that it is useful having tests for the various different paddings. However, I'm not sure which others are currently in (common) cryptographic usage. (I am purposefully excluding hash functions because they never parse/strip the padding, so the test vectors are irrelevant.)

I just recently encountered AES Key Wrap with Padding (RFC 5649) which uses zero padding with a prefixed length. Definitely a construction I haven't seen before and outside of our current tested algorithms. (It also doesn't have a listed JCE Name). So, we should probably wait a while on this one.

For RFC 5649, we must test the whole algorithm. I'm just noting that there are potentially some interesting padding cases there we should explicitly cover.

FYI: we just published some test vectors for AES-CBC-PKCS5Padding at https://github.com/google/wycheproof/blob/master/testvectors/aes_cbc_pkcs5_test.json.

Re RFC 5649: I believe @bleichen is working on some test vectors for it.

I'm going to close this issue to clean up our queue. Please reopen if you have any further questions or comments.