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();
}
}
#1 by Kalpan on 20 October 2010 - 10:44 pm
Quote
Hey! How you read those bytes stream in your iphone app from java code?
#2 by Tom on 24 October 2010 - 10:52 am
Quote
NSURL* url = [NSURL URLWithString:@"http://localhost:8080/data"];
NSString* text = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
NSData* cipherText = [NSData dataWithBase64EncodedString:text];
#3 by Dominique Gibeau on 20 November 2010 - 1:09 am
Quote
Is this example code (aes for java and iPhone) supposed to work as is? I am having a hard time getting it to work, is there a working example somewhere?
#4 by Tom on 20 November 2010 - 3:59 pm
Quote
@Dominique The code above forms the core interoperability point between a java server application and an iPhone client application that I started to write earlier this year. As I hope to make some money from this application I will not be posting its full code but I figured that the really hard crypto bits were something that I could share. What is your actual issue with the above code?
#5 by Ankita on 23 November 2010 - 9:02 pm
Quote
Hi Tom,
I have used this code in my app, it is working well when i m encrypting a string in iPhone and decrypting that string on java server, but when i encrypt a string in java..it doesn’t get decrypted in iPhone. may be i have done some mistake in decryption code. So can u please show the decryption code of iphone…so that i can refer it…
#6 by Tom on 24 November 2010 - 8:48 pm
Quote
- (void) testDecryptReturnsExpectedPlainText {
NSString* cipherText = @”lVKmlQnR02uCC7B36VRypFDMlJTWDP/jdm01Y1iRV576eA/XSK5xvMlIDd81FnnmjrE90BB/MlU7uSH/7JL+xA==”;
NSString* expectedText = @”Why are all crypto APIs so bloody hideous to use?”;
Cipher* cipher = [[Cipher alloc] initWithKey:@”password0″];
NSData* cipherTextData = [NSData dataWithBase64EncodedString:cipherText];
NSData* plainTextData = [cipher decrypt:cipherTextData];
NSString* plainText = [[NSString alloc] initWithData:plainTextData encoding:NSUTF8StringEncoding];
STAssertEqualObjects(expectedText, plainText, @”Decryption mismatch”);
}
#7 by Ayush on 20 December 2010 - 8:51 am
Quote
Hi Tom,
Thanks a lot for posting this. I am trying to use your code in my app. I am trying to just test trying to generating a cipher key in Java and placing it in a file in a string format. With your Java code, when I use “password0″ as the key and ”Why are all crypto APIs so bloody hideous to use?” as the plain string, the cipher text from Java that I get is “UhjnNu5COgiurpnmgpTYXDLIh0EH4J43OAuHlpBCsuw=”.
In java, after getting a byte[] from your encrypt method, I convert it to a Base64 String using “org.apache.commons.codec.binary.Base64″ library.
Could you please tell me what I might be doing wrong, or how you are converting the cipher data to a string using Java.
#8 by Tom on 20 December 2010 - 9:33 pm
Quote
byte[] cipherTextBytes = encrypt(plainText.getBytes(“UTF-8″));
String cipherText = new String(Base64.encodeBase64(cipherTextBytes), “UTF-8″);
#9 by Robert on 20 May 2011 - 5:26 am
Quote
What specific jars do I need to get this to work? I’m using OSX. Do I need to download Bouncy Castle? And if so, there are tons of jars. What are the minimum required jars?
#10 by Tom on 24 July 2011 - 11:59 pm
Quote
If you’re using Java6 then http://www.bouncycastle.org/download/bcprov-jdk16-146.jar will do just fine.
#11 by Joshua on 12 September 2011 - 11:49 am
Quote
Thanks man ! It worked both ways iphone-java & java-iphone.
Thanks a Million …