Encrypt NSString using AES-128 and a key

匆匆过客 提交于 2019-12-06 10:36:10

Both versions have the same problem: You tell CommonCrypto to write past the end of your buffer, and then you ignore the result.

The first version:

[self mutableBytes], [self length] + kCCBlockSizeAES128, /* output */

The second version:

// Calculate byte block alignment for all calls through to and including final.
bufferPtrSize = CCCryptorGetOutputLength(thisEncipher, plainTextBufferSize, true);

// Allocate buffer.
bufferPtr = [self mutableBytes];

That's not right. You're not allocating anything. You're telling it to write bufferPtrSize bytes to a buffer of size [self length]!

You want to do something more like this (if you really want to encrypt in-place):

// Calculate byte block alignment for all calls through to and including final.
bufferPtrSize = CCCryptorGetOutputLength(thisEncipher, plainTextBufferSize, true);
// Increase my size if necessary:
if (bufferPtrSize > self.length) {
  self.length = bufferPtrSize;
}

I'm also not sure why encrypting is in-place while decrypting is not; the latter is, if anything, easier to do.

Your second version has an additional problems:

  • You free something you didn't allocate: if(bufferPtr) free(bufferPtr);
  • You potentially read past the end of the string: (const void *)[key UTF8String], kCCKeySizeAES128

Additional crypto problems:

  • Keys are supposed to be fixed-size and have a decent amount of entropy. Naively converting a string to bytes does not a good key make (for one, keys longer than 16 bytes are effectively truncated). The least you could do is hash it. You might also want to iterate the hash, or just use PBKDF2 (admittedly I haven't found the spec/test vectors for PBKDF2...)
  • You almost certainly want to use a random IV too (see SecRandomCopyBytes).

Addendum:

The reason why you're seeing a truncated result is because you're returning a truncated answer (with PKCS7 padding, the encrypted result is always bigger than the original data). Chances (about 255/256) are that the last ciphertext block was incorrectly padded (because you gave CCryptor truncated data), so ccStatus says an error happened but you ignored this and returned the result anyway. This is incredibly bad practice. (Additionally, you really want to use a MAC with CBC to avoid the padding oracle security hole.)

EDIT:

Some code that seems to work looks something like this (complete with test cases):

Notes:

  • Not actually tested on iOS (though the non-iOS code should work on iOS; it's just that SecRandomCopyBytes is a slightly nicer interface but not available on OS X).
  • The read() loop might be right, but is not thoroughly tested.
  • The ciphertext is prefixed with the IV. This is the "textbook" method, but makes ciphertexts bigger.
  • There is no authentication, so this code can act as a padding oracle.
  • There is no support for AES-192 or AES-256. It would not be difficult to add (you'd just need to switch on the key length and pick the algorithm appropriately).
  • The key is specified as a NSData, so you'll need to do something like [string dataUsingEncoding:NSUTF8StringEncoding]. For bonus points, run it through CC_SHA256 and take the first 16 output bytes.
  • There's no in-place operation. I didn't think it was worth it.

.

#include <Foundation/Foundation.h>
#include <CommonCrypto/CommonCryptor.h>

#if TARGET_OS_IPHONE
#include <Security/SecRandom.h>
#else
#include <fcntl.h>
#include <unistd.h>
#endif

@interface NSData(AES)
- (NSData*) encryptedDataUsingAESKey: (NSData *) key;
- (NSData*) decryptedDataUsingAESKey: (NSData *) key;
@end
@implementation NSData(AES)

- (NSData*) encryptedDataUsingAESKey: (NSData *) key {
        uint8_t iv[kCCBlockSizeAES128];
#if TARGET_OS_IPHONE
        if (0 != SecRandomCopyBytes(kSecRandomDefault, sizeof(iv), iv))
        {
                return nil;
        }
#else
        {
                int fd = open("/dev/urandom", O_RDONLY);
                if (fd < 0) { return nil; }
                ssize_t bytesRead;
                for (uint8_t * p = iv; (bytesRead = read(fd,p,iv+sizeof(iv)-p)); p += (size_t)bytesRead) {
                        // 0 means EOF.
                        if (bytesRead == 0) { close(fd); return nil; }
                        // -1, EINTR means we got a system call before any data could be read.
                        // Pretend we read 0 bytes (since we already handled EOF).
                        if (bytesRead < 0 && errno == EINTR) { bytesRead = 0; }
                        // Other errors are real errors.
                        if (bytesRead < 0) { close(fd); return nil; }
                }
                close(fd);
        }
#endif
        size_t retSize = 0;
        CCCryptorStatus result = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, 
                                     [key bytes], [key length],
                                     iv,
                                     [self bytes], [self length],
                                     NULL, 0,
                                     &retSize);
        if (result != kCCBufferTooSmall) { return nil; }

        // Prefix the data with the IV (the textbook method).
        // This requires adding sizeof(iv) in a few places later; oh well.
        void * retPtr = malloc(retSize+sizeof(iv));
        if (!retPtr) { return nil; }

        // Copy the IV.
        memcpy(retPtr, iv, sizeof(iv));

        result = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, 
                                     [key bytes], [key length],
                                     iv,
                                     [self bytes], [self length],
                                     retPtr+sizeof(iv),retSize,
                                     &retSize);
        if (result != kCCSuccess) { free(retPtr); return nil; }

        NSData * ret = [NSData dataWithBytesNoCopy:retPtr length:retSize+sizeof(iv)];
        // Does +[NSData dataWithBytesNoCopy:length:] free if allocation of the NSData fails?
        // Assume it does.
        if (!ret) { free(retPtr); return nil; }
        return ret;
}

- (NSData*) decryptedDataUsingAESKey: (NSData *) key {
        const uint8_t * p = [self bytes];
        size_t length = [self length];
        if (length < kCCBlockSizeAES128) { return nil; }

        size_t retSize = 0;
        CCCryptorStatus result = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, 
                                     [key bytes], [key length],
                                     p,
                                     p+kCCBlockSizeAES128, length-kCCBlockSizeAES128,
                                     NULL, 0,
                                     &retSize);
        if (result != kCCBufferTooSmall) { return nil; }

        void * retPtr = malloc(retSize);
        if (!retPtr) { return nil; }

        result = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, 
                                     [key bytes], [key length],
                                     p,
                                     p+kCCBlockSizeAES128, length-kCCBlockSizeAES128,
                                     retPtr, retSize,
                                     &retSize);
        if (result != kCCSuccess) { free(retPtr); return nil; }

        NSData * ret = [NSData dataWithBytesNoCopy:retPtr length:retSize];
        // Does +[NSData dataWithBytesNoCopy:length:] free if allocation of the NSData fails?
        // Assume it does.
        if (!ret) { free(retPtr); return nil; }
        return ret;
}

