0

I am receiving data encrypted with SJCL into an iOS app where I need to decrypt it. The other end is using SJCL with the AES CCM mode which is not supported by the Apple's CommonCrypto so I use the VPCCMCrypt library for this. It is not possible to make any changes to the other side sending the SJCL cipher text.

Here are my methods to decrypt:

+ (NSData *)decryptSjcl:(NSDictionary *)cipher password:(NSString *)password {
    if (cipher == nil || password == nil) {
        return nil;
    }

    int version = [cipher[@"v"] intValue];
    NSString *iv = cipher[@"iv"];
    uint iter = [cipher[@"iter"] unsignedIntValue];
    uint ks = [cipher[@"ks"] unsignedIntValue];
    uint ts = [cipher[@"ts"] unsignedIntValue];
    NSString *mode = cipher[@"mode"];
    NSString *adata = cipher[@"adata"];
    NSString *algorithm = cipher[@"cipher"];
    NSString *salt = cipher[@"salt"];
    NSString *ct = cipher[@"ct"];

    if (version != 1 || ! [@"aes" isEqualToString:algorithm]) {
        return nil;
    }

    NSData *rawIv = [[NSData alloc] initWithBase64EncodedString:iv options:0];
    NSData *rawSalt = [[NSData alloc] initWithBase64EncodedString:salt options:0];
    NSData *rawAdata = [[NSData alloc] initWithBase64EncodedString:adata options:0];
    NSData *cipherData = [[NSData alloc] initWithBase64EncodedString:ct options:0];
    NSData *key;

    NSMutableData *decipheredData = nil;

    if ([@"ccm" isEqualToString:mode]) {
        key = [Cryptor sjclAesKeyForPassword:password salt:rawSalt iterations:iter keySize:ks];
        decipheredData = [Cryptor decryptAesCcmData:cipherData iv:rawIv key:key adata:rawAdata tagSize:ts];
    }

    return decipheredData;
}

SJCL key generation:

+ (NSData *)sjclAesKeyForPassword:(NSString *)password salt:(NSData *)salt iterations:(uint)iterations keySize:(uint)keySizeBits {
    NSMutableData *derivedKey = [NSMutableData dataWithLength:keySizeBits / 8];

    int result = CCKeyDerivationPBKDF(kCCPBKDF2,
            password.UTF8String,
            [password lengthOfBytesUsingEncoding:NSUTF8StringEncoding],
            salt.bytes,
            salt.length,
            kCCPRFHmacAlgSHA256,
            iterations,
            derivedKey.mutableBytes,
            derivedKey.length);

    NSAssert(result == kCCSuccess, @"Unable to create AES key for password: %d", result);

    return derivedKey;
}

AES CCM decryption:

+ (NSMutableData *)decryptAesCcmData:(NSData *)cipherData iv:(NSData *)iv key:(NSData *)key adata:(NSData *)adata tagSize:(uint)tagSizeBits {
    VPCCMCrypt *ccm = [[VPCCMCrypt alloc] initWithKey:key
                                                   iv:iv
                                                adata:adata
                                            tagLength:tagSizeBits / 8];

    [ccm decryptDataWithData:cipherData
               finishedBlock:^(NSData *data) {
                   NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

                   NSLog(@"Decrypted SJCL message: %@", result);
               }
                  errorBlock:^(NSError *error) {
                      NSLog(@"ERROR");
                  }];

    return nil;
}

All the input data from the SJCL is correctly parsed (IV, salt, key size, tag size, PBKDF iterations, cipher text) and decoded to NSData from their Base64 encoded representation. Password is used the same. All data including the created AES key are not nil.

At the end it fails inside the VPCCMCrypt verifying the CCM tag (tags are different). Is there something wrong with the code above? Is there any other iOS/Objective-C/Swift library to decrypt AES CCM or even better the SJCL? I am not interested in a JavaScript wrapper of the SJCL library.

For testing I am using simple encrypted data from the SJCL's demo page.

EDIT:

As said in comments the SJCL send 16 bytes IV instead of max 12 bytes for the CCM mode and then internally clamps it to max 12 bytes when deciphering. Here is the updated decrypt method:

