Шифрование NSMutableData с использованием NSInputStream

Я пытаюсь использовать CommonCrypto для шифрования объекта NSMutableData на месте (копируя полученные байты себе без дублирования). Раньше я использовал «однократный» метод CCCrypt(), главным образом потому, что он казался простым. Я заметил, что мой объект данных дублируется в памяти. Чтобы избежать этого, я попытался использовать объект NSInputStream с размером буфера 2048 байт. Я читаю свой объект NSMutableData и постоянно вызываю CCCryptorUpdate() для обработки шифрования. Проблема в том, что он все еще кажется дублированным. Вот мой текущий код (обратите внимание, что это категория в NSMutableData — в основном по историческим причинам — таким образом, ссылки на «я»):

- (BOOL)encryptWithKey:(NSString *)key
{
    // Key creation - not relevant to the dercribed problem
    char * keyPtr = calloc(1, kCCKeySizeAES256+1); 
    [key getCString: keyPtr maxLength: sizeof(keyPtr) encoding: NSUTF8StringEncoding];

    // Create cryptographic context for encryption
    CCCryptorRef cryptor;
    CCCryptorStatus status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionECBMode, keyPtr, kCCKeySizeAES256, NULL, &cryptor);
    if (status != kCCSuccess)
    {
        MCLog(@"Failed to create a cryptographic context (%d CCCryptorStatus status).", status);
    }

    // Initialize the input stream
    NSInputStream *inStream = [[NSInputStream alloc] initWithData:self];
    [inStream open];
    NSInteger result;
    // BUFFER_LEN is a define 2048
    uint8_t buffer[BUFFER_LEN];
    size_t bytesWritten;

    while ([inStream hasBytesAvailable])
    {
        result = [inStream read:buffer maxLength:BUFFER_LEN];
        if (result > 0)
        {
            // Encryption goes here
            status = CCCryptorUpdate(
                                     cryptor,               // Previously created cryptographic context
                                     &result,               // Input data
                                     BUFFER_LEN,            // Length of the input data
                                     [self mutableBytes],   // Result is written here
                                     [self length],         // Size of result
                                     &bytesWritten          // Number of bytes written
                                     );

            if (status != kCCSuccess)
            {
                MCLog(@"Error during data encryption (%d CCCryptorStatus status)", status);
            }
        }
        else
        {
            // Error
        }
    }

    // Cleanup
    [inStream close];
    CCCryptorRelease(cryptor);
    free(keyPtr);
    return ( status == kCCSuccess );
}

Я определенно упускаю здесь что-то очевидное, шифрование и даже использование входных потоков для меня немного в новинку.


person József Vesza    schedule 14.05.2014    source источник
comment
В чем проблема, вызванная входными и выходными буферами?   -  person zaph    schedule 14.05.2014
comment
Проблема в том, что есть большие документы, которые нужно шифровать, а я не хочу, чтобы они дублировались в памяти.   -  person József Vesza    schedule 15.05.2014
comment
Так что на самом деле это не проблема, просто временное использование памяти. Вот для чего нужна память — для того, чтобы ею пользоваться. Это называется предварительной оптимизацией, то есть оптимизацией до определения наличия проблемы. Почти всегда это зря потраченное время.   -  person zaph    schedule 15.05.2014
comment
Хотя 60 МБ памяти, вероятно, не так уж и важны, она достаточно велика и достаточно интересна, чтобы стать полезным опытом обучения. Нет проблем с изучением этого, особенно в качестве оптимизации уже работающего кода. Помните, что временные всплески памяти могут привести к ненужным предупреждениям о памяти в других приложениях, заставляя их сбрасывать кэши или, возможно, даже уничтожать их, что ухудшает общее впечатление пользователя. Стоит попытаться уменьшить это (когда у вас есть работающее решение и без чрезмерной сложности).   -  person Rob Napier    schedule 19.05.2014


Ответы (2)


Пока вы вызываете CCUpdate() только один раз, вы можете шифровать в тот же буфер, из которого вы читаете, без использования потока. Для примера см. RNCryptManager.m. Исследование applyOperation:fromStream:toStream:password:error:. Я использовал здесь потоки, но это не обязательно, если у вас уже есть NSData.

