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();
    }
}