Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 643
t0mm13b Avatar asked Oct 04 '22 05:10

t0mm13b


1 Answers

I have taken the liberty in coding a direct port of the PKCS12Parameters generator as used on the Android side, the gist for this header is above.

The implementation is direct copy also, as found here, the password, is converted to PKCS12 equivalent - unicode, big-endian, with two extra zeros padded on to the end.

The Generator generates the derived key and iv via performing the number of iterations, in this case, 1000, as is on the Android side, using the SHA256 Digest, the final generated key and iv is then used as the parameters to CCCryptorCreate.

Using the following code sample is not working either, it ends with -4304 upon a call to CCCryptorFinal

The code excerpt is as shown:

#define ITERATIONS 1000

PKCS12ParametersGenerator *pGen = [[PKCS12ParametersGenerator alloc]
        init:argPassword 
        saltedHash:argPtrSalt 
        iterCount:ITERATIONS 
        keySize:128 
        initVectSize:128]; 
//
[pGen generateDerivedParameters];
//
CCCryptorRef decryptor = NULL;
// Create and Initialize the crypto reference.
CCCryptorStatus ccStatus = CCCryptorCreate(kCCDecrypt,
                           kCCAlgorithmAES128,
                           kCCOptionPKCS7Padding,
                           pGen.derivedKey.bytes,
                           kCCKeySizeAES128,
                           pGen.derivedIV.bytes,
                           &decryptor
                           );
NSAssert(ccStatus == kCCSuccess, 
    @"Unable to initialise decryptor!");
//
size_t szPlainBufLen = cipherData.length + (kCCBlockSizeAES128);

// Calculate byte block alignment for all calls through to and including final.
size_t szPtrPlainBufSize = CCCryptorGetOutputLength(decryptor, szPlainBufLen, true);
uint8_t *ptrPlainBuf = calloc(szPtrPlainBufSize, sizeof(uint8_t));
//
// Set up initial size.
size_t remainingBytes = szPtrPlainBufSize;
uint8_t *ptr = ptrPlainBuf;
size_t movedBytes = 0;
size_t totalBytesWritten = 0;

// Actually perform the encryption or decryption.
ccStatus = CCCryptorUpdate(decryptor,
                           (const void *) cipherData.bytes,
                           szPtrPlainBufSize,
                           ptr,
                           remainingBytes,
                           &movedBytes
                           );
NSAssert(ccStatus == kCCSuccess, 
    @"Unable to update decryptor! Error: %d", ccStatus);
ptr += movedBytes;
remainingBytes -= movedBytes;
totalBytesWritten += movedBytes;
//
// Finalize everything to the output buffer.
CCCryptorStatus resultCCStatus = CCCryptorFinal(decryptor,
                          ptr,
                          remainingBytes,
                          &movedBytes
                          );

totalBytesWritten += movedBytes;

if(decryptor) {
    (void) CCCryptorRelease(decryptor);
    decryptor = NULL;
}

NSAssert(resultCCStatus == kCCSuccess, 
    @"Unable to perform PBE AES128bit decryption: %d", resultCCStatus);

The funny thing, the decryption works, the final call to CCCryptorFinal returns 0 if I substitute the kCCOptionPKCS7Padding for 0x0000 at the start of CCCryptorCreate, i.e no padding. Alas, the data is not what I expected, still totally scrambled regardless when that "does not work".

It is failing somewhere, so if anyone has any better ideas on how to achieve the equivalent, I would be delighted to hear other opinions.

Either its change the mechanism on the Android side to make it "cross-platform" compatible with iPhone or seek an alternative cryptographic solution to be compatible on both ends at the expense of weaker cryptography on both sides of platforms used for making data interchange portable.

The input data as supplied:

  • Base64 encoded cipher, with the salt and the cipher separated by ':' tnNhKyJ2vvrUzAmtQV5q9uEwzzAH63sTKtLf4pOQylw=:qTBluA+aNeFnEUfkUFUEVgNYrdz7enn5W1n4Q9uBKYmFfJeSCcbsfziErsa4EU9Cz/pO0KE4WE1QdqRcvSXthQ==
  • The password supplied is f00b4r
  • The original string is The quick brown fox jumped over the lazy dog and ran away
like image 156
t0mm13b Avatar answered Oct 13 '22 12:10

t0mm13b