in Encryption, Objective-C, Programming

Encrypt data using AES and 256-bit keys

AES stands for Advanced Encryption Standard and is an industry-standard algorithm for encrypting data symmetrically which even the US government has approved for SECRET documents. If you don’t know what symmetrical encryption is, it means that you use the same key or password to encrypt the data as you do to unencrypt it. So you need to keep the key extremely secret however it’s still incredibly useful. I’ll show you how to use openssl to encrypt some data and decrypt it using the Common Crypto libraries on iOS.

AES Key Size

First a bit about key size… a key can be considered a password to decrypt the data with except with a password you are limited to characters you can type, to keep it simple lets say this was 26 letters + 10 numbers or 36 characters, if you had a password that was 1 character long you’d have 36 possible combinations to try to un-encrypt your data with. If you had a 2 character password you’d have 36 x 36 (362) which would be 1,296 combinations, and 3 characters would mean 36 x 36 x 36 (363) or 46,656 combinations. This increases exponentially with the length of the password.

Now characters are just represented as numbers on most computers these days so why not just skip straight to numbers? If you had a key which was one bit long (21), you’d have 2 combinations as it could just be a 0 or a 1. If you had a key which was 2 bits long (22) you’d have 4 combinations, 3 bits long (23 ) so with a 128 bit key you’d have (2128) 340,282,366,920,938,463,463,374,607,431,768,211,456 combinations. To put that into perspective, it’s estimated the whole world has 5,000,000,000,000,000,000,000 grains of sand and with a 128-bit key you’d still have to search every grain of sand on 68,000,000,000,000,000 earth-like planets. That means 128-bit keys are pretty secure if someone was to try every single combination (a brute force attack). Now imagine how many combinations there must be for a 256-bit key… yes basically 2256 possible combinations and don’t forget that even going from a 128-bit key to a 129-bit key would not just double the combinations, it would increase it exponentially! In summary if you’re going for key size, a 128-bit key would be more than enough and a 256-bit key would be so huge it would probably slow down even a quantum brute-force attack… probably! However bigger isn’t always better and a 256-bit key uses more processing power so it’s something to think about…

IV – the bit twister

It gets a bit complicated but the CommonCrypto library defaults to using Cipher Block Chaining (CBC) for the Block Cipher Mode. Without going into too much detail, the plaintext you’d like to encrypt gets broken up into small blocks of data and starting with the first block of data the AES algorithm gets applied and used in the calculation for encrypting the next block. That means each block is dependant on the previous block being calculated – hence the “chaining” of blocks. However this exposes a problem: if you have two pieces of data you encrypt using the same key and the first few blocks are the same you’ll find that with the two encrypted pieces of data the beginning of both pieces are the same. This might not seem like a big deal at first but an attacker will know you used the same key and could use this to work out statistical differences with each block and in turn predict the contents and/or work out the key.

For AES, regardless of key size the block size is 128-bits long which if we assume 8-bits per character would mean each block is 16 characters in length. Enough theory, lets see it in action with OpenSSL in the terminal on Mac OS X!

We use a key of secretpassword and use interactive mode which allows Ctrl+D to finish and output the encrypted cipher text in Base64 format.

openssl enc -aes128 -k secretpassword -a -p -nosalt -iv 00
key=2034F6E32958647FDFF75D265B455EBF
Hello Mr Warrender, This is good news

VVklkPrL5fczxmu4vZ93BkAsVA64MLd5uah+zTVzr0XlOONVgDEd7ZunyIIzhpAo

Now lets try another message using the same settings:

openssl enc -aes128 -k secretpassword -a -p -nosalt -iv 00
key=2034F6E32958647FDFF75D265B455EBF
Hello Mr Warrender, I have some terrible news

VVklkPrL5fczxmu4vZ93BnfBBpU8BWK1IQhHF6JRKSNZJ7PvpcaE8K/Mkbx1xgHa

By comparing the two ciphertexts above you can see the beginning block ‘VVklkPrL5fczxmu4vZ93B’ is the same:

VVklkPrL5fczxmu4vZ93BkAsVA64MLd5uah+zTVzr0XlOONVgDEd7ZunyIIzhpAo
VVklkPrL5fczxmu4vZ93BnfBBpU8BWK1IQhHF6JRKSNZJ7PvpcaE8K/Mkbx1xgHa

