Encrypt NSString with AES-128 and Key

I have an application for basic notes, and I want to allow the user to have encrypted or protected notes. I have a user interface, but right now I cannot get encryption to work. Either he returns me a bunch of garbage, or nothing at all. This is what I use for en / decrypt:

- (BOOL) encryptWithAES128Key: (NSString *) key { // 'key' should be 16 bytes for AES128, will be null-padded otherwise char * keyPtr[kCCKeySizeAES128+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]; // encrypts in-place, since this is a mutable data object size_t numBytesEncrypted = 0; CCCryptorStatus result = CCCrypt(kCCEncrypt, kCCAlgorithmAES128 , kCCOptionPKCS7Padding, keyPtr, kCCKeySizeAES128, NULL /* initialization vector (optional) */, [self mutableBytes], [self length], /* input */ [self mutableBytes], [self length] + kCCBlockSizeAES128, /* output */ &numBytesEncrypted); return (result == kCCSuccess); } - (NSMutableData *) decryptWithAES128Key: (NSString *) key { // 'key' should be 16 bytes for AES128, will be null-padded otherwise char * keyPtr[kCCKeySizeAES128+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]; // encrypts in-place, since this is a mutable data object size_t bufferSize = [self length] + kCCBlockSizeAES128; void* buffer = malloc(bufferSize); size_t numBytesEncrypted = 0; CCCryptorStatus result = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, keyPtr, kCCKeySizeAES128, NULL /* initialization vector (optional) */, [self bytes], [self length], /* input */ buffer, bufferSize, /* output */ &numBytesEncrypted); if(result == kCCSuccess || result == kCCParamError) { return [[NSMutableData dataWithBytesNoCopy:buffer length:numBytesEncrypted] retain]; } return nil; } 

Can anyone understand why this might go wrong?

Edit 1: I revised my en / decryption code to be the same. Here's what it looks like right now:

 - (BOOL) encryptWithAES128Key: (NSString *) key { CCCryptorStatus ccStatus = kCCSuccess; // Symmetric crypto reference. CCCryptorRef thisEncipher = NULL; // Cipher Text container. NSData * cipherOrPlainText = nil; // Pointer to output buffer. uint8_t * bufferPtr = NULL; // Total size of the buffer. size_t bufferPtrSize = 0; // Remaining bytes to be performed on. size_t remainingBytes = 0; // Number of bytes moved to buffer. size_t movedBytes = 0; // Length of plainText buffer. size_t plainTextBufferSize = 0; // Placeholder for total written. size_t totalBytesWritten = 0; // A friendly helper pointer. uint8_t * ptr; // Initialization vector; dummy in this case 0's. uint8_t iv[kCCBlockSizeAES128]; memset((void *) iv, 0x0, (size_t) sizeof(iv)); plainTextBufferSize = [self length]; ccStatus = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, (const void *)[key UTF8String], kCCKeySizeAES128, (const void *)iv, &thisEncipher); // Calculate byte block alignment for all calls through to and including final. bufferPtrSize = CCCryptorGetOutputLength(thisEncipher, plainTextBufferSize, true); // Allocate buffer. bufferPtr = [self mutableBytes]; // Zero out buffer. //memset((void *)bufferPtr, 0x0, bufferPtrSize); // Initialize some necessary book keeping. ptr = bufferPtr; // Set up initial size. remainingBytes = bufferPtrSize; // Actually perform the encryption or decryption. ccStatus = CCCryptorUpdate(thisEncipher, (const void *) [self bytes], plainTextBufferSize, ptr, remainingBytes, &movedBytes); ptr += movedBytes; remainingBytes -= movedBytes; totalBytesWritten += movedBytes; // Finalize everything to the output buffer. ccStatus = CCCryptorFinal(thisEncipher, ptr, remainingBytes, &movedBytes); cipherOrPlainText = [NSData dataWithBytes:(const void *)bufferPtr length:(NSUInteger)totalBytesWritten]; NSLog(@"data: %@", cipherOrPlainText); NSLog(@"buffer: %s", bufferPtr); CCCryptorRelease(thisEncipher); thisEncipher = NULL; if(bufferPtr) free(bufferPtr); } - (NSMutableData *) decryptWithAES128Key: (NSString *) key { CCCryptorStatus ccStatus = kCCSuccess; // Symmetric crypto reference. CCCryptorRef thisEncipher = NULL; // Cipher Text container. NSData * cipherOrPlainText = nil; // Pointer to output buffer. uint8_t * bufferPtr = NULL; // Total size of the buffer. size_t bufferPtrSize = 0; // Remaining bytes to be performed on. size_t remainingBytes = 0; // Number of bytes moved to buffer. size_t movedBytes = 0; // Length of plainText buffer. size_t plainTextBufferSize = 0; // Placeholder for total written. size_t totalBytesWritten = 0; // A friendly helper pointer. uint8_t * ptr; // Initialization vector; dummy in this case 0's. uint8_t iv[kCCBlockSizeAES128]; memset((void *) iv, 0x0, (size_t) sizeof(iv)); plainTextBufferSize = [self length]; ccStatus = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, (const void *)[key UTF8String], kCCKeySizeAES128, (const void *)iv, &thisEncipher); // Calculate byte block alignment for all calls through to and including final. bufferPtrSize = CCCryptorGetOutputLength(thisEncipher, plainTextBufferSize, true); // Allocate buffer. bufferPtr = malloc( bufferPtrSize * sizeof(uint8_t) ); // Zero out buffer. memset((void *)bufferPtr, 0x0, bufferPtrSize); // Initialize some necessary book keeping. ptr = bufferPtr; // Set up initial size. remainingBytes = bufferPtrSize; // Actually perform the encryption or decryption. ccStatus = CCCryptorUpdate(thisEncipher, (const void *) [self bytes], plainTextBufferSize, ptr, remainingBytes, &movedBytes); ptr += movedBytes; remainingBytes -= movedBytes; totalBytesWritten += movedBytes; // Finalize everything to the output buffer. ccStatus = CCCryptorFinal(thisEncipher, ptr, remainingBytes, &movedBytes); cipherOrPlainText = [NSData dataWithBytes:(const void *)bufferPtr length:(NSUInteger)totalBytesWritten]; NSLog(@"data: %@", cipherOrPlainText); NSLog(@"buffer: %s", bufferPtr); CCCryptorRelease(thisEncipher); thisEncipher = NULL; if(bufferPtr) free(bufferPtr); return [NSMutableData dataWithData:cipherOrPlainText]; } 

This code works. If I encrypt this string with the passphrase '1234567890123456':

 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>dict</key> <dict> <key>device</key> <string>Tristan Magical Macbook of Death</string> <key>text</key> <string>e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxMDM4XGNvY29hc3VicnRm MzYwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEhlbHZldGljYTt9Cntc Y29sb3J0Ymw7XHJlZDI1NVxncmVlbjI1NVxibHVlMjU1O30KXHBhcmRcdHg1NjBc dHgxMTIwXHR4MTY4MFx0eDIyNDBcdHgyODAwXHR4MzM2MFx0eDM5MjBcdHg0NDgw XHR4NTA0MFx0eDU2MDBcdHg2MTYwXHR4NjcyMFxxbFxxbmF0dXJhbFxwYXJkaXJu YXR1cmFsCgpcZjBcZnMyNCBcY2YwIFx1bCBcdWxjMCBCTEFILn0= </string> <key>title</key> <string>Welcome to Notepaddy!</string> <key>uuid</key> <string>5yvghz9n4ukgefnbx0qa2xne3nxeebcmcvpci9j5lwpncul1asftdayjv8a</string> </dict> <key>text</key> <string>e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxMDM4XGNvY29hc3VicnRm MzYwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEhlbHZldGljYTt9Cntc Y29sb3J0Ymw7XHJlZDI1NVxncmVlbjI1NVxibHVlMjU1O30KXHBhcmRcdHg1NjBc dHgxMTIwXHR4MTY4MFx0eDIyNDBcdHgyODAwXHR4MzM2MFx0eDM5MjBcdHg0NDgw XHR4NTA0MFx0eDU2MDBcdHg2MTYwXHR4NjcyMFxxbFxxbmF0dXJhbFxwYXJkaXJu YXR1cmFsCgpcZjBcZnMyNCBcY2YwIFx1bCBcdWxjMCBCTEFILn0= </string> <key>title</key> <string>Welcome to Notepaddy!</string> <key>uuid</key> <string>5yvghz9n4ukgefnbx0qa2xne3nxeebcmcvpci9j5lwpncul1asftdayjv8a</string> </dict> </plist> 

I get the same text back, but the whole </plist> missing and </dict> disabled. Deciphering and printing the final line gives me complete garbage when encrypting with the passphrase "0987654321123456" or any other passphrase or the same as above when copying in the password field.

+1
source share
4 answers

Both versions have the same problem: you tell CommonCrypto to write past the end of your buffer, and then ignore the result.

First version:

 [self mutableBytes], [self length] + kCCBlockSizeAES128, /* output */ 

Second version:

 // Calculate byte block alignment for all calls through to and including final. bufferPtrSize = CCCryptorGetOutputLength(thisEncipher, plainTextBufferSize, true); // Allocate buffer. bufferPtr = [self mutableBytes]; 

This is not true. You are not highlighting anything. You say write bufferPtrSize bytes to a buffer of size [self length] !

You want to do something more similar (if you really want to encrypt in place):

 // Calculate byte block alignment for all calls through to and including final. bufferPtrSize = CCCryptorGetOutputLength(thisEncipher, plainTextBufferSize, true); // Increase my size if necessary: if (bufferPtrSize > self.length) { self.length = bufferPtrSize; } 

I am also not sure why encryption is in place, while decryption is not; the latter is easier to do if you like.

Your second version has additional problems:

  • You release what you do not allocate: if(bufferPtr) free(bufferPtr);
  • You may have read the end of the line: (const void *)[key UTF8String], kCCKeySizeAES128

Additional cryptological issues:

  • Keys are assumed to be fixed and have a decent amount of entropy. Converting a string to bytes naively is not a good key (for example, keys longer than 16 bytes are effectively truncated). The least you could do is a hash. You can also iterate the hash or just use PBKDF2 (though I did not find the spec / test vectors for PBKDF2 ...)
  • You almost certainly want to use random IV too (see SecRandomCopyBytes).

Addendum:

The reason you see the truncated result is because you are returning a truncated response (with the addition of PKCS7, the encrypted result is always larger than the original data). The odds (around 255/256) are that the last block of ciphertext was incorrectly padded (because you gave truncated CCryptor data), so ccStatus says that an error occurred, but you ignored this and returned the result anyway. This is an incredibly bad practice. (Also, you really want to use the MAC address with CBC to avoid the oracle security cold .)

EDIT:

Some code that seems to work looks something like this (complete with test cases):

Notes:

  • In fact, I have not tested it on iOS (although the iOS code should not work on iOS, namely that SecRandomCopyBytes is a slightly more convenient interface, but not available in OS X).
  • The read () loop may be right, but not fully verified.
  • The ciphertext has the prefix IV. This is a textbook method, but makes ciphertexts large.
  • There is no authentication, so this code can act as a profile.
  • There is no support for AES-192 or AES-256. It is not difficult to add (you just need to include the key length and choose the right algorithm).
  • The key is listed as NSData, so you need to do something like [string dataUsingEncoding:NSUTF8StringEncoding] . For bonus points, run it through CC_SHA256 and take the first 16 output bytes.
  • There is no operation in place. I didn’t think it was worth it.

.

 #include <Foundation/Foundation.h> #include <CommonCrypto/CommonCryptor.h> #if TARGET_OS_IPHONE #include <Security/SecRandom.h> #else #include <fcntl.h> #include <unistd.h> #endif @interface NSData(AES) - (NSData*) encryptedDataUsingAESKey: (NSData *) key; - (NSData*) decryptedDataUsingAESKey: (NSData *) key; @end @implementation NSData(AES) - (NSData*) encryptedDataUsingAESKey: (NSData *) key { uint8_t iv[kCCBlockSizeAES128]; #if TARGET_OS_IPHONE if (0 != SecRandomCopyBytes(kSecRandomDefault, sizeof(iv), iv)) { return nil; } #else { int fd = open("/dev/urandom", O_RDONLY); if (fd < 0) { return nil; } ssize_t bytesRead; for (uint8_t * p = iv; (bytesRead = read(fd,p,iv+sizeof(iv)-p)); p += (size_t)bytesRead) { // 0 means EOF. if (bytesRead == 0) { close(fd); return nil; } // -1, EINTR means we got a system call before any data could be read. // Pretend we read 0 bytes (since we already handled EOF). if (bytesRead < 0 && errno == EINTR) { bytesRead = 0; } // Other errors are real errors. if (bytesRead < 0) { close(fd); return nil; } } close(fd); } #endif size_t retSize = 0; CCCryptorStatus result = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, [key bytes], [key length], iv, [self bytes], [self length], NULL, 0, &retSize); if (result != kCCBufferTooSmall) { return nil; } // Prefix the data with the IV (the textbook method). // This requires adding sizeof(iv) in a few places later; oh well. void * retPtr = malloc(retSize+sizeof(iv)); if (!retPtr) { return nil; } // Copy the IV. memcpy(retPtr, iv, sizeof(iv)); result = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, [key bytes], [key length], iv, [self bytes], [self length], retPtr+sizeof(iv),retSize, &retSize); if (result != kCCSuccess) { free(retPtr); return nil; } NSData * ret = [NSData dataWithBytesNoCopy:retPtr length:retSize+sizeof(iv)]; // Does +[NSData dataWithBytesNoCopy:length:] free if allocation of the NSData fails? // Assume it does. if (!ret) { free(retPtr); return nil; } return ret; } - (NSData*) decryptedDataUsingAESKey: (NSData *) key { const uint8_t * p = [self bytes]; size_t length = [self length]; if (length < kCCBlockSizeAES128) { return nil; } size_t retSize = 0; CCCryptorStatus result = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, [key bytes], [key length], p, p+kCCBlockSizeAES128, length-kCCBlockSizeAES128, NULL, 0, &retSize); if (result != kCCBufferTooSmall) { return nil; } void * retPtr = malloc(retSize); if (!retPtr) { return nil; } result = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, [key bytes], [key length], p, p+kCCBlockSizeAES128, length-kCCBlockSizeAES128, retPtr, retSize, &retSize); if (result != kCCSuccess) { free(retPtr); return nil; } NSData * ret = [NSData dataWithBytesNoCopy:retPtr length:retSize]; // Does +[NSData dataWithBytesNoCopy:length:] free if allocation of the NSData fails? // Assume it does. if (!ret) { free(retPtr); return nil; } return ret; } @end void test(NSData * data, NSData * key) { NSLog(@"%@, %@", data, key); NSData * enc = [data encryptedDataUsingAESKey:key]; NSLog(@"%@", enc); NSData * dec = [enc decryptedDataUsingAESKey:key]; NSLog(@"%@", dec); NSLog((data == dec || [data isEqual:dec]) ? @"pass" : @"FAIL"); } int main() { #define d(x) [NSData dataWithBytesNoCopy:("" x) length:sizeof("" x)-1 freeWhenDone:0] [NSAutoreleasePool new]; NSData * key = d("0123456789abcdef"); test([NSData data], key); test(d(""), key); test(d("a"), key); test(d("0123456789abcde"), key); test(d("0123456789abcdef"), key); test(d("0123456789abcdef0"), key); test(d("0123456789abcdef0123456789abcde"), key); test(d("0123456789abcdef0123456789abcdef"), key); test(d("0123456789abcdef0123456789abcdef0"), key); } 
+5
source

Regarding your * de * cryption code

kCCParamError , as its name says, is a mistake. Why do you think this is successful? If you get this error, it means you did something wrong; look at the parameters you passed and find out what.

This is probably why you get garbage: CCCrypt (decryption) never gave you anything because it could not work with any values ​​you gave it. What you get is what lay in the output buffer when you allocated it.

If you switch to calloc or to create an NSMutableData object before calling CCCrypt and using its mutableBytes as a buffer, I think you will find that the buffer always contains all zeros. For the same reason: CCCrypt does not populate it because it does not work, because you passed one or more invalid values ​​(parameter error).

You need to fix the parameter error before you can expect it to work.

You can try to break the CCCrypt call into CCCryptorCreate , CCCryptorUpdate , CCCryptorFinal and CCCryptorRelease , at least temporarily, to see where this happens.

Encryption: same problem or no problem at all

Does your encryption method return YES or NO ? I assume that it returns NO , because the code seems to be basically identical to the encryption and decryption methods, so everything that you have wrong in your decryption code is probably not true in your encryption code either. See what CCCrypt returns, and if it doesn't work, make it work.

If it returns YES ( CCCrypt succeeds), it is interesting what you mean by "returns me a lot of garbage." Do you mean the contents of the data object sent to the encryptWithAES128Key: message?

If this is the case, then this is the expected result. Your code encrypts the contents of the data object in place, overwriting the text with encrypted text. What you see is not pure β€œgarbage” - it is encrypted text! Decryption (successful) will open the text again.

By the way, you have "in-place encryption, since it is a mutable data object", a comment on creating an output buffer so as not to work in-place in the decryption code. It should be in the encryption method where you work on the spot. I propose to do either work on the spot or not work on the spot; coherence is a virtue.

+1
source

If you have changes to your code, delete it and always keep kCCOptionPKCS7Padding, this should solve your problem.

 if (encryptOrDecrypt == kCCEncrypt) { if (*pkcs7 != kCCOptionECBMode) { if ((plainTextBufferSize % kChosenCipherBlockSize) == 0) { *pkcs7 = 0x0000; } else { *pkcs7 = kCCOptionPKCS7Padding; } } } 
0
source

You have to use RNCryptor , this is the high-level open-source encryption api around CommonCrypto, and the high-level encryption API is the best practice for cryptography of the days, because it is easy for experts to make mistakes in implementations using crypto primates, and there are many side channel attacks that use these errors.

For example, you indicate that /* initialization vector (optional) * / 100% is not true, so you completely crippled AES-CBC and this is the most obvious problem.

In your case, RNCryptor is perfect, I strongly recommend that you not minimize your own implementation.

0
source

Source: https://habr.com/ru/post/908182/


All Articles