Зашифруйте NSString, используя AES-128 и ключ - PullRequest
2 голосов
/ 28 июня 2011

У меня есть базовое приложение для заметок, и я хочу разрешить пользователю иметь зашифрованные или защищенные заметки. У меня есть пользовательский интерфейс, но сейчас я не могу заставить работать шифрование. Либо это возвращает мне кучу мусора, либо вообще ничего. Это то, что я использую для en / decrypt:

- (BOOL) encryptWithAES128Key: (NSString *) key {
    // 'key' should be 16 bytes for AES128, will be null-padded otherwise
    char * keyPtr[kCCKeySizeAES128+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];

    // encrypts in-place, since this is a mutable data object
    size_t numBytesEncrypted = 0;
    CCCryptorStatus result = CCCrypt(kCCEncrypt, kCCAlgorithmAES128 , kCCOptionPKCS7Padding, 
                                     keyPtr, kCCKeySizeAES128,
                                     NULL /* initialization vector (optional) */, 
                                     [self mutableBytes], [self length], /* input */
                                     [self mutableBytes], [self length] + kCCBlockSizeAES128, /* output */
                                     &numBytesEncrypted);

    return (result == kCCSuccess);
}

- (NSMutableData *) decryptWithAES128Key: (NSString *) key {
    // 'key' should be 16 bytes for AES128, will be null-padded otherwise
    char * keyPtr[kCCKeySizeAES128+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];

    // encrypts in-place, since this is a mutable data object
    size_t bufferSize           = [self length] + kCCBlockSizeAES128;
    void* buffer                = malloc(bufferSize);

    size_t numBytesEncrypted = 0;
    CCCryptorStatus result = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, 
                                     keyPtr, kCCKeySizeAES128,
                                     NULL /* initialization vector (optional) */, 
                                     [self bytes], [self length], /* input */
                                     buffer, bufferSize, /* output */
                                     &numBytesEncrypted);   


    if(result == kCCSuccess || result == kCCParamError) {
        return [[NSMutableData dataWithBytesNoCopy:buffer length:numBytesEncrypted] retain];
    }

    return nil;
}

Кто-нибудь знает, почему это может пойти не так?

Редактировать 1: Я изменил мой код en / decryption, чтобы быть таким же. Вот как это выглядит сейчас:

- (BOOL) encryptWithAES128Key: (NSString *) key {
    CCCryptorStatus ccStatus = kCCSuccess;
    // Symmetric crypto reference.
    CCCryptorRef thisEncipher = NULL;
    // Cipher Text container.
    NSData * cipherOrPlainText = nil;
    // Pointer to output buffer.
    uint8_t * bufferPtr = NULL;
    // Total size of the buffer.
    size_t bufferPtrSize = 0;
    // Remaining bytes to be performed on.
    size_t remainingBytes = 0;
    // Number of bytes moved to buffer.
    size_t movedBytes = 0;
    // Length of plainText buffer.
    size_t plainTextBufferSize = 0;
    // Placeholder for total written.
    size_t totalBytesWritten = 0;
    // A friendly helper pointer.
    uint8_t * ptr;

    // Initialization vector; dummy in this case 0's.
    uint8_t iv[kCCBlockSizeAES128];
    memset((void *) iv, 0x0, (size_t) sizeof(iv));
    plainTextBufferSize = [self length];

    ccStatus = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, (const void *)[key UTF8String], kCCKeySizeAES128, (const void *)iv, &thisEncipher);

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

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

    // Zero out buffer.
    //memset((void *)bufferPtr, 0x0, bufferPtrSize);

    // Initialize some necessary book keeping.

    ptr = bufferPtr;

    // Set up initial size.
    remainingBytes = bufferPtrSize;

    // Actually perform the encryption or decryption.
    ccStatus = CCCryptorUpdate(thisEncipher, (const void *) [self bytes], plainTextBufferSize, ptr, remainingBytes, &movedBytes);

    ptr += movedBytes;
    remainingBytes -= movedBytes;
    totalBytesWritten += movedBytes;

    // Finalize everything to the output buffer.
    ccStatus = CCCryptorFinal(thisEncipher, ptr, remainingBytes, &movedBytes);

    cipherOrPlainText = [NSData dataWithBytes:(const void *)bufferPtr length:(NSUInteger)totalBytesWritten];
    NSLog(@"data: %@", cipherOrPlainText);

    NSLog(@"buffer: %s", bufferPtr);

    CCCryptorRelease(thisEncipher);
    thisEncipher = NULL;
    if(bufferPtr) free(bufferPtr);
}