If we knew the contents of one of the messages, perhaps let’s say from a captured U-Boat, we could work out when a message was addressed to Mr Warrender without ever knowing the key! The same principle holds true for file formats, transfer protocols and other data you encrypt but how do you fix it?

The solution is to use a unique Initialisation Vector or an IV with CBC mode. This adjusts the initial plaintext block by a certain amount and since each block is chained, every subsequent block is different every time even if you encrypt the same message twice. As long as you use a different IV for every message it will result in a completely different ciphertext.

Typically for AES the IV is the size of a block so the IV is 128-bits long. With openssl you can specify the IV using hex.

openssl enc -aes256 -k secretpassword -a -p -nosalt
key=2034F6E32958647FDFF75D265B455EBF40C80E6D597092B3A802B3E5863F878C
iv =AD0ACC568C88C116D57B273D98FB92C0
Hello Mr Warrender, This is good news 

9/0FGE21YYBl8NvlCp1Ft8j1V7BiIpCIlNa/zbYwL5LWyemd/7QEu0tkVz9/f0JG
openssl enc -aes256 -k secretpassword -a -p -nosalt -iv 0F0AFF0F0AFF0F0AFF0F0AFF0F0AFF
key=2034F6E32958647FDFF75D265B455EBF40C80E6D597092B3A802B3E5863F878C
iv =0F0AFF0F0AFF0F0AFF0F0AFF0F0AFF00
Hello Mr Warrender, This is good news 

S6flMkdMeC77p/7pokXZkHT0is7Lp57Zgkokg/O99puZloTB/ZUzp0FwH8sWFekg

By comparing the results with the same message, same key and different IV we can see the blocks don’t match which is just what we wanted!

9/0FGE21YYBl8NvlCp1Ft8j1V7BiIpCIlNa/zbYwL5LWyemd/7QEu0tkVz9/f0JG
S6flMkdMeC77p/7pokXZkHT0is7Lp57Zgkokg/O99puZloTB/ZUzp0FwH8sWFekg

Now you can store the IV with the key or prepend it to the encrypted payload, it doesn’t matter too much as long as you use the same IV to decrypt with and obviously the key should always remain private. However make sure you use a unique IV for each message!

Wait… The IV is not a key!

It’s tempting to think that the IV is an extra layer of encryption except it isn’t. In-fact you don’t even need the IV to decrypt the majority of a message. Remember how block chaining worked? You took the ciphertext from the current block and used that to help you encrypt the plaintext for the next block…

Block 1:
(Plaintext + IV) > Encrypt > Ciphertext

Block 2:
(Plaintext + Block 1 Ciphertext) > Encrypt > Ciphertext

Block 3:
(Plaintext + Block 2 Ciphertext) > Encrypt > Ciphertext

When decrypting we work backwards and because we used the adjacent ciphertext block before encrypting, we need to remove it. However as you can see below, we already have the ciphertext because we’re decrypting! Hence only block 1 gets corrupted because we don’t know the IV to remove.

Block 3:
Ciphertext > Decrypt > (Result - Block 2 Ciphertext) > Plaintext

Block 2:
Ciphertext > Decrypt > (Result - Block 1 Ciphertext) > Plaintext

Block 1:
Ciphertext > Decrypt > (Result - Missing IV) > Corrupted

Decrypting AES in iOS

So you have your hex-encoded AES 256-bit key:

2034F6E32958647FDFF75D265B455EBF40C80E6D597092B3A802B3E5863F878C 

You have your hex-encoded IV and your Base64-encoded ciphertext:

IV: AD0ACC568C88C116D57B273D98FB92C0
Ciphertext: 9/0FGE21YYBl8NvlCp1Ft8j1V7BiIpCIlNa/zbYwL5LWyemd/7QEu0tkVz9/f0JG

Now to decrypt it… place the following code into a category named NSData+AES256Encryption.

#import <commoncrypto commondigest.h>
#import <commoncrypto commoncryptor.h>

@interface NSData (AES256Encryption)
- (NSData *)encryptedDataWithHexKey:(NSString*)hexKey hexIV:(NSString *)hexIV;
- (NSData *)originalDataWithHexKey:(NSString*)hexKey hexIV:(NSString *)hexIV;
@end

@implementation NSData (AES256Encryption)

