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 GitHub. This comes from Chapter 11, “Batten the Hatches with Security Services.”

Update2: The things described here are handled automatically by RNCryptor, which is an easier approach unless you want to write your own solution.

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. Carey
    May 9th, 2012 at 16:25 | #1

    I’m completely confused now. I just thought that encrypting and decrypting the data was the easiest safest way. I’m new to security and don’t really know how I should be handling data or which way is the best way. I don’t know how to get the server to respond to the AuthenticationChallenge. I often don’t have access to a webserice’s server. I wonder if perhaps you would be willing to assist me further in understanding. I would compensate you for your time. This is a huge weakness in my iOS programming skills and I would like to improve it and understand it much better. Would you be interested?

  2. Adam
    May 31st, 2012 at 13:28 | #2

    Hey Rob, I was wondering if you had a chance to update your RNCryptor code to interact with didReceiveData? Thanks!

  3. May 31st, 2012 at 14:16 | #3

    @Adam No, still in progress.

  4. nicki
    June 1st, 2012 at 08:16 | #4

    Hi Any chance of releasing a version of RNCryptor for OSx 10.6?

    • June 1st, 2012 at 08:50 | #5

      This is basically the same as ios 4. See issue 22 for details. I probably won’t look at this until I finish issue 21.

  5. JJ
    June 4th, 2012 at 15:42 | #6

    While I trying to compile the RNCryptor code with an Application program, it gives me an link error: Undefined symbols for architecture i386: “OBJC_CLASS$_RNCryptor”, referenced from: objc-class-ref in TViewController.o ld: symbol(s) not found for architecture i386 clang: error: linker command failed with exit code 1 (use -v to see invocation)

    I looked around they said this error is because of the Security.framework is not in library. But I’m sure I have it in the project summary and Build Phases->Link Binary With tab. From the ld command before the error, I can also verify the ld has the “-framework Security -framework UIKit -framework Foundation -framework CoreGraphics”.

    Interesting part is when I create the project as an iOS static library, it does compile without the error.

    I’m using xcode 4.3.2 on Mac Lion.

    Could you please point out where went wrong?

    Thanks JJ

    • June 4th, 2012 at 16:04 | #7

      You aren’t linking RNCryptor.o. If you dropped the .m files into your project, then you forgot to add them to the target. Click the .m files in Xcode and look at the Assistant to see what targets they’re assigned to. If you’re using the static library, then you’ve forgotten to actually link it.

  6. JJ
    June 4th, 2012 at 16:27 | #8

    Got it, thank you :) After I check the check box before the target, it compiles.

  7. Adam
    June 7th, 2012 at 11:54 | #9

    The didReceiveData interaction is the same issue as #21 on your list, correct? I’ll keep a watch on your repo, I’m looking forward to this enhancement :)

  8. June 7th, 2012 at 11:57 | #10

    @Adam Correct. Compatibility with didReceiveData is #21. This will likely be a fairly radical interface change. It may or may not be 100% compatible with the old API (though I will try to keep it similar).

  9. June 9th, 2012 at 11:56 | #11

    I was trying to compile this to run with a ios 5 project of mine but i keep getting a expectedprimary-expression before ‘.’ token error from line 50 t0 60 in rncryptor.h .. Any way i could solve this?

  10. June 9th, 2012 at 13:51 | #12

    @Aditya are you using the github RNCryptor? https://github.com/rnapier/RNCryptor

  11. DK
    June 11th, 2012 at 19:15 | #13

    Hey Rob, any idea when the update will be coming out? No rush, just curious :)

    • June 15th, 2012 at 11:56 | #14

      I’m just finishing up WWDC today and plan to start hacking on this next week. I hope to have something by the end of June.

  12. Adam
    June 26th, 2012 at 13:04 | #15

    Hi Rob, I have a file I’m breaking up in to fixed size chunks, encrypting, and appending to a file. When I read the file, I read in a chunk, and decrypt it. I thought this would be pretty straightforward, until I realized the encrypted chunks were not the same size as the decrypted chunks. Is there some way to know exactly how big the encrypted chunks will be? Thanks!

    • June 26th, 2012 at 14:28 | #16

      I’m not exactly certain what you mean here. First, are you using the version of RNCryptor on master, or on the async branch? The async version is probably better suited to this problem. RNCyptor prepends some header information to the beginning of the data, and appends an HMAC, so the final result is going to be a bit bigger than the plaintext. If you use CBC mode (which RNCryptor has switched to), then there is some extra padding at the end of the file as well. Can you explain where you’re seeing “the encrypted chunks were not the same size as the decrypted chunks” and particularly why this matters?

      But to your main question, definitely if you are using the version of RNCryptor on master, and are handing it chunks of data, the resulting data will be huge, since each chunk will get a full header and its own HMAC. This is the kind of use case the async changes (on the async branch) are intended to address.

  13. Adam
    June 26th, 2012 at 16:05 | #17

    I’m using the master branch. What I’m doing is basically encrypting data as it comes in 10k blocks, and appending it to my encrypted file. When I want to read the file I similarly decrypt it block by block.

    If I encrypt a chunk of data of size 10240 I get an encrypted data of size 10306- which isn’t a huge difference, I’m fine with that. I guess I’m just wondering if it’s possible to turn the padding off so I know for sure what the data size will be? Although I’m always dealing with data blocks of the same size, so it shouldn’t be a big problem either way.

    I’ll switch to the async branch now that it’s up and running, although I’m getting close to a release now, I’ll probably wait until the next update to swap it out.

  14. June 26th, 2012 at 16:21 | #18

    On master, there is no padding (it’s in CTR mode, which does not require padding). The extra bytes are the RNCryptor header and HMAC. These always will be exactly 66 bytes per encryption (on master; the metadata changes slightly on async, and there’s padding).

  15. Adam
    June 26th, 2012 at 18:42 | #19

    Ah ok, thanks!

  16. Adam
    July 3rd, 2012 at 18:00 | #20

    Is it advisable to remove the header and hmac, or are these required for the library to work properly?

  17. July 4th, 2012 at 14:47 | #21

    @Adam Removing the header and HMAC would significantly degrade the security of the system. It is not recommended.

  18. Edgar
    August 20th, 2012 at 12:33 | #22

    Hallo Rob, using your grateful Cryptor in this way:

    NSLog(@"to      encrypt:%@",[[self inputField] stringValue]);
    NSData *data = [[[self inputField] stringValue] dataUsingEncoding:NSUTF8StringEncoding];
    NSError *error;
    NSData *encryptedData = [RNEncryptor encryptData:data
                                        withSettings:kRNCryptorAES256Settings
                                            password:@"1a23abc"
                                               error:&error];
    NSLog(@"encrypted   hex:%@",[encryptedData  hexadecimalString]);

    NSData *decryptedData = [RNDecryptor decryptData:encryptedData withPassword:@"1a23abc" error:&error];

    NSLog(@"decrypted plain:%@",[NSString stringWithUTF8String:[decryptedData bytes]]); NSLog(@"decrypted hex:%@",[decryptedData hexadecimalString]);

    ..outputs me correct results in case of input-string.length = 1..6, 12 and maybe so on… Example: 2012-08-20 18:27:42.092 TestCrypt[2054:403] to encrypt:abcdef 2012-08-20 18:27:42.121 TestCrypt[2054:403] encrypted hex: 0101e25f362ee7e3fa9253e7d3a559f38e7bcc2c2632e85b73f681c0d6ded2b9c5a88945b832faae228db42c6c50f66ea900640ffc52b6a3b86ed59e768be70e7202c6994cb0a00f87fa47e329a521bfd2d5 2012-08-20 18:27:42.151 TestCrypt[2054:403] decrypted plain:abcdef 2012-08-20 18:27:42.151 TestCrypt[2054:403] decrypted hex:616263646566

    but incorrect results in case of input-string.length = 7..11, 13.. and maybe so on… Example: 2012-08-20 18:28:41.909 TestCrypt[2054:403] to encrypt:abcdefg 2012-08-20 18:28:41.938 TestCrypt[2054:403] encrypted hex: 010130a01f3f4a39aab31fdcfacfd737749238e8f0e1cbc2589caae48f9d473737eeb7684aac7f002f430a15c95f6afbd2f7057d975a51c9e6fbee63cc362841b775aeeecf770b37baa0e26a8f093d7eb48f 2012-08-20 18:28:41.967 TestCrypt[2054:403] decrypted plain:abcdefg0v& 2012-08-20 18:28:41.967 TestCrypt[2054:403] decrypted hex:61626364656667

    What is wrong with me? sincerly, Edgar

  19. August 20th, 2012 at 15:06 | #23

    @Edgar It’s working fine. You’re just converting back to a string incorrectly. This is not correct: [NSString stringWithUTF8String:[decryptedData bytes]]. You should be creating the string this way: [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding].

    You’re getting lucky that in some cases your NSData happens to have a \0 immediately after it. This is not promised. Using stringWithUTF8String expects a \0 at the end of the bytes.

  20. Edgar
    August 20th, 2012 at 16:29 | #24

    Ups, I’m getting lucky that you hint me this way. Thank you

  21. September 13th, 2012 at 08:18 | #25

    I have been reading your documentation on the RNCryptor GitHub site regarding the Data Format. What are your thoughts on perhaps using the same file format as aescrypt (www.aescrypt.com)?

    • September 13th, 2012 at 10:15 | #26

      I’m looking over the code, and there are a few problems that make it hard to use this. (Your file format document does not actually explain how these fields are calculated, so I’m basing this on the code.) First, AESCrypt doesn’t use PBKDF2. It has a custom KDF that I haven’t seen an crypto analysis of. Most noteworthy is that it doesn’t have a salt (it effectively reuses the random IV as the salt). This might be ok, but again, I’ve seen no analysis of that approach. Second, it encrypts the data with a random key, which it then encrypts (together with the IV) with the derived key. I take it that the goal here is to speed up detecting a bad password? It does this with CBC with a NULL IV. It’s random data, so that’s possibly fine. The HMAC appears to be using the same KDF key. You generally should not reuse keys this way. Similarly, the data HMAC is created using the encryption key rather than its own.

      I don’t see gaping security holes in this format, but several of the decisions are non-standard approaches (particularly in the KDF) that I don’t believe have been well analyzed.

  22. September 13th, 2012 at 21:02 | #27

    @Rob Napier OK, thank you for your prompt feedback and comments.

  23. September 13th, 2012 at 23:18 | #28

    @Robert Kovacic Note that I’ve opened a discussion with the maintainers of AESCrypt (http://forums.packetizer.com/viewtopic.php?f=72&t=288). It’s an interesting format, and it would be nice to be compatible with a good cross-platform commandline tool.

  24. Brent Roberts
    October 3rd, 2012 at 17:38 | #29

    I have been using your example code to retrieve a stream of an encrypted file from a Java servlet and decrypt it in iOS. Thank for you for providing nicely written and documented code.

    This worked fine in iOS 5 and, if given the proper password, it works fine in iOS 6. What is not working in iOS 6 is when an incorrect password is supplied. In iOS 5, the CCCrypt call would return with an error code > 0 and I could supply a meaningful error dialog. With iOS 6, the returned error code is always 0, so I do not know the supplied password was incorrect.

    Has anyone encountered this problem?

    Thanks, Brent

    • October 3rd, 2012 at 17:57 | #30

      I’m not certain what your code looks like exactly, but I’ll assume you’re not using an HMAC. If not, then there is not, in general, a way to determine that the password was incorrect. If you use CBC mode with PKCS#7 padding, then in about 99.6% of cases, you’ll get away with it because the padding will be wrong. But in about 0.4% of random data (and if your data isn’t random, then it could be much more often), the padding just happen to work. If you use CTR mode without PKCS#7 padding, then it can never tell you that the decryption was incorrect.

      AES does not provide authentication. AES will happily decrypt any random blob of data with any random key, and give you “something.” If an attacker knows the plaintext, then it’s even possible to modify AES-encrypted data so that when it decrypts, the resulting plaintext is some other desired value (this is possible without knowing the key). AES does not provide authentication.

      The most secure way to check that the password was correct is to use an HMAC. RNCryptor does this, but the example code in this blog post does not. The disadvantage of this approach is that you have to decrypt the entire blob before determining that the password was wrong. But it is the most secure approach, and often just fine. If it’s a problem, then another approach is to encrypt some known value with the key and a new random IV. You can then attempt to decrypt that known value prior to attempting to decrypt the real document. This makes it much faster to determine that the password is bad, but also makes it easier on an attacker to try to brute-force the password. Even if you use a “short-cut” test like this, you should still use an HMAC. As I said…. AES does not provide authentication.

  25. Brent Roberts
    October 3rd, 2012 at 18:21 | #31

    Thanks Rob. I am certainly not a security expert and appreciate your willingness to help others understand.

    On the Java side, I am creating a AES 256 key using PBKDF2WithHmacSHA1 and using it to cipher the data using AES/CBC/PKCS5Padding (the standard Java security library does not provide a PKCS7Padding option).

    On iOS, I am basically using your example code as is to decrypt a stream with the IV and salt at the beginning of the stream.

    So, I guess I’m still confused on why iOS 5 would always provide an error code > 0 when the incorrect password was supplied and iOS 6 always returns a success (0) code.

  26. October 4th, 2012 at 10:27 | #32

    @Brent Roberts First, have you confirmed that it’s identical data you’re encrypting/decrypting in both cases? The point is that you can verify that the password is correct on some kinds of data, but you cannot on others. If you are encrypting different things, you may find that some of those things cannot verify passwords using AES without an HMAC. (PBKDF2WithHmacSHA1 does not mean you’re using an HMAC; that’s a different thing related to key generation, not authentication.) I would first suspect the data rather than the OS version.

  27. Brent Roberts
    October 4th, 2012 at 11:00 | #33

    @Rob Napier Thanks for your reply. If you don’t mind, I would like to describe what I am trying to accomplish.

    We have a service for customers to upload files to their own area on our server and these files are associated with other meaningful information. They authenticate separately to the web UI and then enter an encryption password when the files are uploaded and saved. The encryption is done to ensure the files can only be decrypted by our iPad app. These can be sensitive documents so we don’t want even our employees to be able to read them. We do not store the password on our servers and it is only passed in the upload request and then forgotten.

    On the iPad, user authentication is handled separately and the encryption password is entered and used to decrypt the downloaded files. The files are streamed via HTTPS with the IV and salt at the beginning. I write the received data to a file in the temp directory and then use your code for decrypting a stream to write the real file in the app’s document directory.

    As long as the encryption password is entered correctly everything works fine on both iOS 5 and 6. I am just trying to provide a meaningful message if the password was not entered correctly by the iPad user. I can have 2 iPads side-by-side, one iOS 5 and the other iOS 6, and the iOS 5 one will display the message as the error code is > 0 while the iOS 6 one does not because the error code is 0.

    Do you think I am on the right path with this approach?

    Thanks again for your help and comments, Brent

    • October 4th, 2012 at 12:36 | #34

      Your approach is ok. When you say you are testing with two iPads, are they encrypting exactly the same document (and does it happen repeatedly, even when you generate different IVs)? The issue is that in general, AES cannot detect a bad password. Bad password detection is a “lucky” side-effect of using padding in CBC mode. That works for most, but not all data. If the bad password causes the decryption to just happen to end in 0×01, then you won’t get an error. (There are some other cases that fail, but that’s the most likely, occurring randomly 0.4% of the time.)

      In any case, AES cannot reliably do what you’re trying to do. You should use an HMAC to verify that the resulting document is correct. See RNCryptor for an example of that. Look particularly at RNEncryptor.m and RNDecryptor.m at the calls to CCHmac*. Note that the HMAC requires its own salt. See the Data Format page for how RNCryptor encodes its output.

      An HMAC is basically a secure checksum. Think of it as an encrypted checksum (it isn’t exactly, but thinking of it that way should make it clearer). It supports password checking like this (roughly; the math is a little different): Using the password and salt, decrypt the checksum. Make sure the checksum matches the encrypted data. If it doesn’t, then either the encrypted data was modified, or the password is incorrect. It still isn’t possible to know the difference between those two cases.

      If you want to know specifically if the password is wrong, separate from whether the file was modified, then the best way to do that is to prepend the plaintext with some known header. 16 bytes is probably best (I need to think about that some more, but I would use 16 bytes). Then when you decrypt, you can check that the header decrypted correctly and strip it off. If it didn’t, then the password is wrong. This tells you nothing about whether the file has been altered, but does reliably check the password.

  28. Brent Roberts
    October 7th, 2012 at 00:54 | #35

    @Rob Napier I wanted to follow-up and say that your suggestion works well. It got a little tricky since the 16 byte header value needs to be run thru the cipher engine in the same pass as the file’s contents. Once I got that working on the Java side, on the iOS side I modified your code a little to verify the first 16 bytes that were decrypted from the stream matched the well-known value. If it doesn’t I display a meaningful error dialog. If it matches, I remove the first 16 bytes from the buffer and write to the file. This works correctly on iOS 5 and 6.

    Thanks again for your help!

  29. Stanislav
    November 14th, 2012 at 14:39 | #36

    Hi. What about crypting using streams? How to decrypt data with output stream and encrypt with input stream?

    • November 14th, 2012 at 16:04 | #37

      Older versions of RNCryptor included NSStream support, but I believe the new asynchronous interface is more flexible and recommend that. Let me know if there’s a specific use case that is much easier to implement with NSStream rather than the async API.

  30. Stanislav
    November 14th, 2012 at 16:32 | #38

    @Rob Napier I have to set NSInputStream in setHTTPBodyStream: of NSMutableURLRequest. And I want this stream do encrypt data. What part of your API should I use and how?

    • November 14th, 2012 at 17:50 | #39

      That’s an interesting approach to uploading POST data. If it’s a small amount of data, just encrypt it and set it as the HTTP body rather than the stream. If it’s a large amount of data (more than you want to hold in memory), or if you’re already using the stream for other reasons, then you’ll need to build a stream delegate that takes each part of the data and calls RNCryptor addData. I’ve contemplated creating this kind of shim, and may still do it since this is a pretty interesting use case. But I won’t be able to look at it until at least several weeks. You can open an issue at the github site.

  31. Stanislav
    November 15th, 2012 at 14:10 | #40

    Well, yes it’s mainly because of large amount of data. I’ll try to use this addData : but I’m no sure if it will work with stream delegate because I thing system will set it’s own. Maybe I shall inherit. Anyway I’ll be interested in your solution. Can you open that issue? I’m sorry I don’t have GitHub account.

  32. December 19th, 2012 at 00:12 | #41

    Hi, I’m having some trouble getting Triple DES encryption working with the RNCryptor library and I’m hoping you can help. I’m trying to encrypt a string on the command line using OpenSSL like so:

    $ echo “I was encrypted by OpenSSL on the command line” | openssl enc -des3 -k “abc” -a U2FsdGVkX19jgmvEmfAMjOh1swMCXMUExIGyn36n74Laumew2AytjovCaf6MtV/0x2s0+4Mou1c1o3bdoEO/fA==

    Then I try to decrypt it like so:

    static const RNCryptorSettings kRNCryptor3DESSettings = { .algorithm = kCCAlgorithm3DES, .blockSize = kCCBlockSize3DES, .IVSize = kCCBlockSize3DES, .options = kCCOptionPKCS7Padding, .keySettings = { .keySize = kCCKeySize3DES } };

    NSError *error = nil;
    

    NSString *encryptedBase64EncodedString = @”U2FsdGVkX19jgmvEmfAMjOh1swMCXMUExIGyn36n74Laumew2AytjovCaf6MtV/0x2s0+4Mou1c1o3bdoEO/fA==”; NSData *encryptedData = [NSData decodeBase64ForString:encryptedBase64EncodedString]; NSData *decryptedData = [RNOpenSSLDecryptor decryptData:encryptedData withSettings:kRNCryptor3DESSettings password:@"abc" error:&error]; NSString *decryptedString = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]; NSLog(@”decryptedString: %@”, decryptedString);

    However, this results in the following exception:

    2012-12-18 20:04:38.756 Starbucks[81866:c07] *** Assertion failure in NSData *RNOpenSSLCryptorGetIV(NSData *__strong, NSString *__strong, NSData *__strong, RNCryptorKeyDerivationSettings)(), /Users/jmorgan/Developer/Projects/Starbucks/master/ThirdParty/RNCryptor/RNCryptor/RNOpenSSLCryptor.m:91

    When I try any permutation of the kRNCryptor3DESSettings I occasionally get a null decrypted string. No matter what I try I always get an exception or a null result.

    Any help would be greatly appreciated!

    • December 19th, 2012 at 13:45 | #42

      OpenSSL has a custom (non-standard) algorithm for creating the IV and key from the password. RNOpenSSLCryptor only supports the AES-256 generation algorithms for OpenSSL. You’d have to research what algoritm OpenSSL uses for 3DES. You could then write a new RNOpenSSLCryptorGetIV() and RNOpenSSLCryptorGetKey(). Alternately, you can use the “-P” parameter to openssl to ask it what IV and key it generated for a given password. You can then use those in decryptData:withSettings:encryptionKey:IV:error:.

  33. December 21st, 2012 at 17:32 | #43

    @Rob Napier Many thanks for the info. I tried that for a while and it wasn’t working, so ultimately I took the easy route and went with AES encryption (which works great).

  34. Timo
    December 22nd, 2012 at 16:57 | #44

    Hi Rob. Thanks for that great code snippets. Do you know if/how it’s possible to use this together with UIDocument/UIManagedDocument? These are saved with:

    saveToURL:forSaveOperation:completionHandler:

    (see: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIDocument_Class/UIDocument/UIDocument.html#//apple_ref/occ/instm/UIDocument/saveToURL:forSaveOperation:completionHandler:)

    I’m not sure how the encryption/decryption method (which uses NSData) can be combined with that…

    • December 23rd, 2012 at 20:16 | #45

      You could attach encrypted data to a managed object as a property, but you would need to encrypt and decrypt it each time you accessed it.

      You can also implement an NSAtomicStore that encrypted all the data in save: and decrypted it in load:. This would probably be fairly straight-forward. Implementing an incremental store like this would likely be quite complicated, however.

      Using just UIDocument (rather than Core Data), you should be able to override contentsForFileType:error: and loadFromContents:ofType:error: fairly simply to implement encryption and decryption.

      Make sure you know what problem you’re trying to solve. You should look into what the Data Protection APIs provide already, and make sure that doesn’t cover your use case.

  35. David DelMonte
    March 18th, 2013 at 21:01 | #46

    Rob, do I need to concern myself with security, if I have an core data / iCloud app where people do not import any data except for images? There are no http links.

    Thanks for all your work in this area. I hope this is legible, I have recently lost most of my sight and am trying to incorporate assistive use along with security into my apps.

    • March 19th, 2013 at 14:04 | #47

      The general rule for “do I need to concern myself with security” is “If this result were on the front page of the paper, along with the user’s name, would it be a problem?” If not, then you likely have no security concern. Otherwise you do.

  36. Bryan B
    March 19th, 2013 at 13:59 | #48

    Rob, I am using this approach for decryption in a program I am working on. Usually data comes in that matches the block size (like 16, 32, 1024). Sometimes data comes in that is not – like 334 bytes. I was expecting that the cccryptorstatus would return non zero for this. Instead it returned success and has 333 decrypted bytes! Is this the expected behavior?

    Thank you.

    • March 19th, 2013 at 14:07 | #49

      If this is AES, then there are no 32- or 1024-byte blocks. What “block size” do you mean? Are you applying PKCS#7 padding or not? Are you using CBC or some other mode? Modes like CTR have no problem decrypting any size ciphertext you like.

  37. David DelMonte
    March 19th, 2013 at 14:08 | #50

    @Rob Napier

    hmm. interesting. That seems like a reasonable measurement. Thank you.

  38. Bryan B
    March 19th, 2013 at 14:14 | #51

    Sorry about the lack of info – I am using AES (128 bit), CBC, PKCS7. 16, 32, 1024 was referring to the sizes of incoming messages. If I decrypt a message consisting of full blocks (multiples of 16) then there is no problem. However, what I am running into now is when attempting to decrypt an incomplete message. I had code that was expecting the library to return an error if the data was not a multiple of 16.

  39. March 19th, 2013 at 14:20 | #52

    @Bryan B Yes, if you pass a block that properly padded to 16 bytes (with PKCS#7 padding) to CCCryptor(), then it will fail (with kCCDecodeError if I remember right; though maybe it was kCCAlignmentError). If it’s not failing, then you’re probably passing something other than what you think you are.

  40. Bryan B
    March 19th, 2013 at 14:44 | #53

    @Rob Napier Thanks – you mean NOT properly padded to 16 bytes, right? Time to super sanity check the code. [a few minutes later] I am not insane (emailed screenshot).

    I think the size must be validated.

Comment pages
1 2 564
  1. November 30th, 2011 at 00:39 | #1
  2. July 6th, 2012 at 15:49 | #2
  3. August 31st, 2012 at 11:35 | #3
  4. February 4th, 2013 at 16:58 | #4
  5. March 18th, 2013 at 13:53 | #5
  6. May 2nd, 2013 at 22:55 | #6