- (NSMutableData *) decryptWithAES128Key: (NSString *) key {    
    CCCryptorStatus ccStatus = kCCSuccess;
    // Symmetric crypto reference.
    CCCryptorRef thisEncipher = NULL;
    // Cipher Text container.
    NSData * cipherOrPlainText = nil;
    // Pointer to output buffer.
    uint8_t * bufferPtr = NULL;
    // Total size of the buffer.
    size_t bufferPtrSize = 0;
    // Remaining bytes to be performed on.
    size_t remainingBytes = 0;
    // Number of bytes moved to buffer.
    size_t movedBytes = 0;
    // Length of plainText buffer.
    size_t plainTextBufferSize = 0;
    // Placeholder for total written.
    size_t totalBytesWritten = 0;
    // A friendly helper pointer.
    uint8_t * ptr;

    // Initialization vector; dummy in this case 0's.
    uint8_t iv[kCCBlockSizeAES128];
    memset((void *) iv, 0x0, (size_t) sizeof(iv));
    plainTextBufferSize = [self length];

    ccStatus = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, (const void *)[key UTF8String], kCCKeySizeAES128, (const void *)iv, &thisEncipher);

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

    // Allocate buffer.
    bufferPtr = malloc( bufferPtrSize * sizeof(uint8_t) );

    // Zero out buffer.
    memset((void *)bufferPtr, 0x0, bufferPtrSize);

    // Initialize some necessary book keeping.

    ptr = bufferPtr;

    // Set up initial size.
    remainingBytes = bufferPtrSize;

    // Actually perform the encryption or decryption.
    ccStatus = CCCryptorUpdate(thisEncipher, (const void *) [self bytes], plainTextBufferSize, ptr, remainingBytes, &movedBytes);

    ptr += movedBytes;
    remainingBytes -= movedBytes;
    totalBytesWritten += movedBytes;

    // Finalize everything to the output buffer.
    ccStatus = CCCryptorFinal(thisEncipher, ptr, remainingBytes, &movedBytes);

    cipherOrPlainText = [NSData dataWithBytes:(const void *)bufferPtr length:(NSUInteger)totalBytesWritten];
    NSLog(@"data: %@", cipherOrPlainText);

    NSLog(@"buffer: %s", bufferPtr);

    CCCryptorRelease(thisEncipher);
    thisEncipher = NULL;
    if(bufferPtr) free(bufferPtr);

    return [NSMutableData dataWithData:cipherOrPlainText];
}

Этот код несколько работает. Если я зашифрую эту строку парольной фразой «1234567890123456»:

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>dict</key>
    <dict>
        <key>device</key>
        <string>Tristan's Magical Macbook of Death</string>
        <key>text</key>
        <string>e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxMDM4XGNvY29hc3VicnRm
MzYwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEhlbHZldGljYTt9Cntc
Y29sb3J0Ymw7XHJlZDI1NVxncmVlbjI1NVxibHVlMjU1O30KXHBhcmRcdHg1NjBc
dHgxMTIwXHR4MTY4MFx0eDIyNDBcdHgyODAwXHR4MzM2MFx0eDM5MjBcdHg0NDgw
XHR4NTA0MFx0eDU2MDBcdHg2MTYwXHR4NjcyMFxxbFxxbmF0dXJhbFxwYXJkaXJu
YXR1cmFsCgpcZjBcZnMyNCBcY2YwIFx1bCBcdWxjMCBCTEFILn0=
</string>
        <key>title</key>
        <string>Welcome to Notepaddy!</string>
        <key>uuid</key>
        <string>5yvghz9n4ukgefnbx0qa2xne3nxeebcmcvpci9j5lwpncul1asftdayjv8a</string>
    </dict>
    <key>text</key>
    <string>e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxMDM4XGNvY29hc3VicnRm
MzYwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEhlbHZldGljYTt9Cntc
Y29sb3J0Ymw7XHJlZDI1NVxncmVlbjI1NVxibHVlMjU1O30KXHBhcmRcdHg1NjBc
dHgxMTIwXHR4MTY4MFx0eDIyNDBcdHgyODAwXHR4MzM2MFx0eDM5MjBcdHg0NDgw
XHR4NTA0MFx0eDU2MDBcdHg2MTYwXHR4NjcyMFxxbFxxbmF0dXJhbFxwYXJkaXJu
YXR1cmFsCgpcZjBcZnMyNCBcY2YwIFx1bCBcdWxjMCBCTEFILn0=
</string>
    <key>title</key>
    <string>Welcome to Notepaddy!</string>
    <key>uuid</key>
    <string>5yvghz9n4ukgefnbx0qa2xne3nxeebcmcvpci9j5lwpncul1asftdayjv8a</string>
