Search code examples
iosobjective-cencryptionaesrncryptor

Encrypt and decrypt a random NSString with AES128 CTR in iOS with a given key


I'm not sure how secure is my solution.

Rob Napier has an extraordinary Framework (RNCryptor) to encrypt and decrypt in iOS and other systems.

As far as I know, he is using AES-CBC, that in fact is the standard of CommonCryptor.h

However, my requirements, has forced me to use AES-CTR. Both are really similar, so in theory it had to be something easy. But was not.

There is a lack of information around CommonCryptor.h. It is one of the worst explained frameworks ever.

Working with CBC you just need to call CCCrypt(). However, to work with CTR you should call: CCCrytorCreate(), CCCryptorUpdate(), CCCryptorFinal() and CCCryptorRelease()

Trying to encrypt my data, I was receiving different data every time, of course decrypting it had incorrect results.

I had 2 big problems in my first approach: the length of the key and the number of bytes written to dataOut.

I sorted the problems with:

1.- A NSString key of 32 characters

NSString *key = @"1234567890ABCDEFGHIJKLMNOPQRSTUV";

2.- To cut dataOut with needed length

Finally this is my code to Encrypt and Decrypt:

#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonKeyDerivation.h>
#import <Security/Security.h>

+ (NSMutableData*) encryptString: (NSString*) stringToEncrypt withKey: (NSString*) keyString
{
//Key to Data
NSData *key = [keyString dataUsingEncoding:NSUTF8StringEncoding];

//String to encrypt to Data
NSData *data = [stringToEncrypt dataUsingEncoding:NSUTF8StringEncoding];

// Init cryptor
CCCryptorRef cryptor = NULL;

// Alloc Data Out
NSMutableData *cipherData = [NSMutableData dataWithLength:data.length + kCCBlockSizeAES128];

//Empty IV: initialization vector
NSMutableData *iv =  [NSMutableData dataWithLength:kCCBlockSizeAES128];

//Create Cryptor
CCCryptorStatus  create = CCCryptorCreateWithMode(kCCEncrypt,
                                                  kCCModeCTR,
                                                  kCCAlgorithmAES,
                                                  ccPKCS7Padding,
                                                  iv.bytes, // can be NULL, because null is full of zeros
                                                  key.bytes,
                                                  key.length,
                                                  NULL,
                                                  0,
                                                  0,
                                                  kCCModeOptionCTR_BE,
                                                  &cryptor);

if (create == kCCSuccess)
{
    //alloc number of bytes written to data Out
    size_t outLength;

    //Update Cryptor
    CCCryptorStatus  update = CCCryptorUpdate(cryptor,
                                              data.bytes,
                                              data.length,
                                              cipherData.mutableBytes,
                                              cipherData.length,
                                              &outLength);
    if (update == kCCSuccess)
    {
        //Cut Data Out with nedded length
        cipherData.length = outLength;

        //Final Cryptor
        CCCryptorStatus final = CCCryptorFinal(cryptor, //CCCryptorRef cryptorRef,
                                               cipherData.mutableBytes, //void *dataOut,
                                               cipherData.length, // size_t dataOutAvailable,
                                               &outLength); // size_t *dataOutMoved)

        if (final == kCCSuccess)
        {
            //Release Cryptor
            //CCCryptorStatus release =
            CCCryptorRelease(cryptor ); //CCCryptorRef cryptorRef
        }
        return cipherData;

    }



}
else
{
    //error

}

return nil;
}



