iosobjective-cxcodecommoncrypto

Decrypting iOS with Objective c and SwiftUI


I am planning to implement the AES encryption in my application and for this I went through an informative tutorial by Rob Napier :

It was a wonderful read and I was able to encrypt few strings using :

USING ROB NAPIER RNCRYPTOR CLASS

NSString * const
kRNCryptManagerErrorDomain = @"net.robnapier.RNCryptManager";

const CCAlgorithm kAlgorithm = kCCAlgorithmAES128;
const NSUInteger kAlgorithmKeySize = kCCKeySizeAES128;
const NSUInteger kAlgorithmBlockSize = kCCBlockSizeAES128;
const NSUInteger kAlgorithmIVSize = kCCBlockSizeAES128;
const NSUInteger kPBKDFSaltSize = 8;
const NSUInteger kPBKDFRounds = 10000;  // ~80ms on an iPhone 4

// ===================

+ (NSData *)encryptedDataForData:(NSData *)data
                        password:(NSString *)password
                              iv:(NSData **)iv
                            salt:(NSData **)salt
                           error:(NSError **)error {
  NSAssert(iv, @"IV must not be NULL");
  NSAssert(salt, @"salt must not be NULL");
  
  *iv = [self randomDataOfLength:kAlgorithmIVSize];
  *salt = [self randomDataOfLength:kPBKDFSaltSize];
  
  NSData *key = [self AESKeyForPassword:password salt:*salt];
  
  size_t outLength;
  NSMutableData *
  cipherData = [NSMutableData dataWithLength:data.length +
                kAlgorithmBlockSize];

  CCCryptorStatus
  result = CCCrypt(kCCEncrypt, // operation
                   kAlgorithm, // Algorithm
                   kCCOptionPKCS7Padding, // options
                   key.bytes, // key
                   key.length, // keylength
                   (*iv).bytes,// iv
                   data.bytes, // dataIn
                   data.length, // dataInLength,
                   cipherData.mutableBytes, // dataOut
                   cipherData.length, // dataOutAvailable
                   &outLength); // dataOutMoved

  if (result == kCCSuccess) {
    cipherData.length = outLength;
  }
  else {
    if (error) {
      *error = [NSError errorWithDomain:kRNCryptManagerErrorDomain
                                   code:result
                               userInfo:nil];
    }
    return nil;
  }
  
  return cipherData;
}

// ===================

+ (NSData *)randomDataOfLength:(size_t)length {
  NSMutableData *data = [NSMutableData dataWithLength:length];
  
  int result = SecRandomCopyBytes(kSecRandomDefault, 
                                  length,
                                  data.mutableBytes);
  NSAssert(result == 0, @"Unable to generate random bytes: %d",
           errno);
  
  return data;
}

// ===================

// Replace this with a 10,000 hash calls if you don't have CCKeyDerivationPBKDF
+ (NSData *)AESKeyForPassword:(NSString *)password 
                         salt:(NSData *)salt {
  NSMutableData *
  derivedKey = [NSMutableData dataWithLength:kAlgorithmKeySize];
  
  int 
  result = CCKeyDerivationPBKDF(kCCPBKDF2,            // algorithm
                                password.UTF8String,  // password
                                [password lengthOfBytesUsingEncoding:NSUTF8StringEncoding],  // passwordLength
                                salt.bytes,           // salt
                                salt.length,          // saltLen
                                kCCPRFHmacAlgSHA1,    // PRF
                                kPBKDFRounds,         // rounds
                                derivedKey.mutableBytes, // derivedKey
                                derivedKey.length); // derivedKeyLen
  
  // Do not log password here
  NSAssert(result == kCCSuccess,
           @"Unable to create AES key for password: %d", result);
  
  return derivedKey;
}

But while decrypting I am not able to decrypt properly and I am getting null in the scenario: For your reference the decrypt code is :

    + (NSData*)decryptData:(NSData*)data key:(NSData*)key error:(NSError **)error
{
    if (key.length != 16 && key.length != 24 && key.length != 32) {
        *error = [NSError errorWithDomain:@"keyLengthError" code:-1 userInfo:nil];
        return nil;
    }

    
    CCCryptorStatus ccStatus   = kCCSuccess;
    int             ivLength   = kCCBlockSizeAES128;
    size_t          clearBytes = 0;
    NSMutableData *dataOut     = [NSMutableData dataWithLength:data.length - ivLength];
    
    NSLog(@"Data Out String Decrypt%@", dataOut);

    ccStatus = CCCrypt(kCCDecrypt,
                       kCCAlgorithmAES,
                       kCCOptionPKCS7Padding,
                       key.bytes,
                       key.length,
                       data.bytes,
                       data.bytes + ivLength,
                       data.length - ivLength,
                       dataOut.mutableBytes,
                       dataOut.length,
                       &clearBytes);

    if (ccStatus == kCCSuccess) {
        dataOut.length = clearBytes;
    }
    else {
        if (error) {
            *error = [NSError errorWithDomain:@"kEncryptionError" code:ccStatus userInfo:nil];
        }
        dataOut = nil;
    }

    return dataOut;
}