Однако вы должны убедиться, что CCUpdate() вызывается только один раз. Если вы вызовете его несколько раз, он повредит свой собственный буфер. Это открытая ошибка в CommonCryptor (radar://9930555).

Примечание: генерация вашего ключа чрезвычайно ненадежна, и использование режима ECB для такого рода данных вряд ли можно считать шифрованием. Он оставляет шаблоны в зашифрованном тексте, которые можно использовать для расшифровки данных, в некоторых случаях просто посмотрев на него. Я не рекомендую этот подход, если вы действительно собираетесь защитить эти данные. Если вы хотите изучить, как правильно использовать эти инструменты, см. раздел Правильное шифрование с помощью AES с CommonCrypto. Если вам нужно готовое решение, см. RNCryptor. (Однако в настоящее время у RNCryptor нет удобного метода шифрования на месте.)

person Rob Napier    schedule 14.05.2014
comment
Проблема в том, что документ большой. Я хотел бы иметь меньше данных в памяти, если это возможно, поэтому я решил использовать поток. (Спасибо за ссылки, я обязательно изучу их). - person József Vesza; 15.05.2014
comment
Хотя преобразование на месте, безусловно, является допустимым вариантом использования, часто бывает удобнее просто расшифровать или зашифровать данные при передаче. Посмотрите на асинхронный интерфейс RNCryptor для примера. Например, он может легко расшифровать данные, когда они загружаются через NSURLConnection. С небольшим количеством дополнительного кода он может расшифровать, пока вы читаете поток, или зашифровать, пока вы его записываете, поэтому вам никогда не понадобится зашифрованная версия в памяти. - person Rob Napier; 15.05.2014
comment
Это совершенно правильное наблюдение. К сожалению, я не в состоянии изменить существующие (даже плохие) решения, поэтому шифрование на месте будет моим единственным вариантом. Я также пытался решить эту проблему без буфера (либо с помощью одноразового CCCrypt(), либо с помощью обычного процесса с CCCreate() и CCCUpdate(), мне не удалось решить эту проблему. - person József Vesza; 15.05.2014
comment
Вы пытались сделать один вызов CCUpdate с указателями зашифрованного и открытого текста, обращающимися к одному и тому же буферу? Это должно работать, пока вы вызываете его только один раз. Вы можете попробовать отключить заполнение (это не обязательно для ECB). Это была еще одна часть ошибки CCUpdater. - person Rob Napier; 15.05.2014
comment
Я попробовал это, но, к сожалению, это не решило проблему. - person József Vesza; 19.05.2014
comment
Просто чтобы перепроверить настоящую проблему: какой симптом вы видите? У вас коррупция? Ошибка? Какие? Вы замечаете дублирование в памяти, но откуда вы это знаете и какую проблему это вызывает? Вы работаете, получая предупреждения о памяти? - person Rob Napier; 19.05.2014
comment
Нет, само шифрование работает хорошо, но у меня в памяти данные дважды (например: у меня файл 60 мб, а по окончании шифрования временно у меня занято 120 мб). Я не хочу дублировать использование памяти. - person József Vesza; 19.05.2014
comment
как вы определяете, что у вас занято 120 МБ? И какова природа 120MBs? Это грязно? Какие объекты в нем и где они расположены? Ваша фактическая отметка высокого уровня растет? У вас есть трассировка инструментов? Общеизвестно, что определение фактического использования памяти в современных системах управления памятью является сложной задачей. ОС использует множество трюков. - person Rob Napier; 19.05.2014
comment
Я пытался отслеживать использование памяти как в Xcode, так и в Instruments (хотя я немного новичок в последнем), и я вижу внезапный всплеск использования памяти в конце моей функции шифрования. Использование памяти увеличивается в зависимости от размера документа, который я пытаюсь зашифровать. - person József Vesza; 19.05.2014
comment
bbum представляет собой хорошее введение в анализ HeapShot в инструментах: friday.com/bbum/2010/10/17/. Начните с этого. Инструменты подскажут вам, где происходит распределение. Если он вдруг подпрыгнет в конце, вполне возможно, что он не там, где вы думаете. Например, вы можете случайно скопировать NSData. Такого рода работа по оптимизации требует большого количества доказательств; Будьте осторожны, предполагая заранее, что вы знаете, что происходит. - person Rob Napier; 19.05.2014
comment
Я проверил анализ кучи, к сожалению, он не дал окончательных результатов, так как этот значительный скачок не отображается в распределениях (его можно увидеть на графике). - person József Vesza; 19.05.2014

В соответствии:

result = [inStream read:buffer maxLength:BUFFER_LEN];

данные считываются в buffer, а result устанавливается на результат выполнения. в строках:

status = CCCryptorUpdate(cryptor, &result, ...

Вы должны использовать buffer для входных данных, а не статус

status = CCCryptorUpdate(cryptor, buffer, ...

Использование лучших имен помогло бы устранить простую ошибку. Если бы вместо result переменная была названа readStatus, то ошибки, скорее всего, не было бы. Точно так же вместо того, чтобы называть переменную данных buffer, она была названа streamData, что также было бы более понятным. Плохое название действительно может привести к ошибкам.

person zaph    schedule 14.05.2014
comment
В этом вы определенно правы, однако это не было причиной дублирования. - person József Vesza; 15.05.2014
comment
Я понимаю и вижу Роба по поводу CCUpdate(). - person zaph; 15.05.2014