androidioscommoncryptospongycastle

Equivalent of spongycastle encryption for ios


This has stumped me - the following code uses SpongyCastle's encryption/decryption for Android - I am trying to achieve cross-platform encryption/decryption for iOS.

The following code (from Android) works a treat, AES 128bit CBC with PKCS7Padding, using a supplied salt and password, which the salt is stored on the mysql database, the password is by the end-user, the following code is adapted from this answer by kelhoer.

The reason I used AES128bit is that AES256 is not available in iOS 4+, it was introduced in iOS5+, and having to dip the toe into using openssl to generate a derived key and initialization vector (iv), it was dicey as learnt that Apple rejects apps that are statically linked with openssl library.

Since the platform is based on iOS 4.2+, having resorted to bundling and statically linking the openssl library seems rather, over-kill and would preferably use the CommonCryptor library.

Here's the Android version with Spongycastle code in place:

private static void encrypt(InputStream fin, 
    OutputStream fout, 
    String password, 
    byte[] bSalt) {
    try {
        PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(
            new SHA256Digest()
            );
        char[] passwordChars = password.toCharArray();
        final byte[] pkcs12PasswordBytes = 
            PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
        pGen.init(pkcs12PasswordBytes, bSalt, ITERATIONS);
        CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
        ParametersWithIV aesCBCParams = 
            (ParametersWithIV) pGen.generateDerivedParameters(128, 128);
        aesCBC.init(true, aesCBCParams);
        PaddedBufferedBlockCipher aesCipher = 
            new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
        aesCipher.init(true, aesCBCParams);
        byte[] buf = new byte[BUF_SIZE];
        // Read in the decrypted bytes and write the cleartext to out
        int numRead = 0;
        while ((numRead = fin.read(buf)) >= 0) {
            if (numRead == 1024) {
                byte[] plainTemp = new byte[
                    aesCipher.getUpdateOutputSize(numRead)];
                int offset = 
                    aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                final byte[] plain = new byte[offset];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            } else {
                byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
                int offset = 
                    aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset + last];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            }
        }
        fout.close();
        fin.close();
    } catch (Exception e) {
        e.printStackTrace();
    }

}

private static void decrypt(InputStream fin, 
    OutputStream fout, 
    String password, 
    byte[] bSalt) {
    try {
        PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(
            new SHA256Digest()
            );
        char[] passwordChars = password.toCharArray();
        final byte[] pkcs12PasswordBytes = 
            PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
        pGen.init(pkcs12PasswordBytes, bSalt, ITERATIONS);
        CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
        ParametersWithIV aesCBCParams = 
            (ParametersWithIV) pGen.generateDerivedParameters(128, 128);
        aesCBC.init(false, aesCBCParams);
        PaddedBufferedBlockCipher aesCipher = 
            new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
        aesCipher.init(false, aesCBCParams);
        byte[] buf = new byte[BUF_SIZE];
        // Read in the decrypted bytes and write the cleartext to out
        int numRead = 0;
        while ((numRead = fin.read(buf)) >= 0) {
            if (numRead == 1024) {
                byte[] plainTemp = new byte[
                    aesCipher.getUpdateOutputSize(numRead)];
                int offset = 
                    aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                // int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            } else {
                byte[] plainTemp = new byte[
                    aesCipher.getOutputSize(numRead)];
                int offset = 
                    aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset + last];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            }
        }
        fout.close();
        fin.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

However under iOS 4.2 (Working with XCode) I cannot figure out how to do the equivalent,

This is what I have tried under Objective C, with the goal of decrypting data from the Android side, stored in mysql database, to test this out:

+(NSData*) decrypt:(NSData*)cipherData 
    userPassword:(NSString*)argPassword 
    genSalt:(NSData*)argPtrSalt{

    size_t szPlainBufLen = cipherData.length + (kCCBlockSizeAES128);
    uint8_t *ptrPlainBuf = malloc(szPlainBufLen);
    //
    const unsigned char *ptrPasswd = 
        (const unsigned char*)[argPassword 
            cStringUsingEncoding:NSASCIIStringEncoding];
    int ptrPasswdLen = strlen(ptrPasswd);
    //
    NSString *ptrSaltStr = [[NSString alloc]
        initWithData:argPtrSalt 
        encoding:NSASCIIStringEncoding];

    const unsigned char *ptrSalt = 
        (const unsigned char *)[ptrSaltStr UTF8String];
    NSString *ptrCipherStr = 
        [[NSString alloc]initWithData:cipherData 
            encoding:NSASCIIStringEncoding];
    unsigned char *ptrCipher = (unsigned char *)[ptrCipherStr UTF8String];
    unsigned char key[kCCKeySizeAES128];
    unsigned char iv[kCCKeySizeAES128];
    //
    //int     EVP_BytesToKey(const EVP_CIPHER *type,const EVP_MD *md,
    //const unsigned char *salt, const unsigned char *data,
    //int datal, int count, unsigned char *key,unsigned char *iv);
    int i = EVP_BytesToKey(EVP_aes_128_cbc(), 
                       EVP_sha256(), 
                       ptrSalt, 
                       ptrPasswd, 
                       ptrPasswdLen, 
                       PBKDF2_ITERATIONS, 
                       key, 
                       iv);
    NSAssert(i == kCCKeySizeAES128, 
        @"Unable to generate key for AES");
    //
    size_t cipherLen = [cipherData length];
    size_t outlength = 0;
    //
    CCCryptorStatus resultCCStatus = CCCrypt(kCCDecrypt,
                                             kCCAlgorithmAES128,
                                             kCCOptionPKCS7Padding,
                                             key,
                                             kCCBlockSizeAES128,
                                             iv,
                                             ptrCipher,
                                             cipherLen,
                                             ptrPlainBuf,
                                             szPlainBufLen,
                                             &outlength);
    NSAssert(resultCCStatus == kCCSuccess, 
        @"Unable to perform PBE AES128bit decryption: %d", errno);
    NSData *ns_dta_PlainData = nil;

    if (resultCCStatus == kCCSuccess){
        ns_dta_PlainData = 
        [NSData dataWithBytesNoCopy:ptrPlainBuf length:outlength];
    }else{
        return nil;
    }
    return ns_dta_PlainData;
}

Have supplied the data and user's password and get a return code from CCCrypt as -4304 which indicates not successful and bad decoding.

I have thought that perhaps the encoding scheme would be throwing off the CommonCryptor's decryption routing hence the long-winded way of converting to NSASCIIStringEncoding.

The Salt is stored along with the cipher data, and is 32bytes in length.

What am I missing in this regard, bearing in mind, am weak on cryptography.


Solution

  • Right, I have had to scrap the encryption algorithm on the Android side, which posed a challenge, to find one that is cross-platform compatible.

    I have read up a lot about Rob Napier's RNCryptor, and after googling for an Android equivalent, in which I found JNCryptor , I took the plunge and employed RNCryptor on the iOS side.

    Forked the JNCryptor code on github to add an enhancement in being able to specify custom settings, and to use SpongyCastle, for older versions of Android. From there on, both platforms were able to encrypt/decrypt interchangeably.

    The reason I enhanced JNCryptor, was the iteration count for the PKDBF2 function was quite too high - 10,000 and was default value (since the code will be running on older handsets - it seized up - great if you have dual/quad core!), and needed to override the iteration count to be more "bearable" - 1,000. Using the custom setting was possible with RNCryptor.

    Thanks to both Rob Napier and Duncan Jones for their work!