CCCryptorCreateWithMode ignores padding parameter

1.5k views Asked by At

I'm trying to use CommonCrypto for crypting with AES-CTR and PKCS7 padding.

I am aware that CTR does not need padding in order to work correctly, there are uses for padding beyond that. Quoting RFC 3686:

For this reason, AES-CTR does not require the plaintext to be padded to a multiple of the block size. However, to provide limited traffic flow confidentiality, padding MAY be included, as specified in ESP.

Section 2.4 of the referenced document is relevant, and lists several uses of padding.

Therefore, I figure CommonCrypto should be able to apply padding to any cipher (mode). The code seems straight forward, breaking down to something like this:

CCCryptorRef cryptor = nil;
NSData* input = [NSMutableData dataWithLength:3];
NSData* key = [NSMutableData dataWithLength:32];
NSMutableData* output = [NSMutableData dataWithLength:32];

CCCryptorCreateWithMode(
        kCCEncrypt, kCCModeCTR, kCCAlgorithmAES, ccPKCS7Padding,
        nil,
        key.bytes, key.length,
        nil, 0, 0,
        kCCModeOptionCTR_BE,
        &cryptor
);

size_t written = 0;
CCCryptorUpdate(
        cryptor,
        input.bytes, input.length,
        output.mutableBytes, output.length,
        &written
);

size_t writtenF = 0;
CCCryptorFinal(
    cryptor, 
    output.mutableBytes + written, output.length - written, 
    &writtenF);

CCCryptorRelease(cryptor);

output.length = written + writtenF;

NSLog(@"Expected: 16 bytes");
NSLog(@"Actual: %i bytes", output.length);

The output is:

Expected: 16 bytes
Actual: 3 bytes

If you check the status codes, you'll find there there are no errors.

I can decrypt the output just fine okay, so encryption itself seems to work just fine. But clearly no padding happens.

I can not debug into the implementations, so I have no idea what's going wrong. Am I using the options incorrectly?


Nota bene: I can make CCCrypt apply padding with kCCOptionPKCS7Padding, but then I don't see a way to select CTR mode.

2

There are 2 answers

3
jww On BEST ANSWER

Quoting RFC 3686:

For this reason, AES-CTR does not require the plaintext to be padded to a multiple of the block size. However, to provide limited traffic flow confidentiality, padding MAY be included, as specified in ESP.

This is a different use of padding. It is not PKCS#7 padding used in block ciphers. Its is RFC 2406 padding placed in the ESP packet behind the payload data (see below).


Therefore, I figure CommonCrypto should be able to apply padding to any cipher (mode)...

The confusion has taken you down a rabbit hole. It does not intersect with CommonCrypto like you are thinking. Stop now :)


From page 2 of RFC 2406:

2.  Encapsulating Security Payload Packet Format

   The protocol header (IPv4, IPv6, or Extension) immediately preceding
   the ESP header will contain the value 50 in its Protocol (IPv4) or
   Next Header (IPv6, Extension) field [STD-2].

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ----
|               Security Parameters Index (SPI)                 | ^Auth.
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Cov-
|                      Sequence Number                          | |erage
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ----
|                    Payload Data* (variable)                   | |   ^
~                                                               ~ |   |
|                                                               | |Conf.
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Cov-
|               |     Padding (0-255 bytes)                     | |erage*
+-+-+-+-+-+-+-+-+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |   |
|                               |  Pad Length   | Next Header   | v   v
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ------
|                 Authentication Data (variable)                |
~                                                               ~
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1
Raphael On

This is an attempt at an answer, ultimately unsuccessful, in the hopes that someone else can use the pointers and make more sense of the situation.


In the implementation of ccSetupCryptor (which gets called by CCCryptorCreateWithMode) we find:

case ccPKCS7Padding:
        if(mode == kCCModeCBC)
            ref->padptr = &ccpkcs7_pad;
        else
            ref->padptr = &ccpkcs7_ecb_pad;
        break;

This seems to suggest that PKCS7 is only available for CBC and ECB.

But the padding should do something. Digging deeper, ccpkcs7_ecb_pad is defined in corecryptoSymmetricBridge:

const cc2CCPaddingDescriptor ccpkcs7_ecb_pad = {
    .encrypt_pad = ccpkcs7_encrypt_ecb_pad,
    .decrypt_pad = ccpkcs7_decrypt_ecb_pad,
    .padlen = ccpkcs7_padlen,
    .padreserve = ccpkcs7_reserve,
};

ccpkcs7_encrypt_ecb_pad leads to an actual encryption routine, which I'm sure is not executed in CTR mode.

Tracking ccpkcs7_padlen leads me to this:

static size_t ccctr_mode_get_block_size(const corecryptoMode modeObject) { 
    return modeObject.ctr->block_size; 
}

Now, this corecryptoMode is defined in corecryptoSymmetricBridge but I can not find the definitions of block_size there, or anywhere else.

It should be 128bit, as for all AES modes, but then maybe it's set to zero or one since CTR can deal with arbitrary-length inputs? That would explain why zero padding bytes get added, but I can not confirm that that is what happens.