Home > cocoa, iphone, Security > Properly encrypting with AES with CommonCrypto

Properly encrypting with AES with CommonCrypto

August 11th, 2011 Leave a comment Go to comments

Update: You can now download the full example code from my book at the Wiley site. This comes from Chapter 11, “Batten the Hatches with Security Services.”

I see a lot of example code out there showing how to use CCCrypt(), and most of it is unfortunately wrong. Since I just got finished writing about 10 pages of explanation for my upcoming book, I thought I’d post a shortened form here and hopefully help clear things up a little. This is going to be a little bit of a whirlwind, focused on the simplest case. If you want the gory details including performance improvements for large amounts of data, well, the book will be out later this year. :D

First and foremost: the key. This is almost always done wrong in the examples you see floating around the Internet. A human-typed password is not an AES key. It has far too little entropy. Using it directly as an AES key opens you up to all kinds of attacks. In particular, lines like this are wrong:

// DO NOT DO THIS
NSString *password = @"P4ssW0rd!";
char keyPtr[kCCKeySizeAES128];
[password getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];
// DO NOT DO THIS

This key is susceptible to a variety of attacks. It is neither salted nor stretched. If password is longer than the key size, then the password will be truncated. This is not how you build a key.

First, you need to salt your key. That means adding random data to it so that if the same data is encrypted with the same password, the ciphertext will still be different. The key should then be hashed, so that the final result is the correct length. The correct way to do this is with PKCS #5 (PBKDF2). Unfortunately, prior to 10.7, there wasn’t an easy function to do that. I’m going to focus here on 10.7 code, but if you need a simple solution, just add about 8 random characters to the the string, and run it through -hash. Stringify that and run it through -hash again. Repeat 100,000 times. Take the lower “X” bits where “X” is the number of bits in your key. This isn’t perfect, but it’s easy to code and close enough. It works on all versions of OS X and iOS. 100,000 times is based on this taking about 100ms for a MacBookPro to calculate in my quick tests. On an iPhone 4, the same delay is around 10,000 iterations. The goal is to force the attacker to waste some time.

(If someone has a better quick-and-easy key generation algorithm, leave a comment.)

But like I said, don’t do that way if you can help it. We have PBKDF2 built into CommonCrypto now (well, in 10.7). Hand it your salt, your password, the number of iterations, and the length of your key and it spits out the answer for you. I’ll show how to do this in the code below.

Did I mention that CommonCrypto is all open source? So if you needed the PBKDF2 code for other platforms, you could probably get it to work.

OK, now you have a salt. What do you do with it? Save it with the cipertext. You’ll need it later to decrypt. The salt is considered public information so you don’t need to protect it.

And now the mystical initialization vector (IV) that confuses everyone. In CBC-mode, each 16-byte encryption influences the next 16-byte encryption. This is a good thing. It makes the encryption much stronger. It’s also the default. The problem is, what about block 0? The answer is you make up a random block -1. That’s the IV.

This is listed as “optional” in CCCrypt() which is confusing because it isn’t really optional in CBC mode. If you don’t provide one, it’ll automatically generate an all-0 IV for you. That throws away significant protection on the first block. There’s no reason to do that. IV is simple: it’s just 16 random bytes. “Save it with the cipertext. You’ll need it later to decrypt. The salt IV is considered public information so you don’t need to protect it.”

OK, now that I’ve gone on and on about theory, let’s see this in practice. First, here’s how you use it. The method returns the encrypted data (nil for error), and returns the IV, salt and error by reference. Slap the data, IV, and salt together in your file in whatever way is easy for you to retrieve them later. The IV has to be 16 bytes long for AES. The salt can be any length, but my code sets it to 8 bytes, which is the PKCS#5 minimum recommended length.

NSData *iv;
NSData *salt;
NSError *error;
NSData *encryptedData = [RNCryptManager encryptedDataForData:plaintextData
                                                    password:password
                                                          iv:&iv
                                                        salt:&salt
                                                       error:&error];

And here’s the code. I will leave the decrypt method as an exercise for the reader. It’s almost identical, and it’s a good idea to actually understand this code, not just copy it. Don’t forget to link Security.framework.

Go forth and encrypt stuff.

#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonKeyDerivation.h>

NSString * const
kRNCryptManagerErrorDomain = @"net.robnapier.RNCryptManager";

const CCAlgorithm kAlgorithm = kCCAlgorithmAES128;
const NSUInteger kAlgorithmKeySize = kCCKeySizeAES128;
const NSUInteger kAlgorithmBlockSize = kCCBlockSizeAES128;
const NSUInteger kAlgorithmIVSize = kCCBlockSizeAES128;
const NSUInteger kPBKDFSaltSize = 8;
const NSUInteger kPBKDFRounds = 10000;  // ~80ms on an iPhone 4

// ===================