</dict>
</plist>

Я получаю тот же текст обратно, но весь </plist> отсутствует и </dict> обрезается. Расшифровка и вывод результирующей строки дает мне полный мусор при шифровании парольной фразой «0987654321123456» или любой другой парольной фразой или такой же, как указано выше, при копировании в поле пароля.

Ответы [ 4 ]

5 голосов
/ 29 июня 2011

Обе версии имеют одну и ту же проблему: вы приказываете 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);
}
1 голос
/ 29 июня 2011

Относительно вашего * де * шифровального кода

kCCParamError, как следует из названия, является ошибкой. Почему вы относитесь к этому как к успеху? Если вы получаете эту ошибку, значит, вы сделали что-то не так; посмотрите на параметры, которые вы передали, и выясните, что.

Возможно, именно поэтому вы получаете «мусор»: CCCrypt (расшифровка) на самом деле никогда ничего вам не дала, потому что он не мог работать с любыми значениями, которые вы ему дали. То, что вы получаете, - это то, что лежало в буфере вывода при выделении.

Если вы переключитесь на calloc или на создание объекта NSMutableData перед вызовом CCCrypt и использованием его mutableBytes в качестве буфера, я думаю, вы обнаружите, что буфер тогда всегда содержит все нули. По той же причине: CCCrypt не заполняет его, потому что он терпит неудачу, потому что вы передали одно или несколько неправильных значений (ошибка параметра).

Вам нужно исправить ошибку параметра, прежде чем можно ожидать, что это сработает.

Вы можете попытаться разбить вызов CCCrypt на вызовы CCCryptorCreate, CCCryptorUpdate, CCCryptorFinal и CCCryptorRelease, по крайней мере, временно, чтобы увидеть, где происходит ошибка.

Шифрование: та же проблема или вообще не проблема

Ваш метод шифрования возвращает YES или NO? Я предполагаю, что он возвращает NO, потому что код, по-видимому, в основном одинаков между методами шифрования и дешифрования, поэтому все, что вы ошиблись в своем коде расшифровки, вероятно, также неверно в вашем коде шифрования. Посмотрите, что CCCrypt возвращается и, если это не удастся, заставьте его работать.

Если он возвращает YES (CCCrypt добивается успеха), то мне интересно, что вы подразумеваете под «возвращает мне кучу мусора». Вы ссылаетесь на содержимое объекта данных, на который отправлено сообщение encryptWithAES128Key:?

Если это так, то это ожидаемый результат. Ваш код шифрует содержимое объекта данных на месте, перезаписывая открытый текст зашифрованным текстом. То, что вы видите, не является чистым «мусором» - это зашифрованный текст! Расшифровка (успешно) снова откроет открытый текст.

Кстати, у вас есть «шифрование на месте, поскольку это изменяемый объект данных», комментарий о создании выходного буфера, чтобы не работал на месте в коде дешифрования , Это должно быть в методе шифрования, где вы работаете на месте. Я предлагаю заставить либо работать на месте, либо не работать на месте; последовательность - добродетель.

0 голосов
/ 22 февраля 2013

Вы должны использовать RNCryptor , это высокоуровневый API-интерфейс с открытым исходным кодом для шифрования в CommonCrypto, и высокоуровневые API-интерфейсы шифрования являются наилучшей практикой для криптографии в наши дни, потому что для экспертов легко допустить ошибки в реализациях с использованием криптографии приматов, и есть много побочных атак, которые используют эти ошибки.

Например, ваш код говорит: /* initialization vector (optional) * / 100% не соответствует действительности, поэтому вы полностью искалечили AES-CBC, и это только самая очевидная проблема.

В вашем случае RNCryptor идеален, я настоятельно рекомендую вам не использовать собственную реализацию.

0 голосов
/ 22 февраля 2013

Если в вашем коде есть следующие изменения заполнения, удалите его и всегда оставляйте kCCOptionPKCS7Padding включенным, это должно решить вашу проблему.

if (encryptOrDecrypt == kCCEncrypt) {
    if (*pkcs7 != kCCOptionECBMode) {
        if ((plainTextBufferSize % kChosenCipherBlockSize) == 0) {
            *pkcs7 = 0x0000;
        } else {
            *pkcs7 = kCCOptionPKCS7Padding;
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...