+ (NSString*) decryptData: (NSData*) data withKey: (NSString*) keyString
{
//Key to Data
NSData *key = [keyString dataUsingEncoding:NSUTF8StringEncoding];

// Init cryptor
CCCryptorRef cryptor = NULL;

//Empty IV: initialization vector
NSMutableData *iv =  [NSMutableData dataWithLength:kCCBlockSizeAES128];

// Create Cryptor
CCCryptorStatus createDecrypt = CCCryptorCreateWithMode(kCCDecrypt, // operation
                                                        kCCModeCTR, // mode CTR
                                                        kCCAlgorithmAES, // Algorithm
                                                        ccPKCS7Padding, // padding
                                                        iv.bytes, // can be NULL, because null is full of zeros
                                                        key.bytes, // key
                                                        key.length, // keylength
                                                        NULL, //const void *tweak
                                                        0, //size_t tweakLength,
                                                        0, //int numRounds,
                                                        kCCModeOptionCTR_BE, //CCModeOptions options,
                                                        &cryptor); //CCCryptorRef *cryptorRef


if (createDecrypt == kCCSuccess)
{
    // Alloc Data Out
    NSMutableData *cipherDataDecrypt = [NSMutableData dataWithLength:data.length + kCCBlockSizeAES128];

    //alloc number of bytes written to data Out
    size_t outLengthDecrypt;

    //Update Cryptor
    CCCryptorStatus updateDecrypt = CCCryptorUpdate(cryptor,
                                                    data.bytes, //const void *dataIn,
                                                    data.length,  //size_t dataInLength,
                                                    cipherDataDecrypt.mutableBytes, //void *dataOut,
                                                    cipherDataDecrypt.length, // size_t dataOutAvailable,
                                                    &outLengthDecrypt); // size_t *dataOutMoved)

    if (updateDecrypt == kCCSuccess)
    {
        //Cut Data Out with nedded length
        cipherDataDecrypt.length = outLengthDecrypt;

        // Data to String
        NSString* cipherFinalDecrypt = [[NSString alloc] initWithData:cipherDataDecrypt encoding:NSUTF8StringEncoding];

        //Final Cryptor
        CCCryptorStatus final = CCCryptorFinal(cryptor, //CCCryptorRef cryptorRef,
                                               cipherDataDecrypt.mutableBytes, //void *dataOut,
                                               cipherDataDecrypt.length, // size_t dataOutAvailable,
                                               &outLengthDecrypt); // size_t *dataOutMoved)

        if (final == kCCSuccess)
        {
            //Release Cryptor
            //CCCryptorStatus release =
            CCCryptorRelease(cryptor); //CCCryptorRef cryptorRef
        }

        return cipherFinalDecrypt;
    }
}
    else
{
    //error

}

return nil;
}

To call it:

NSString *key = @"1234567890ABCDEFGHIJKLMNOPQRSTUV";
NSString *stringToEncrypt = @"Gabriel.Massana";

NSData* encrypted = [GM_AES128_CTR encryptString:stringToEncrypt withKey:key];

NSString *decrypted = [GM_AES128_CTR decryptData:encrypted withKey:key];

I'm posting my solution because there are not to much questions for AES CTR in Stackoverflow. Likewise, if someone want to check it and tell me if something is wrong will be very appreciated.

My example in GitHub

How secure is this solution? It is easy to crack the system? What are my possibilities to add more security to AES-CTR?


Solution

  • I'm listing this as a separate answer, but I'm just amplifying what Zaph has already said:

    This is totally broken encryption.

    It's not surprising that this has happened to you. It is a very common problem when you try to build your own scheme. There are just a lot of places you can mess up. But I don't want to understate just how insecure this scheme is. It is really, really broken.

    CTR cannot ever repeat the same nonce+key, and you reuse the nonce every time. This is very different from CBC. In CBC if you reuse the IV, then you make it somewhat easier on the attacker to break your encryption. In CTR, if you reuse the nonce+key it is pretty easy to decrypt the message once you have a few ciphertexts. Some good discussion can be found in RFC3686.

    When used correctly, AES-CTR provides a high level of confidentiality. Unfortunately, AES-CTR is easy to use incorrectly. Being a stream cipher, any reuse of the per-packet value, called the IV, with the same nonce and key is catastrophic. An IV collision immediately leaks information about the plaintext in both packets. For this reason, it is inappropriate to use this mode of operation with static keys. Extraordinary measures would be needed to prevent reuse of an IV value with the static key across power cycles. To be safe, implementations MUST use fresh keys with AES-CTR. The Internet Key Exchange (IKE) [IKE] protocol can be used to establish fresh keys. IKE can also provide the nonce value.

    Note that RNCryptor originally used CTR. I moved back to CBC on the recommendation of Apple after talking with them about how hard easy it is to screw up CTR. If you can avoid CTR, you absolutely should. It is extremely useful for certain problems, but for general file encryption it is seldom appropriate.

    That said, I understand you have a problem in the chip. How is your chip going to get its key? It seems strange to use symmetric encryption with a chip this way. In any case, RNCryptor v1 may meet your needs. You'd likely need to use encryptFromStream:toStream:encryptionKey:HMACKey:error: since I assume the chip can't handle PBKDF2.

    Trying to encrypt my data, I was receiving different data every time, of course decrypting it had incorrect results.

    Any good encryption system will have this property. That's why you need to send your nonce/IV (and if you use passwords, salts) along with the ciphertext.

    NSString *key = @"1234567890ABCDEFGHIJKLMNOPQRSTUV";

    This is not a key. This is a password and dramatically reduces your available keyspace. Keys are typically going to be NSData since they need to be chosen over all possible values, not just ASCII.