+ (NSData *)encryptedDataForData:(NSData *)data
                        password:(NSString *)password
                              iv:(NSData **)iv
                            salt:(NSData **)salt
                           error:(NSError **)error {
  NSAssert(iv, @"IV must not be NULL");
  NSAssert(salt, @"salt must not be NULL");

  *iv = [self randomDataOfLength:kAlgorithmIVSize];
  *salt = [self randomDataOfLength:kPBKDFSaltSize];

  NSData *key = [self AESKeyForPassword:password salt:*salt];

  size_t outLength;
  NSMutableData *
  cipherData = [NSMutableData dataWithLength:data.length +
                kAlgorithmBlockSize];

  CCCryptorStatus
  result = CCCrypt(kCCEncrypt, // operation
                   kAlgorithm, // Algorithm
                   kCCOptionPKCS7Padding, // options
                   key.bytes, // key
                   key.length, // keylength
                   (*iv).bytes,// iv
                   data.bytes, // dataIn
                   data.length, // dataInLength,
                   cipherData.mutableBytes, // dataOut
                   cipherData.length, // dataOutAvailable
                   &outLength); // dataOutMoved

  if (result == kCCSuccess) {
    cipherData.length = outLength;
  }
  else {
    if (error) {
      *error = [NSError errorWithDomain:kRNCryptManagerErrorDomain
                                   code:result
                               userInfo:nil];
    }
    return nil;
  }

  return cipherData;
}

// ===================

+ (NSData *)randomDataOfLength:(size_t)length {
  NSMutableData *data = [NSMutableData dataWithLength:length];

  int result = SecRandomCopyBytes(kSecRandomDefault, 
                                  length,
                                  data.mutableBytes);
  NSAssert(result == 0, @"Unable to generate random bytes: %d",
           errno);

  return data;
}

// ===================