Where I am getting wrong in this scenario? I have been trying for few days to sort it out. Can someone please help me?


Solution

  • the method given in the example you mentioned refers Rob Napiers Github Repo. Just testet it with your given password, salt, etc.. and it just works!
    Yes understood, you want to throw out password: and iv: as well the salt: parameter when decrypting and go only with key:. Well you need at least iv: to do that. But again as Rob commented to your other question, don't re-invent the wheel.

    The method i linked above is just working fine with your parameters for decrypting. The only difference to your code is that password, iv and salt are given to decrypt.

    apart from the idea you want to develop something that can decrypt without a password you will have to digg deeper into how CCKeyDerivationPBKDF() (CommonKeyDerivation.h) is working.

    Edit: As you asked to have a way to pack and unpack your salt, iv and cypher thats pretty simple with NSData.

    + (NSData *)packWithSalt:(NSData*)salt IV:(NSData*)iv Cypher:(NSData*)tocypher {
        
        //adding Salt + IV + Cipher text
        NSMutableData *combi = [NSMutableData data];
        
        //[combi appendBytes:salt.bytes length:16];
        //[combi appendBytes:iv.bytes length:16]; //16
        //[combi appendBytes:tocypher.bytes length:tocypher.length];
        
        [combi appendData:salt];
        [combi appendData:iv];
        [combi appendData:tocypher];
        
        return combi;
    }
    
    + (NSData*)cypherUnpackToSalt:(NSMutableData**)salt andIV:(NSMutableData**)iv fromPackData:(NSData*)pack {
        
        void *sBuff[16] = {};
        void *iBuff[16] = {};
        NSUInteger len = pack.length - 16 - 16; //keep length flexible
        void *pBuff = malloc(sizeof(Byte)*len); //needs dynamically size of buff
        [pack getBytes:sBuff range:NSMakeRange(0, 16)];
        [pack getBytes:iBuff range:NSMakeRange(16, 32)];
        [pack getBytes:pBuff range:NSMakeRange(32, len)];
        
        [(*salt) replaceBytesInRange:NSMakeRange(0, 16) withBytes:sBuff];
        [(*iv) replaceBytesInRange:NSMakeRange(0, 16) withBytes:iBuff];
    
        NSMutableData *unpack = [NSMutableData dataWithLength:len];
        [unpack replaceBytesInRange:NSMakeRange(0, len) withBytes:pBuff];
        free(pBuff);
        return unpack;
    } 
    

    it should be pretty simple to integrate the encryption and decryption from these two methods.

    Proof of concept: Can we pack all together? and Unpack again?

    NSData *salt = [CryptAES randomDataOfLength:16];
    NSData *iv = [CryptAES randomDataOfLength:16];
    NSData *chunk = [CryptAES packWithSalt:salt IV:iv Cypher:plaintextData];
    NSLog(@"salt=%@ iv=%@ pack=%@ ",[salt base64EncodedStringWithOptions:0], [iv base64EncodedStringWithOptions:0], [chunk base64EncodedStringWithOptions:0] );
        
    NSMutableData *unSalt = [NSMutableData dataWithLength:16];
    NSMutableData *unIv = [NSMutableData dataWithLength:16];
    NSData *unchunk = [CryptAES cypherUnpackToSalt:&unSalt andIV:&unIv fromPackData:chunk];
    NSString *plainAgain = [[NSString alloc] initWithData:unchunk encoding:NSUTF8StringEncoding];
    NSLog(@"salt=%@ iv=%@ unpack=%@",[unSalt base64EncodedStringWithOptions:0], [unIv base64EncodedStringWithOptions:0], plainAgain );
    

    So your decryption method will still need parameters for a password. Which is not perfect but as you should never throw around encrypted data together with its password - that should be ok - you just keep the handling of a password on the user side. I mean otherwise the whole encryption is useless!