I’ve been trying to get my head around cryptography on the iPhone so that I can create a native iPhone app (iPasskeep) that interoperates with my JPasskeep password keeper application. It has taken a while to get my head around CommonCrypto APIs – how to use them, how not to use them and their limitations. It then took a bit of fiddling to find the right incantations in Java to get an interoperable cryptographic transformation.
Caveat: The following Objective-C code is not yet production ready or quality. It passes unit tests, but I haven’t checked it for memory leaks, performance, etc.
Hope this helps.
Listing: Cipher.h
#import <Cocoa/Cocoa.h> #import <CommonCrypto/CommonDigest.h> #import <CommonCrypto/CommonCryptor.h> @interface Cipher : NSObject { NSString* cipherKey; } @property (retain) NSString* cipherKey; - (Cipher *) initWithKey:(NSString *) key; - (NSData *) encrypt:(NSData *) plainText; - (NSData *) decrypt:(NSData *) cipherText; - (NSData *) transform:(CCOperation) encryptOrDecrypt data:(NSData *) inputData; + (NSData *) md5:(NSString *) stringToHash; @end
Listing: Cipher.m
#import "Cipher.h" @implementation Cipher @synthesize cipherKey; - (Cipher *) initWithKey:(NSString *) key { self = [super init]; if (self) { [self setCipherKey:key]; } return self; } - (NSData *) encrypt:(NSData *) plainText { return [self transform:kCCEncrypt data:plainText]; } - (NSData *) decrypt:(NSData *) cipherText { return [self transform:kCCDecrypt data:cipherText]; } - (NSData *) transform:(CCOperation) encryptOrDecrypt data:(NSData *) inputData { // kCCKeySizeAES128 = 16 bytes // CC_MD5_DIGEST_LENGTH = 16 bytes NSData* secretKey = [Cipher md5:cipherKey]; CCCryptorRef cryptor = NULL; CCCryptorStatus status = kCCSuccess; uint8_t iv[kCCBlockSizeAES128]; memset((void *) iv, 0x0, (size_t) sizeof(iv)); status = CCCryptorCreate(encryptOrDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, [secretKey bytes], kCCKeySizeAES128, iv, &cryptor); if (status != kCCSuccess) { return nil; } size_t bufsize = CCCryptorGetOutputLength(cryptor, (size_t)[inputData length], true); void * buf = malloc(bufsize * sizeof(uint8_t)); memset(buf, 0x0, bufsize); size_t bufused = 0; size_t bytesTotal = 0; status = CCCryptorUpdate(cryptor, [inputData bytes], (size_t)[inputData length], buf, bufsize, &bufused); if (status != kCCSuccess) { free(buf); CCCryptorRelease(cryptor); return nil; } bytesTotal += bufused; status = CCCryptorFinal(cryptor, buf + bufused, bufsize - bufused, &bufused); if (status != kCCSuccess) { free(buf); CCCryptorRelease(cryptor); return nil; } bytesTotal += bufused; CCCryptorRelease(cryptor); return [NSData dataWithBytesNoCopy:buf length:bytesTotal]; } + (NSData *) md5:(NSString *) stringToHash { const char *src = [stringToHash UTF8String]; unsigned char result[CC_MD5_DIGEST_LENGTH]; CC_MD5(src, strlen(src), result); return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH]; } @end
Listing: Cipher.java
package com.tomczarniecki.iphone; import org.apache.commons.codec.digest.DigestUtils; import org.bouncycastle.crypto.BufferedBlockCipher; import org.bouncycastle.crypto.engines.AESFastEngine; import org.bouncycastle.crypto.modes.CBCBlockCipher; import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; import org.bouncycastle.crypto.params.KeyParameter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; public class Cipher { private final String password; public Cipher(String password) { this.password = password; } public byte[] encrypt(byte[] plainText) throws Exception { return transform(true, plainText); } public byte[] decrypt(byte[] cipherText) throws Exception { return transform(false, cipherText); } private byte[] transform(boolean encrypt, byte[] inputBytes) throws Exception { byte[] key = DigestUtils.md5(password.getBytes("UTF-8")); BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine())); cipher.init(encrypt, new KeyParameter(key)); ByteArrayInputStream input = new ByteArrayInputStream(inputBytes); ByteArrayOutputStream output = new ByteArrayOutputStream(); int inputLen; int outputLen; byte[] inputBuffer = new byte[1024]; byte[] outputBuffer = new byte[cipher.getOutputSize(inputBuffer.length)]; while ((inputLen = input.read(inputBuffer)) > -1) { outputLen = cipher.processBytes(inputBuffer, 0, inputLen, outputBuffer, 0); if (outputLen > 0) { output.write(outputBuffer, 0, outputLen); } } outputLen = cipher.doFinal(outputBuffer, 0); if (outputLen > 0) { output.write(outputBuffer, 0, outputLen); } return output.toByteArray(); } }