@end

void test(NSData * data, NSData * key)
{
        NSLog(@"%@, %@", data, key);
        NSData * enc = [data encryptedDataUsingAESKey:key];
        NSLog(@"%@", enc);
        NSData * dec = [enc decryptedDataUsingAESKey:key];
        NSLog(@"%@", dec);
        NSLog((data == dec || [data isEqual:dec]) ? @"pass" : @"FAIL");
}

int main()
{
#define d(x) [NSData dataWithBytesNoCopy:("" x) length:sizeof("" x)-1 freeWhenDone:0]
        [NSAutoreleasePool new];
        NSData * key = d("0123456789abcdef");
        test([NSData data], key);
        test(d(""), key);
        test(d("a"), key);
        test(d("0123456789abcde"), key);
        test(d("0123456789abcdef"), key);
        test(d("0123456789abcdef0"), key);
        test(d("0123456789abcdef0123456789abcde"), key);
        test(d("0123456789abcdef0123456789abcdef"), key);
        test(d("0123456789abcdef0123456789abcdef0"), key);
}

Regarding your *de*cryption code

kCCParamError is, as its name says, an error. Why are you treating it as success? If you get that error, it means you did something wrong; look at the parameters you passed and figure out what.

This is probably why you're getting “garbage”: CCCrypt (decrypting) never actually gave you anything, because it could not work with whatever values you gave it. What you're getting is whatever was lying around in the output buffer when you allocated it.

If you switch to calloc or to creating the NSMutableData object before calling CCCrypt and using its mutableBytes as the buffer, I think you'll find that the buffer then always contains all zeroes. Same reason: CCCrypt is not filling it out, because it's failing, because you passed one or more wrong values (parameter error).

You need to fix the parameter error before you can expect this to work.

You might try breaking the CCCrypt call into calls to CCCryptorCreate, CCCryptorUpdate, CCCryptorFinal, and CCCryptorRelease, at least temporarily, to see where it's going wrong.

Encryption: The same problem, or no problem at all

Is your encryption method returning YES or NO? I'm guessing it returns NO, because the code appears to be mostly the same between the encryption and decryption methods, so whatever you have wrong in your decryption code is probably wrong in your encryption code as well. See what CCCrypt is returning and, if it's failing, get it working.

If it is returning YES (CCCrypt is succeeding), then I wonder what you mean by “returns me a bunch of garbage”. Are you referring to the contents of the data object you sent the encryptWithAES128Key: message to?

If that's the case, then that is the expected result. Your code encrypts the contents of the data object in place, overwriting the cleartext with the ciphertext. What you're seeing isn't pure “garbage”—it's the ciphertext! Decrypting it (successfully) will reveal the cleartext again.

By the way, you have the “encrypts in-place, since this is a mutable data object” comment on the creation of an output buffer in order to not work in-place in the decryption code. It should be in the encryption method, where you are working in-place. I suggest making either both work in-place or neither work in-place; consistency is a virtue.

If you have following padding changes in your code remove it and always keep kCCOptionPKCS7Padding on, this should solve your issue.

if (encryptOrDecrypt == kCCEncrypt) {
    if (*pkcs7 != kCCOptionECBMode) {
        if ((plainTextBufferSize % kChosenCipherBlockSize) == 0) {
            *pkcs7 = 0x0000;
        } else {
            *pkcs7 = kCCOptionPKCS7Padding;
        }
    }
}

You should use RNCryptor, it's high level encryption opensource api around CommonCrypto, and high level encryption API's are the best practice for cryptography these days, because it's easy for experts to make mistakes in the implementations using crypto primatives, and there are alot of side channel attacks out there that take advantage of those mistakes.

For example, you code says /* initialization vector (optional) * / 100% not true, thus you've totally crippled AES-CBC, and that's just the most obvious issue.

In your case RNCryptor is ideal, I'd strongly suggest you don't roll your own implementation.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!