I’m trying to encrypt some data from python (Google App Engine) and then decrypt it on iOS.
There are several issues surrounding this based on the fact that there are so many options with AES Encryption and the different formats available in Python and Objective-C.
Because of the limited availability of the PyCrypto libraries on Google App Engine and the AES code on the iOS/Objective-C side requiring PKCS7Padding, I decided to use slowAES on the python side.
I’m also using a 16-bit key, CBC Mode, and PKCS7Padding.
Given that, this is my encrypt function and helper variables/function:
def str2nums(s):
return map(ord, s)
key = "hjt4mndfy234n5fs"
moo = aes.AESModeOfOperation()
iv = [12, 34, 96, 15] * 4
CBC_mode = moo.modeOfOperation['CBC']
nkey = str2nums(key)
def encrypt(plaintext):
funcName = inspect.stack()[0][3]
logging.debug("Enter " + funcName)
logging.debug("Input string: " + plaintext)
m, s, encData = moo.encrypt(plaintext, CBC_mode, nkey, len(nkey), iv)
fmt = len(encData)*'B'
dataAsStr = ""
for j in encData:
dataAsStr = dataAsStr + str(j) + ","
logging.debug("Output encrypted data:[" + dataAsStr + "]")
encoded = base64.b64encode(struct.pack(fmt, *encData))
logging.debug("Output encrypted string: " + encoded)
decoded = struct.unpack(fmt, base64.b64decode(encoded))
decrypted = moo.decrypt(decoded, s, CBC_mode, nkey, len(nkey), iv)
logging.debug("Output decrypted back: " + decrypted)
return encoded
Note that on the Python side, I am encrypting, packing, and then base64 encoding the data. Before returning this encrypted data, I’m also doing a test run at decrypting it just to show that in the logs and it does indeed work.
On the iOS side, I’m using the following AES NSData addition:
- (NSData *)AES128DecryptWithKey:(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:NSASCIIStringEncoding];
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, kCCKeySizeAES128,
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;
}
And I’m making use of this when I pull down the base64/packed/encrypted data like so:
NSMutableString * result = [[NSMutableString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
if (LOG) { NSLog(@"Base64 encoded / Encrypted string: %@", result); }
NSData* encryptedData = [NSData decodeBase64ForString:result]; // base64 decode
if (LOG) { NSLog(@"Encrypted string: %@", encryptedData); }
NSData* decryptedData = [encryptedData AES128DecryptWithKey:@"hjt4mndfy234n5fs"]; // AES Decrypt
The problem is, I can’t seem to get the data to decrypt correctly on the client (iOS) side even though it decrypts just fine on the server (in python).
During the decryption, the cryptStatus always ends up : kCCAlignmentError. Which I don’t quite understand.
I’ve also messed with AES 256 but I need a 32bit key I think and that doesn’t seem to be an option for slowAES in CBC mode (at least according to the examples?).
Logging of the Server (Notice the actual unencrypted data is merely an empty set [] . That’s a JSON representation of such to return to the client.
2012-01-04 08:48:13.962
Enter encrypt
D 2012-01-04 08:48:13.962
Input string: []
D 2012-01-04 08:48:13.967
Output encrypted data:[4,254,226,26,101,240,22,113,44,54,209,203,233,64,208,255,]
D 2012-01-04 08:48:13.967
Output encrypted string: BP7iGmXwFnEsNtHL6UDQ/w==
D 2012-01-04 08:48:13.971
Output decrypted back: []
Logging of the client (iOS):
2012-01-04 12:45:13.891 Base64 encoded / Encrypted string: BP7iGmXwFnEsNtHL6UDQ/w==
2012-01-04 12:45:13.892 Encrypted string: <04fee21a 65f01671 2c36d1cb e940d0ff>
2012-01-04 12:45:29.126 Decrypted string:
So my questions are:
- What does it mean by an “Alignment Error”? Or what am I doing wrong that it doesn’t want to decrypt on the client?
- Do I need to worry about unpacking the data at all considering it looks like it matches up just fine? And if so, how would I go about an unpack() function in C or Objective-C?
- Is there just plain a better way to do AES encryption between Google App Engine and iOS?
EDIT: I should also note that besides the answer of using the same Initialization Vector, I found a discrepancy between the python and iOS code for the padding. The encrypt/decrypt worked fine for small amounts of data but then I ran into failure to decrypt with larger ones. I fixed this by changing the iOS side to NOT use PKCS7Padding and put 0 there instead.
The IV needs to match on both ends.
The IV (initialization vector) is a string of bytes that’s sent through the encryptor/decryptor to place its “memory” in a pseudo-random state before the “real” data is sent through. Since the encryption results depend on what’s gone through before, this initialization makes it impossible (without knowing the IV) for a malicious 3rd party o know whether a given cleartext and key could have produced a given cypertext.
Ideally the IV is itself somehow variable, based, perhaps, on a serial number or some other text that’s sent along with the cyphertext, or based on a counter that’s synchronized between ends.
The presence of the IV (even if semi-predictable) significantly increases the difficulty of using a “known cleartext” attack. This is especially important for relatively short, frequent messages.