+ (NSData *)dataFromHexString:(NSString *)string
{
    string = string.lowercaseString;
    NSMutableData *data = [[NSMutableData alloc] initWithCapacity:string.length/2];
    unsigned char whole_byte;
    char byte_chars[3] = {'\0','\0','\0'};
    int i = 0;
    NSUInteger length = string.length;
    while (i < length-1) {
        char c = [string characterAtIndex:i++];
        if (c < '0' || (c > '9' && c < 'a') || c > 'f')
            continue;
        byte_chars[0] = c;
        byte_chars[1] = [string characterAtIndex:i++];
        whole_byte = strtol(byte_chars, NULL, 16);
        [data appendBytes:&whole_byte length:1];
    }
    return data;
}

+ (void)fillDataArray:(char **)dataPtr length:(NSUInteger)length usingHexString:(NSString *)hexString
{
    NSData *data = [NSData dataFromHexString:hexString];
    NSAssert((data.length + 1) == length, @"The hex provided didn't decode to match length");
    
    unsigned long total_bytes = (length + 1) * sizeof(char);
    
    *dataPtr = malloc(total_bytes);
    bzero(*dataPtr, total_bytes);
    memcpy(*dataPtr, data.bytes, data.length);
}

- (NSData *)encryptedDataWithHexKey:(NSString*)hexKey hexIV:(NSString *)hexIV
{
    // Fetch key data and put into C string array padded with \0
    char *keyPtr;
    [NSData fillDataArray:&keyPtr length:kCCKeySizeAES256+1 usingHexString:hexKey];
  
    // Fetch iv data and put into C string array padded with \0
    char *ivPtr;
    [NSData fillDataArray:&ivPtr length:kCCKeySizeAES128+1 usingHexString:hexIV];

    // For block ciphers, the output size will always be less than or equal to the input size plus the size of one block because we add padding.
    // That's why we need to add the size of one block here
    NSUInteger dataLength = self.length;
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc( bufferSize );
    
    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
                                          keyPtr, kCCKeySizeAES256,
                                          ivPtr /* initialization vector */,
                                          [self bytes], [self length], /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesEncrypted);
    free(keyPtr);
    free(ivPtr);
    
    if(cryptStatus == kCCSuccess) {
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }
    
    free(buffer);
    return nil;
}

- (NSData *)originalDataWithHexKey:(NSString*)hexKey hexIV:(NSString *)hexIV
{
    // Fetch key data and put into C string array padded with \0
    char *keyPtr;
    [NSData fillDataArray:&keyPtr length:kCCKeySizeAES256+1 usingHexString:hexKey];
    
    // Fetch iv data and put into C string array padded with \0
    char *ivPtr;
    [NSData fillDataArray:&ivPtr length:kCCKeySizeAES128+1 usingHexString:hexIV];
    
    // For block ciphers, the output size will always be less than or equal to the input size plus the size of one block because we add padding.
    // That's why we need to add the size of one block here
    NSUInteger dataLength = self.length;
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc( bufferSize );
    
    size_t numBytesDecrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt( kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
                                          keyPtr, kCCKeySizeAES256,
                                          ivPtr,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesDecrypted );
    free(keyPtr);
    free(ivPtr);
    
    if( cryptStatus == kCCSuccess )
    {
        // The returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
    }
    
    free(buffer);
    return nil;
}
@end

Now in your view controller or wherever you want to decrypt place the following code:

- (void)runMe
{    
    NSString *hexKey = @"2034F6E32958647FDFF75D265B455EBF40C80E6D597092B3A802B3E5863F878C";
    NSString *cipherText = nil;
    NSString *hexIV = nil;
    
    cipherText = @"9/0FGE21YYBl8NvlCp1Ft8j1V7BiIpCIlNa/zbYwL5LWyemd/7QEu0tkVz9/f0JG";
    hexIV = @"AD0ACC568C88C116D57B273D98FB92C0";
    [self decodeAndPrintCipherBase64Data:cipherText usingHexKey:hexKey hexIV:hexIV];
    
    cipherText = @"S6flMkdMeC77p/7pokXZkHT0is7Lp57Zgkokg/O99puZloTB/ZUzp0FwH8sWFekg";
    hexIV = @"0F0AFF0F0AFF0F0AFF0F0AFF0F0AFF00";
    [self decodeAndPrintCipherBase64Data:cipherText usingHexKey:hexKey hexIV:hexIV];
    
    NSString *plainText = @"Thank you Mr Warrender, Reinforcements have arrived! Send biscuits";
    hexIV = @"010932650CDD998833CC0AFF9AFF00FF";
    [self encodeAndPrintPlainText:plainText usingHexKey:hexKey hexIV:hexIV];
}

