127

Can anybody point me in the right direction to be able to encrypt a string, returning another string with the encrypted data? (I've been trying with AES256 encryption.) I want to write a method which takes two NSString instances, one being the message to encrypt and the other being a 'passcode' to encrypt it with - I suspect I'd have to generate the encryption key with the passcode, in a way that can be reversed if the passcode is supplied with the encrypted data. The method should then return an NSString created from the encrypted data.

I've tried the technique detailed in the first comment on this post, but I've had no luck so far. Apple's CryptoExercise certainly has something, but I can't make sense of it... I've seen lots of references to CCCrypt, but it's failed in every case I've used it.

I would also have to be able to decrypt an encrypted string, but I hope that's as simple as kCCEncrypt/kCCDecrypt.

Quinn Taylor
  • 44,553
  • 16
  • 113
  • 131
Boz
  • 1,646
  • 5
  • 15
  • 15

5 Answers5

128

Since you haven't posted any code, it's difficult to know exactly which problems you're encountering. However, the blog post you link to does seem to work pretty decently... aside from the extra comma in each call to CCCrypt() which caused compile errors.

A later comment on that post includes this adapted code, which works for me, and seems a bit more straightforward. If you include their code for the NSData category, you can write something like this: (Note: The printf() calls are only for demonstrating the state of the data at various points — in a real application, it wouldn't make sense to print such values.)

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSString *key = @"my password";
    NSString *secret = @"text to encrypt";

    NSData *plain = [secret dataUsingEncoding:NSUTF8StringEncoding];
    NSData *cipher = [plain AES256EncryptWithKey:key];
    printf("%s\n", [[cipher description] UTF8String]);

    plain = [cipher AES256DecryptWithKey:key];
    printf("%s\n", [[plain description] UTF8String]);
    printf("%s\n", [[[NSString alloc] initWithData:plain encoding:NSUTF8StringEncoding] UTF8String]);

    [pool drain];
    return 0;
}

Given this code, and the fact that encrypted data will not always translate nicely into an NSString, it may be more convenient to write two methods that wrap the functionality you need, in forward and reverse...

- (NSData*) encryptString:(NSString*)plaintext withKey:(NSString*)key {
    return [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
}

- (NSString*) decryptData:(NSData*)ciphertext withKey:(NSString*)key {
    return [[[NSString alloc] initWithData:[ciphertext AES256DecryptWithKey:key]
                                  encoding:NSUTF8StringEncoding] autorelease];
}

This definitely works on Snow Leopard, and @Boz reports that CommonCrypto is part of the Core OS on the iPhone. Both 10.4 and 10.5 have /usr/include/CommonCrypto, although 10.5 has a man page for CCCryptor.3cc and 10.4 doesn't, so YMMV.


EDIT: See this follow-up question on using Base64 encoding for representing encrypted data bytes as a string (if desired) using safe, lossless conversions.

Community
  • 1
  • 1
Quinn Taylor
  • 44,553
  • 16
  • 113
  • 131
  • 1
    Thanks. CommonCrypto is part of the Core OS on the iPhone, and I'm running 10.6 too. – Boz Sep 09 '09 at 18:46
  • 1
    I did -1, because the referenced code is dangerously insecure. Look at Rob Napier's answer instead. His blog entry" http://robnapier.net/aes-commoncrypto details exactly why this is insecure. – Erik Engheim May 29 '14 at 12:01
  • 1
    This solution doesn't work in my case. I have a string that I want to decode: U2FsdGVkX1+MEhsbofUNj58m+8tu9ifAKRiY/Zf8YIw= and I have the key: 3841b8485cd155d932a2d601b8cee2ec . I can't decrypt the string using the key with your solution. Thanks – George Jun 11 '14 at 16:13
  • This solution doesn't work in a Cocoa app on El Capitan with XCode7. ARC forbids the `autorelease`. – Volomike Jan 17 '16 at 00:59
  • @QuinnTaylor I can edit this answer, but wanted to give you the opportunity to change it as you see fit. [I repaired your code here](http://pastebin.com/cptcEJUA). Also, you may want to point out that without [that adapted code](http://pastie.org/426530), it won't compile. So, I got it to work on a Cocoa application on El Capitan with XCode7. Now what I'm trying to do is figure out how to Base64Encode/Base64Decode this data so that it's transmittable without being disturbed in transit, rather than return raw data. – Volomike Jan 17 '16 at 01:24
  • @QuinnTaylor Here's [the concise answer](http://pastebin.com/icm3TQn2) for you to rewrite your answer. I've made it work in XCode7 and included all parts. Plus, I added the base64encoding/decoding. Works great! I even tested with Japanese strings and it works -- so, UTF-8 is supported here. – Volomike Jan 17 '16 at 02:02
46

I have put together a collection of categories for NSData and NSString which uses solutions found on Jeff LaMarche's blog and some hints by Quinn Taylor here on Stack Overflow.

It uses categories to extend NSData to provide AES256 encryption and also offers an extension of NSString to BASE64-encode encrypted data safely to strings.

Here's an example to show the usage for encrypting strings:

NSString *plainString = @"This string will be encrypted";
NSString *key = @"YourEncryptionKey"; // should be provided by a user

NSLog( @"Original String: %@", plainString );

NSString *encryptedString = [plainString AES256EncryptWithKey:key];
NSLog( @"Encrypted String: %@", encryptedString );

NSLog( @"Decrypted String: %@", [encryptedString AES256DecryptWithKey:key] );

Get the full source code here:

https://gist.github.com/838614

Thanks for all the helpful hints!

-- Michael

Community
  • 1
  • 1
Michael Thiel
  • 2,434
  • 23
  • 21
  • NSString *key = @"YourEncryptionKey"; // should be provided by a user Can we generate a random secure 256-bit key, instead of one provided by user. – Pranav Jaiswal Apr 24 '12 at 09:45
  • The Jeff LaMarche link is broken – whyoz Feb 26 '18 at 21:38
  • @michael - can you please guide me in this https://stackoverflow.com/questions/63632975/generate-same-encrypted-string-aes-cbc-pkcs7padding-in-objective-c-net-and-and Thanks – Chandni Aug 31 '20 at 05:41
35

@owlstead, regarding your request for "a cryptographically secure variant of one of the given answers," please see RNCryptor. It was designed to do exactly what you're requesting (and was built in response to the problems with the code listed here).

RNCryptor uses PBKDF2 with salt, provides a random IV, and attaches HMAC (also generated from PBKDF2 with its own salt. It support synchronous and asynchronous operation.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Interesting code, and probably worth the points. What's the iteration count for the PBKDF2 and what do you calculate the HMAC over? I presume just the encrypted data? I could not find that easily in the provided documentation. – Maarten Bodewes Aug 14 '12 at 22:38
  • Look at "Best practice security" for the details. I recommend 10k iterations on iOS (~80ms on an iPhone 4). And yes, encrypt-than-HMAC. I'll probably look over the "Data format" page tonight to make sure it's up to date on v2.0 (the main docs are up to date, but I can't remember if I revised the data format page). – Rob Napier Aug 14 '12 at 22:48
  • Ah, yeah, found the number of rounds in the docs and looked the code. I see cleanup functions and separate HMAC and encryption keys in there. If time permits I'll try and take a deeper look tomorrow. Then I'll assign the points. – Maarten Bodewes Aug 14 '12 at 23:06
  • I see RNCryptor in https://github.com/rnapier/RNCryptor but it seems to be impossible to encrypt (and decrypt) from NSString to NSString. I'm very interested to this possibility, is there any news? – ndman Oct 05 '12 at 19:57
  • 5
    Encrypt to NSData, and use one of the many Base64 encoders to convert that to a string. There is no way to encrypt from a string to a string without a data-to-string encoder. – Rob Napier Oct 07 '12 at 17:40
  • I have tried to use RNCryptor to decrypt a string, but with no success. I have a string that I want to decode: U2FsdGVkX1+MEhsbofUNj58m+8tu9ifAKRiY/Zf8YIw= and I have the key: 3841b8485cd155d932a2d601b8cee2ec . I can't decrypt the string using RNDecrypt – George Jun 11 '14 at 16:16
  • You have to use the same AES format for encryption and decryption. There is no standard AES format. If you want to use RNCryptor for decryption, you need to use RNCryptor for encryption. – Rob Napier Jun 11 '14 at 17:38
  • @RobNapier If i use RNCryptor, then should i use **Export Compliance Information (YES)** on iTunes-Connect ? – Jack Jul 27 '18 at 06:48
  • 1
    @Jack On advice of my lawyer (who described my lack of expertise in export compliance law in extremely colorful terms…), I no longer give advice on export compliance law. You'll need to discuss with your lawyer. – Rob Napier Jul 27 '18 at 13:08
  • @RobNapier I have tried your enccryption but is it possible that we generate same encrypted string in objective c , Java and in .Net. I have used same key for all but getting different encrypted string in iOS. Can you please guide me. thanks Here is my question for this. https://stackoverflow.com/questions/63632975/generate-same-encrypted-string-aes-cbc-pkcs7padding-in-objective-c-net-and-and – Chandni Aug 31 '20 at 05:39
  • @Chandni A secure encryption scheme will give you a different encryption every time you run it. That's intentional. In the case of RNCryptor, it includes 3 large random numbers specifically to achieve this, but all secure encryption schemes do this in some way (by injecting some piece of random data). There are some very specialized exceptions to the "every encryption creates a unique output," but as a rule you should expect it. You can find interoperable ObjC, Java and C# implementations of RNCryptor here: https://github.com/RNCryptor – Rob Napier Aug 31 '20 at 13:52
13

I waited a bit on @QuinnTaylor to update his answer, but since he didn't, here's the answer a bit more clearly and in a way that it will load on XCode7 (and perhaps greater). I used this in a Cocoa application, but it likely will work okay with an iOS application as well. Has no ARC errors.

Paste before any @implementation section in your AppDelegate.m or AppDelegate.mm file.

#import <CommonCrypto/CommonCryptor.h>

@implementation NSData (AES256)

- (NSData *)AES256EncryptWithKey:(NSString *)key {
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or 
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                     keyPtr, kCCKeySizeAES256,
                                     NULL /* initialization vector (optional) */,
                                     [self bytes], dataLength, /* input */
                                     buffer, bufferSize, /* output */
                                     &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {
        //the returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }

    free(buffer); //free the buffer;
    return nil;
}

- (NSData *)AES256DecryptWithKey:(NSString *)key {
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or 
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesDecrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                     keyPtr, kCCKeySizeAES256,
                                     NULL /* initialization vector (optional) */,
                                     [self bytes], dataLength, /* input */
                                     buffer, bufferSize, /* output */
                                     &numBytesDecrypted);

    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); //free the buffer;
    return nil;
}

@end

Paste these two functions in the @implementation class you desire. In my case, I chose @implementation AppDelegate in my AppDelegate.mm or AppDelegate.m file.

- (NSString *) encryptString:(NSString*)plaintext withKey:(NSString*)key {
    NSData *data = [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
    return [data base64EncodedStringWithOptions:kNilOptions];
}

- (NSString *) decryptString:(NSString *)ciphertext withKey:(NSString*)key {
    NSData *data = [[NSData alloc] initWithBase64EncodedString:ciphertext options:kNilOptions];
    return [[NSString alloc] initWithData:[data AES256DecryptWithKey:key] encoding:NSUTF8StringEncoding];
}
Volomike
  • 23,743
  • 21
  • 113
  • 209
  • Note: 1. On decrypt the output size will be less than the input size when there is padding (PKCS#7). There is no reason to increase the bufferSize, just use the encrypted data size. 2. Instead of malloc'ing a buffer and then `dataWithBytesNoCopy` just allocate an `NSMutableData` with `dataWithLength` and use the `mutableBytes` property for the byte pointer and then just resize by setting it's `length` property. 3. Using string directly for an encryption is very insecure, a derived key should be used such as created by PBKDF2. – zaph Jan 19 '16 at 01:15
  • @zaph, can you do a pastebin/pastie somewhere so that I can see the changes? BTW, on the code above, I merely adapted the code I saw from Quinn Taylor in order to make it work. I'm still learning this business as I go, and your input will be very useful to me. – Volomike Jan 19 '16 at 04:14
  • See this [SO answer](http://stackoverflow.com/a/23641521/451475) and it even has minimum error handling and handles both encryption and decryption. There is no need to extent the buffer on decryption, it is just less code not specializing with an additional if when there is little to be gained. In case extending the key with nulls is desired (that shouldn't be done) just create a mutable version of the key and set the length: `keyData.length = kCCKeySizeAES256;`. – zaph Jan 19 '16 at 04:42
  • See this [SO answer](http://stackoverflow.com/a/34867174/451475) for using PBKDF2 to create a key from a string. – zaph Jan 19 '16 at 05:41
  • @Volomike If i use this, then should i select **Export Compliance Information (YES)** on iTunes-Connect ? – Jack Jul 27 '18 at 06:50
0
Please use the below mentioned URL to encrypt string using AES excryption with 
key and IV values.

https://github.com/muneebahmad/AESiOSObjC

SURESH SANKE
  • 1,653
  • 17
  • 34