Обе версии имеют одну и ту же проблему: вы приказываете CommonCrypto писать после конца буфера, а затем игнорируете результат.
Первая версия:
[self mutableBytes], [self length] + kCCBlockSizeAES128, /* output */
Вторая версия:
// Calculate byte block alignment for all calls through to and including final.
bufferPtrSize = CCCryptorGetOutputLength(thisEncipher, plainTextBufferSize, true);
// Allocate buffer.
bufferPtr = [self mutableBytes];
Это не правильно. Вы ничего не выделяете. Вы предлагаете записать bufferPtrSize
байтов в буфер размером [self length]
!
Вы хотите сделать что-то более похожее на это (если вы действительно хотите зашифровать на месте):
// 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;
}
Я также не уверен, почему шифрование используется, а дешифрование - нет; последнее, во всяком случае, легче сделать.
Ваша вторая версия имеет дополнительные проблемы:
- Вы освобождаете то, что не выделяли:
if(bufferPtr) free(bufferPtr);
- Возможно, вы читаете после конца строки:
(const void *)[key UTF8String], kCCKeySizeAES128
Дополнительные проблемы с шифрованием:
- Ключи должны быть фиксированного размера и иметь приличное количество энтропии. Наивное преобразование строки в байты не дает хорошего ключа (например, ключи длиной более 16 байт эффективно усекаются). Самое меньшее, что вы могли бы сделать - это хешировать это. Возможно, вы также захотите перебрать хеш или просто использовать PBKDF2 (правда, я не нашел векторы спецификации / теста для PBKDF2 ...)
- Вы почти наверняка хотите использовать случайный IV (см. SecRandomCopyBytes).
Добавление:
Причина, по которой вы видите усеченный результат, заключается в том, что вы возвращаете усеченный ответ (при заполнении PKCS7 зашифрованный результат всегда больше исходных данных). Скорее всего (около 255/256), что последний блок зашифрованного текста был заполнен неправильно (потому что вы дали усеченные данные CCryptor), поэтому ccStatus
говорит, что произошла ошибка, но вы проигнорировали это и все равно вернули результат. Это невероятно плохая практика. (Кроме того, вы действительно хотите использовать MAC с CBC, чтобы избежать пробела безопасности oracle padding .)
EDIT:
Некоторый код, который, кажется, работает, выглядит примерно так (в комплекте с контрольными примерами):
Примечания:
- На самом деле не тестировался на iOS (хотя не-iOS код должен работать на iOS; просто SecRandomCopyBytes - немного более приятный интерфейс, но недоступный в OS X).
- Цикл read () может быть правильным, но не проверен полностью.
- Зашифрованный текст начинается с префикса IV. Это метод «учебник», но он увеличивает число шифротекстов.
- Аутентификация отсутствует, поэтому этот код может действовать как дополнительный оракул.
- AES-192 или AES-256 не поддерживается. Добавить несложно (нужно просто включить длину ключа и правильно выбрать алгоритм).
- Ключ указан как NSData, поэтому вам нужно сделать что-то вроде
[string dataUsingEncoding:NSUTF8StringEncoding]
. Для получения бонусных баллов выполните его через CC_SHA256 и возьмите первые 16 выходных байтов.
- Там нет операции на месте. Я не думал, что это того стоило.
.
#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);
}