- (void)decodeAndPrintCipherBase64Data:(NSString *)cipherText
                           usingHexKey:(NSString *)hexKey
                                 hexIV:(NSString *)hexIV
{
    NSData *data = [[NSData alloc] initWithBase64EncodedString:cipherText options:0];
    NSAssert(data != nil, @"Couldn't base64 decode cipher text");
    
    NSData *decryptedPayload = [data originalDataWithHexKey:hexKey
                                                      hexIV:hexIV];
    
    if (decryptedPayload) {
        NSString *plainText = [[NSString alloc] initWithData:decryptedPayload encoding:NSUTF8StringEncoding];
        NSLog(@"Decrypted Result: %@", plainText);
    }
}

- (void)encodeAndPrintPlainText:(NSString *)plainText
                    usingHexKey:(NSString *)hexKey
                          hexIV:(NSString *)hexIV
{
    NSData *data = [plainText dataUsingEncoding:NSUTF8StringEncoding];
    
    NSData *encryptedPayload = [data encryptedDataWithHexKey:hexKey
                                                       hexIV:hexIV];
    
    if (encryptedPayload) {
        NSString *cipherText = [encryptedPayload base64EncodedStringWithOptions:0];
        NSLog(@"Encryped Result: %@", cipherText);
    }
}

As you can see in the code we set the ciphertext, key and IV examples we created using openssl earlier and decoded them. Run the code and you should see “Decrypted Result: Hello Mr Warrender, This is good news” printed twice in the console. Congratulations, you just decoded encrypted text using AES-256 encryption!

CommonCrypto’s CCCrypt C function looks very scary but it’s actually really easy to use. Most of the code in the category involves getting the raw bits from the hex strings and putting them into C byte arrays. As we’re using C functions we have to calculate how long our arrays are since we just pass around pointers to them.

CCCrypt( kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
         keyPtr, kCCKeySizeAES256,
         ivPtr,
         [self bytes], dataLength, /* input */
         buffer, bufferSize, /* output */
         &numBytesDecrypted );

The 1st parameter is a constant to tell the CCCrypt function to decrypt your text.

The 2nd parameter is a const which specifies which algorithm to use – that’s where you specify to use the AES cipher.

The 3rd parameter needs some explaining, this specifies we want to use PKCS #7 Padding. AES is a block cipher because we break our plaintext up into blocks, however if our data doesn’t evenly fit into a block we’ll have to pad it with null characters which would work for printing simple C strings because a null character indicates the end of a string but for images or other data this isn’t what we want. Instead we specify to use the PKCS #7 padding algorithm to pad the block and get back the same data we encrypted. Note: openssl uses PKCS #5 padding algorithm but they are basically the same, that might save you a few hours!

The 4th parameter is a pointer containing your raw key byte array.

The 5th parameter specifies how long your key is – you can use AES256 or AES128 enum consts here.

The 6th parameter is the raw IV byte array pointer. You don’t need a size/length here because it will always be the same as the block size.

The 7th and 8th parameters are the raw byte array pointer and array size of the ciphertext.

The 9th and 10th parameters are the buffer byte pointer and buffer size of where the plaintext will go.

The 11th parameter is an size pointer that will be populated with the size of the data that was decrypted.

Encrypting AES in iOS

Lets test the encryption from the iOS code. Now to ensure everything is correct, we encrypted a message back using a new IV but with the same details. Cut and paste the following from the console and paste into a file called test.enc.

JKKBz4s4mvJJkDALfY5F84mrT0Y/VphISHe1f3qDmvHmpf8DHZXqvB93yLoboU7OkV6zfcfsbLw/xOnbqiIUY8RojXg3vLrjzBFCqisDDc8=

Now we’ll use the -d (switch to decrypt) and -in (load a file) options of the openssl enc command to decrypt the message stored in test.enc using our previous key. Note we’re using a different IV!

Type the following into the terminal:

