开发者

NSData with CCCrypt in multi-threaded environment

开发者 https://www.devze.com 2023-03-08 03:36 出处:网络
I have a file that was encrypted using AES. I use the following NSData category: #import <CommonCrypto/CommonCryptor.h>

I have a file that was encrypted using AES. I use the following NSData category:

#import <CommonCrypto/CommonCryptor.h>

@implementation NSData (AES)

- (NSData *)AES256DecryptWithKey:(NSString *)key {

    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding开发者_开发问答:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or 
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    size_t bufferSize = dataLength + kCCBlockSizeAES128;

    void *buffer = malloc(bufferSize);

    size_t numBytesDecrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          kCCAlgorithmAES128,
                                          kCCOptionPKCS7Padding,
                                          keyPtr,
                                          kCCKeySizeAES256,
                                          NULL /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer,       bufferSize, /* output */
                                          &numBytesDecrypted);

    NSLog(@"Bytes decrypted: %d",numBytesDecrypted);

    if (cryptStatus == kCCSuccess) {
        //the returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
    }

    NSLog(@"Decrypt failed with error code %d",cryptStatus);
    free(buffer); //free the buffer;
    return nil;
}

@end

The descrypt process seems to work fine when I load the entire file from filesystem with the following code:

[NSData dataWithContentsOfFile:dataPath];

The problem occurs when the file is not read with the previous call but when an external code that chunk the file and init an NSData with only a little chunk of data and try to decrypt this, in particolar the problems occurs when different thread use this code (or at least is what i think):

- (NSData *)readDataOfLength:(NSUInteger)length
{
    HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length);

    if (![self openFileIfNeeded])
    {
        // File opening failed,
        // or response has been aborted due to another error.
        return nil;
    }

    // Determine how much data we should read.
    // 
    // It is OK if we ask to read more bytes than exist in the file.
    // It is NOT OK to over-allocate the buffer.

    UInt64 bytesLeftInFile = fileLength - fileOffset;

    NSUInteger bytesToRead = (NSUInteger)MIN(length, bytesLeftInFile);

    // Make sure buffer is big enough for read request.
    // Do not over-allocate.

    if (buffer == NULL || bufferSize < bytesToRead)
    {
        bufferSize = bytesToRead;
        buffer = reallocf(buffer, (size_t)bufferSize);

        if (buffer == NULL)
        {
            HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self);

            [self abort];
            return nil;
        }
    }

    // Perform the read

    HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, bytesToRead);

    ssize_t result = read(fileFD, buffer, bytesToRead);

    // Check the results

    if (result < 0)
    {
        HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath);

        [self abort];
        return nil;
    }
    else if (result == 0)
    {
        HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath);

        [self abort];
        return nil;
    }
    else // (result > 0)
    {
        HTTPLogVerbose(@"%@[%p]: Read %d bytes from file", THIS_FILE, self, result);

        fileOffset += result;

        NSData *data = [NSData dataWithBytes:buffer length:result];
        return [data AES256DecryptWithKey:@"abcdefghijklmnopqrstuvwxyz123456"];
        //return data;
    }
}

What happens is that the function CCCrypt in this case fail with error code -4304 AKA "kCCDecodeError - Input data did not decode or decrypt properly."

Moreover if in the CCCrypt call instead of kCCOptionPKCS7Padding i pass 0 -> No padding the method decrypt the first chunk of data but when the thread is switched it fails with -4300 AKA "kCCParamError - Illegal parameter value."

With the following message in console:

[Switching to process 13059 thread 0x0]
2011-05-25 18:00:03.631 Drm[1843:6e0b] Bytes decrypted: 131072
2011-05-25 18:00:03.647 Drm[1843:6e0b] Bytes decrypted: 68096
[Switching to process 11779 thread 0x0]
2011-05-25 18:00:04.547 Drm[1843:6e0b] Bytes decrypted: 0
2011-05-25 18:00:04.555 Drm[1843:6e0b] Decrypt failed with error code -4300

Someone can help?


AES is a block cipher. You must decrypt it one block at a time. An AES block is 128-bits (this is not related to the "256" in AES256DecryptWithKey). So you must ensure that the data you pass is a multiple of 16 bytes.

I haven't tried to use CCCrypt() this way, that's not really what it's for. CCCrypt() is a convenience function when you want to do a one-shot decryption. When you want to do "as you go" decryption, you use CCCryptorCreate() and then multiple calls to CCCryptorUpdate() and finally CCCryptorFinal() (or you can call CCCryptorFinal() and then CCCryptorReset() to decrypt more stuff with the same key). Finally you call CCCryptorRelease() to release your cryptor.

EDIT I was thinking about this some more, and realized that CCCrypt() can't be used this way even if you broke up the input into 16-byte chunks. Each block of AES encryption modifies the IV of the next block, so you can't just start someone in the middle of a stream. That's why you need a persistent CCCryptor object over the entire session.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号