Search code examples
iosobjective-cencryptioncryptographycommoncrypto

Common Crypto -- Testing for bad key in decryption?


I have written up code that makes use of iOS's Common Crypto to encrypt and decrypt an NSData object. The encryption keys are AES128, and are stored in the iOS keychain. I can encrypt and decrypt the data successfully, so I know that portion of code is working. As a sanity check, however, I've also generated a second AES128 key and attempted to decrypt data that was encrypted with the first encryption key. I was expecting the CCCryptorStatus value to be something other than kCCSuccess, however this was not the case. I received back an NSData object and no error. My encrypt/decrypt code looks something like this...

-(NSData *)dataDecryptedUsingAlgorithm:(CCAlgorithm)algorithm
                                  data:(NSData *)data
                                   key:(id)key
                  initializationVector:(id)iv
                               options:(CCOptions)options
                                 error:(CCCryptorStatus *)error {
    CCCryptorRef cryptor = NULL;
    CCCryptorStatus status = kCCSuccess;

    NSParameterAssert([key isKindOfClass: [NSData class]] || [key isKindOfClass: [NSString class]]);
    NSParameterAssert(iv == nil || [iv isKindOfClass: [NSData class]] || [iv isKindOfClass: [NSString class]]);

    NSMutableData * keyData, * ivData;
    if ( [key isKindOfClass: [NSData class]] )
        keyData = (NSMutableData *) [key mutableCopy];
    else
        keyData = [[key dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];

    if ( [iv isKindOfClass: [NSString class]] )
        ivData = [[iv dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];
    else
        ivData = (NSMutableData *) [iv mutableCopy];    // data or nil

    //    [keyData autorelease];
    //    [ivData autorelease];

    // ensure correct lengths for key and iv data, based on algorithms
    FixKeyLengths( algorithm, keyData, ivData );

    status = CCCryptorCreate( kCCDecrypt, algorithm, options,
                             [keyData bytes], [keyData length], [ivData bytes],
                             &cryptor );

    if ( status != kCCSuccess )
    {
        if ( error != NULL )
            *error = status;
        return ( nil );
    }

    NSData *result = [self runCryptor:cryptor onData:data result:&status];
    if ( (result == nil) && (error != NULL) )
        *error = status;

    CCCryptorRelease(cryptor);

    return ( result );
}

-(NSData *)dataEncryptedUsingAlgorithm:(CCAlgorithm) algorithm
                                  data:(NSData *)data
                                   key:(id)key
                  initializationVector:(id)iv
                               options:(CCOptions)options
                                 error:(CCCryptorStatus *)error {
    CCCryptorRef cryptor = NULL;
    CCCryptorStatus status = kCCSuccess;

    NSParameterAssert([key isKindOfClass: [NSData class]] || [key isKindOfClass: [NSString class]]);
    NSParameterAssert(iv == nil || [iv isKindOfClass: [NSData class]] || [iv isKindOfClass: [NSString class]]);

    NSMutableData * keyData, * ivData;
    if ( [key isKindOfClass: [NSData class]] )
        keyData = (NSMutableData *) [key mutableCopy];
    else
        keyData = [[key dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];

    if ( [iv isKindOfClass: [NSString class]] )
        ivData = [[iv dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];
    else
        ivData = (NSMutableData *) [iv mutableCopy];    // data or nil

    //    [keyData autorelease];
    //    [ivData autorelease];

    // ensure correct lengths for key and iv data, based on algorithms
    FixKeyLengths( algorithm, keyData, ivData );

    status = CCCryptorCreate( kCCEncrypt, algorithm, options,
                             [keyData bytes], [keyData length], [ivData bytes],
                             &cryptor );

    if ( status != kCCSuccess )
    {
        if ( error != NULL )
            *error = status;
        return ( nil );
    }

    NSData *result = [self runCryptor:cryptor onData:data result:&status];
    if ( (result == nil) && (error != NULL) )
        *error = status;

    CCCryptorRelease( cryptor );

    return ( result );
}

-(NSData *)runCryptor:(CCCryptorRef)cryptor onData:(NSData *)data result:(CCCryptorStatus *)status {
    size_t bufsize = CCCryptorGetOutputLength( cryptor, (size_t)[data length], true );
    void * buf = malloc( bufsize );
    size_t bufused = 0;
    size_t bytesTotal = 0;
    *status = CCCryptorUpdate( cryptor, [data bytes], (size_t)[data length],
                              buf, bufsize, &bufused );
    if ( *status != kCCSuccess )
    {
        free( buf );
        return ( nil );
    }

    bytesTotal += bufused;

    // From Brent Royal-Gordon (Twitter: architechies):
    //  Need to update buf ptr past used bytes when calling CCCryptorFinal()
    *status = CCCryptorFinal( cryptor, buf + bufused, bufsize - bufused, &bufused );
    if ( *status != kCCSuccess )
    {
        free( buf );
        return ( nil );
    }

    bytesTotal += bufused;

    return ( [NSData dataWithBytesNoCopy: buf length: bytesTotal] );
}

When I call the encrypt and decrypt methods, I am passing in kCCAlgorithmAES128 as my algorithm, and kCCOptionPKCS7Padding as my options. Is there a way to catch when a bad key is used for decryption so I can return the appropriate error?


Solution

  • The only reliable way to distinguish between a bad key and corrupted data is, as Zaph notes, some kind of crib (i.e. used in the broadest sense of the term; i.e. something you know about the encryption). If you're interested in an approach to this, see the RNCryptor v4 spec. There is no implementation of this yet, it's just a spec, but it includes a validator field that can be used to determine whether the password is correct. It uses an HKDF-Expand step that converts some of your initial keying material into a validation token.


    As a note, this part of your method is quite concerning:

    if ( [iv isKindOfClass: [NSString class]] )
        ivData = [[iv dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];
     else
        ivData = (NSMutableData *) [iv mutableCopy];    // data or nil
    

    If a string is passed in, this has a dramatically smaller keyspace than you may expect. Even if it is 16 totally random bytes of string, legal UTF8 strings represent a much smaller space than an equivalent 16 bytes of random data.