// Replace this with a 10,000 hash calls if you don't have CCKeyDerivationPBKDF
+ (NSData *)AESKeyForPassword:(NSString *)password 
                         salt:(NSData *)salt {
  NSMutableData *
  derivedKey = [NSMutableData dataWithLength:kAlgorithmKeySize];

  int 
  result = CCKeyDerivationPBKDF(kCCPBKDF2,            // algorithm
                                password.UTF8String,  // password
                                password.length,  // passwordLength
                                salt.bytes,           // salt
                                salt.length,          // saltLen
                                kCCPRFHmacAlgSHA1,    // PRF
                                kPBKDFRounds,         // rounds
                                derivedKey.mutableBytes, // derivedKey
                                derivedKey.length); // derivedKeyLen

  // Do not log password here
  NSAssert(result == kCCSuccess,
           @"Unable to create AES key for password: %d", result);

  return derivedKey;
}
Categories: cocoa, iphone, Security Tags:
  1. Matt V
    September 6th, 2011 at 23:30 | #1

    Thanks for the tutorial! I agree, there is serious lack of formal apple documentation and the stuff floating around the internet is either A: wrong or B: not very secure.

    The code you have posted here works fantastically and works perfectly for my application.

    Thanks for taking the time to post this and good luck with your book!

    Best, Matt

  2. Ashar
    September 30th, 2011 at 07:38 | #2

    Thanks for the great article. I have a question; Why can’t we just use the “SecRandomCopyBytes” to generate the key. I have seen some examples doing so. Is is too easy for attack to generate? Maybe the way the random numbers are generated in this function are predicable?

    Thanks Ashar

  3. September 30th, 2011 at 09:33 | #3

    @Ashar

    Ashar, you absolutely can use SecRandomCopyBytes (randomDataOfLength:) to generate the key if you want a random key. This in fact is ideal. Many applications need a key that is based on a human-provided password, and to manage that you should use a KDF. But for computer-to-computer transactions, by all means you should use a random key.

  4. Ashar
    October 3rd, 2011 at 01:56 | #4

    @Rob Napier Thanks that’s very helpful. I just need to secure a file.

  5. Viral
    October 9th, 2011 at 16:27 | #5

    How can I use CCKeyDerivationPBKDF API for iOS development?

  6. Viral
    October 9th, 2011 at 16:35 | #6

    Could you please share sample code for iOS development? I am really impressed when I read your article so, I decided to discuss more about this. Advance Thanks.

  7. October 13th, 2011 at 14:07 | #7

    @Viral CCKeyDerivationPBKDF is available in iOS 5. If you must target iOS 4, then you can port the function from the open source CommonCrypto as noted in the article.

  8. Soniya
    October 22nd, 2011 at 03:58 | #8
    Soniya :

    Thanks a lot sir. I was looking for the same approach as this approach was already implemented by our .net and android team and to synchronize with them I left with no option. You saved me. A big thanks to you.

    But there is a problem I am using Xcode 3.2.6 version and there, it not able to find #import and give error no such file found. I found in Opensource but not able to attach it. There was no download option too. Not found any framework for it.

    if you think i am not sincerely working on it then I would like to tell you sir that its my priority I am working very hard for it.

    Thanks a lot for this code.

  9. Soniya
    October 24th, 2011 at 06:21 | #9

    rob sir please tell me how to use above code in xcode 3.2.6. it show error with CommonCrypto/CommonKeyDerivation.h which it not able to find. I attach code of it directly from open source still not succeeded please help me for this. I have decrypt also but I am still not able to encrypt.

  10. October 24th, 2011 at 09:47 | #10

    Here is the latest version of the CommonCrypto source code: http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/. You should find what you need in CommonKeyDerivation.c.

  11. Jeffrey Goldberg
    November 29th, 2011 at 20:32 | #11

    Although PBKDF2 isn’t “in” OS X 10.6 and before, it is available through the OpenSSL libraries. The incantations aren’t pretty, but there is no reason for anyone to roll their own.

    Cheers,

    -j

  12. November 30th, 2011 at 09:44 | #12

    @Jeffrey Goldberg Thanks for the reminder. This is a reasonable solution for OS X 10.6. Note that OpenSSL is not available directly on iOS. For best portability, I’d probably still port the CommonKeyDerviation code rather than bring in OpenSSL, but if you already are using OpenSSL for other things it can be a good approach.

  13. December 14th, 2011 at 04:37 | #13

    Hi Rob, I’ve taken your code and tried to create a decrypt method, which indeed is almost identical to encrypt. The bit I’m stuck on is the length of the output data. How do I know what length this should be? Can I determine this from the length of the encrypted data?

  14. December 14th, 2011 at 10:50 | #14

    @Rew Great question. The length of the encrypted data is at least as long as the plaintext, and no more than one block (16 bytes) longer. You’ll notice that for encryption, I add kAlgorithmBlockSize to the size of the input size to get the output size. For decryption, you should allocate an output block equal to your input block. Or you can always add one block (like the above code) if you’re making a routine that can encrypt or decrypt. It just might be 16 bytes too large in some cases.

    For those looking for a longer example, the sample files from my book are now available at the Wiley site. The above code comes from Chapter 11.

  15. Husayn
    December 30th, 2011 at 15:46 | #15

    Hey Rob, I was just wondering, how can I change this to AES 256? I can’t seem to find the list of identifiers and I want more secure encryption. Thanks!

  16. Husayn
    December 30th, 2011 at 16:04 | #16

    Nevermind! I just change the kCCKeySizeAES128 to 256.

  17. January 4th, 2012 at 21:25 | #17

    @Husayn Thanks for coming by. Yes, the code is designed to be that simple to modify. It’s why I put all the constants at the top (even those that today make no sense to modify; someday you might be able to).

  18. Richard Fink
    January 10th, 2012 at 20:29 | #18

    Rob, when you decrypt data, it comes back from CommonCrypto as NSData. My efforts to then convert it to an NSString for display fail:

    NSString *encryptedString = [[[NSString alloc] initWithData:encryptedData encoding:NSUTF8StringEncoding] autorelease];
    

    encryptedString returns nil indicating an error in the method. What is the approach to convert output from CommonCrypto from NSData to NSString ?

    I realize your wring here is about encrypt… I’m just stepping forward in the story. Thanks.

    -Ric

  19. Richard Fink
    January 10th, 2012 at 20:32 | #19

    My line of code may h ve been lost - NSString *encryptedString = [[[NSString alloc] initWithData:encryptedData encoding:NSUTF8StringEncoding] autorelease];

  20. January 11th, 2012 at 13:40 | #20

    @Richard Fink Was the original input a UTF-8 encoded string (versus some other encoding such as a Windows CP encoding or UTF-16 encoding)? Make sure that the NSData itself is not nil. If it is, that suggests you have a decryption error. If not, take a look at the data directly. Does it appear to be a UTF-8 string? (In the debugger, use p (char*)[data bytes].)

  21. Richard Fink
    January 14th, 2012 at 18:08 | #21

    @Rob Napier Well, the original data that was encrypted was simple ASCII character data. So I think that qualifies as UTF-8 encoding, am I wrong there ?

  22. January 14th, 2012 at 18:23 | #22

    @Richard Fink If it’s 7-bit ASCII (real ASCII, not just “text”), then yes, that is a subset of the UTF-8 (8-bit) encoding space. What does the decoded data look like? Does it look like a string of characters?

  23. Ranjit
    February 2nd, 2012 at 10:02 | #23

    Hey rob,your article is very informative , and I am using your code,but I have a doubt, according to your code snippet above, you said to pass the IV and salt while encryption, but in the function encrypteddatafordata ,it uses random salt and IV.so if it uses random generator to produce salt and IV ,then how it can be decrypted on server side,also what salt and IV should be,( a number or string)..waiting for your reply

    • February 2nd, 2012 at 12:51 | #24

      The salt and IV that are generated during encryption should be stored with the ciphertext and sent to the decryptor (you will notice that these are returned by reference). The decryptor then uses the same salt and IV.

  1. November 30th, 2011 at 00:39 | #1