+ (NSData *)decryptSjcl:(NSDictionary *)cipher password:(NSString *)password {
    if (cipher == nil || password == nil) {
        return nil;
    }

    int version = [cipher[@"v"] intValue];
    NSString *iv = cipher[@"iv"];
    uint iter = [cipher[@"iter"] unsignedIntValue];
    uint ks = [cipher[@"ks"] unsignedIntValue];
    uint ts = [cipher[@"ts"] unsignedIntValue];
    NSString *mode = cipher[@"mode"];
    NSString *adata = cipher[@"adata"];
    NSString *algorithm = cipher[@"cipher"];
    NSString *salt = cipher[@"salt"];
    NSString *ct = cipher[@"ct"];

    if (version != 1 || ! [@"aes" isEqualToString:algorithm]) {
        return nil;
    }

    NSMutableData *rawIv = [[NSMutableData alloc] initWithBase64EncodedString:iv options:0];
    NSMutableData *rawSalt = [[NSMutableData alloc] initWithBase64EncodedString:salt options:0];
    NSMutableData *rawAdata = [[NSMutableData alloc] initWithBase64EncodedString:adata options:0];
    NSMutableData *cipherData = [[NSMutableData alloc] initWithBase64EncodedString:ct options:0];
    NSData *key;

    NSData *decipheredData = nil;

    if ([@"ccm" isEqualToString:mode]) {
        key = [Cryptor sjclAesKeyForPassword:password salt:rawSalt iterations:iter keySize:ks];

        // Clamp the SJCL IV - They use a 16 byte IV for CCM mode which is against specification and they do a funky
        // clamping. CCM mode IV should be max 13 bytes long. They almost always clamp it to 13 bytes but save the whole
        // 16 bytes in their JSON container.
        // for (L=2; L<4 && ol >>> 8*L; L++) {}
        // if (L < 15 - ivl) { L = 15-ivl; }
        // iv = w.clamp(iv,8*(15-L));
        int64_t ivl = rawIv.length;
        int64_t ol = cipherData.length - (ts / 8);
        int64_t L = 2;
        for (L = 2; L < 4 && ol >> 8*L; L++) {}
        if (L < 15 - ivl) {
            L = 15 - ivl;
        }
        NSRange subrange = NSMakeRange(0, (NSUInteger)(15 - L));

        decipheredData = [Cryptor decryptAesCcmData:cipherData iv:[rawIv subdataWithRange:subrange] key:key adata:rawAdata tagSize:ts];
    }
    else {
        decipheredData = nil;
    }

    return decipheredData;
}

One last missing thing is verifying the TAG, I am unable to do that. Any ideas?

shelll
  • 3,234
  • 3
  • 33
  • 67

1 Answers1

0

SJCL doesn't use the whole (128/192/256bit) IV in AES-CCM mode, but the demo page shows it. Try it with less bytes.

Here you can find how the IV length calculation works: http://bitwiseshiftleft.github.io/sjcl/doc/symbols/src/core_ccm.js.html

for (L=2; L<4 && ol >>> 8*L; L++) {}
if (L < 15 - ivl) { L = 15-ivl; }
iv = w.clamp(iv,8*(15-L));
soyer
  • 316
  • 2
  • 10
  • Thank you, but I have already solved this part of the issues and have added the IV clamping in my code. I did not have the time to update my question though, and no one was caring about it either. I will add the Objective-C for clamping it later. With the clamped IV the decryption works but the TAG verification still fails. Any idea why the TAG verification fails even with the correctly clamped IV? Does the SJCL do something funky with the TAG like with the IV which is against the specification? – shelll Apr 20 '16 at 14:27
  • I have just updated my question with the updated Objective-C code to clamp the IV to 12 bytes max. I will accept your answer because the decryption works but the TAG is still not verified... – shelll May 04 '16 at 08:14
  • The VPCCMCrypt decryptDataWithData verifies the tag, so if the decode works, the tag is verified! https://github.com/billp/VPCCMCrypt/blob/master/lib/VPCCMCrypt.m#L308 – soyer May 13 '16 at 11:02
  • It tries to verify the tag first and if the verification fails it does not continue to decrypting of the data. I have to disable the tag verification code to decrypt the data. – shelll May 13 '16 at 11:08