openssl enc -aes256 -k secretpassword -p -iv 010932650CDD998833CC0AFF9AFF00FF -nosalt -base64 -d -in test.enc 
key=2034F6E32958647FDFF75D265B455EBF40C80E6D597092B3A802B3E5863F878C
iv =010932650CDD998833CC0AFF9AFF00FF

Thank you Mr Warrender, Reinforcements have arrived! Send biscuits

Awesome, that’s great! We’ve successfully decoded our message using openssl we encrypted using iOS. Hopefully that’s shown you how to encrypt and decrypt AES protected data with 256-bit keys. You should also now understand about keys, block cipher modes and a bit about why IVs help protect data. Have fun and good luck protecting your user data.

Write a Comment

Comment

14 Comments

  1. In this situation when we get success in either encrypting or decrypting data
    if( cryptStatus == kCCSuccess )
    {
    // The returned NSData takes ownership of the buffer and will free it on deallocation
    return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
    }
    We did not free the buffer. Why?

    • In the NSData docs under Discussion it says [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted] takes ownership of the bytes pointer and frees it on deallocation. Hence it only needs to be freed if the cryptStatus fails.

      In other words once we wrap the bytes up in NSData we can let the system free it. Hope that helps!

    • The message is saying it tried to decode the hex into binary data and the length it was expecting didn’t match so check you’ve entered your hex string correctly. You might have missed a character off or there are hidden white spaces lurking somewhere.

  2. why getting garbage data after decryption
    if( cryptStatus == kCCSuccess )
    {
    // The returned NSData takes ownership of the buffer and will free it on deallocation
    return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
    }

  3. Thank You, I have no real experience with Objective-C and not much encryption knowledge but found it easy to follow and implement.

  4. Shouldn’t the bzero part (line 37 in NSData+AES256Encryption)
    be `bzero(*dataPtr, length);`? I mean, you want to zero out the entire array and not just the `sizeof(*dataPtr)`.

    • Boris,

      bzero(*dataPtr, sizeof(*dataPtr));

      XCode 9 hates this line. I keep getting memory errors if I use the category multiple times.
      ‘bzero’ call operates on objects of type ‘char’ while the size is based on a different type ‘char *’

      Symbiosis(8772,0x10d2d1340) malloc: *** error for object 0x608000457af0: Invalid pointer dequeued from free list
      *** set a breakpoint in malloc_error_break to debug

      The strange thing is my encryption and decryption is working before the memory crash.

      • Yes, this is a bug. Good find, I knew someone would find it soon! ;-)

        `bzero` replaces everything at the given memory address with zeros. My intention was to “zero out” the memory before copying the data from the NSData object resulting in at least one byte at the end being \0 (\0 is used by C to indicate a string is complete).

        However just doing what Boris suggests won’t work on its own because we need the length of data + 1 for the \0 character. We’re actually initialising the pointer with one less byte of data than we should do. Hence the fix that will solve both warnings in Xcode is:

            NSData *data = [NSData dataFromHexString:hexString];
            NSAssert((data.length + 1) == length, @"The hex provided didn't decode to match length");
            
            unsigned long total_bytes = (length + 1) * sizeof(char);
            
            *dataPtr = malloc(total_bytes);
            bzero(*dataPtr, total_bytes);
            memcpy(*dataPtr, data.bytes, data.length);
        

        Hope that helps.

  5. Documentation of CCrypt says about IV “If present, must be the same length as the selected algorithm’s block size.” Block size of AES256 is 32. This solution uses kCCKeySizeAES128 + 1. Is this correct?

    • Hi Simon, be careful here. Don’t forget that block size and key size are different. Your assumption that the block size of AES256 is 32 is wrong. The AES algorithm uses a 16-byte block size (which is 128-bit assuming 8 bits in a byte). This block size is the same for all key sizes. In laymen terms, the block size is how thinly the data gets chopped up to be worked with; the key size is how often it rotates or gets “shuffled”. The IV is used to modify the first data block so that is why it must match.

      Typically for AES the IV is the size of a block so the IV is 128-bits long.

      My solution does actually follow the `CCrypt` snippet you posted – if you look at the IVs you’ll see they are 32 character hexadecimal strings, 16 pairs – each hex letter pair represents 8-bits each so (8 x 16 = 128-bit).

      Hope that helps.