2011-02-05 3 views
4

Я использую довольно стандартный пример (один, который сломан для моих целей) из CBC шифрования рубина:AES CBC шифрование потоков в рубине?

def aes(m,k,t) 
    (aes = OpenSSL::Cipher::Cipher.new('aes-256-cbc').send(m)).key = Digest::SHA256.digest(k) 
    aes.update(t) << aes.final 
end 

def encrypt(key, text) 
    aes(:encrypt, key, text) 
end 

def decrypt(key, text) 
    aes(:decrypt, key, text) 
end 

Это работает в качестве приемлемой отправной точки, но мне нужно, чтобы иметь возможность шифровать большие потоки данных, не загружая их в один огромный кусок памяти. Я хочу загрузить мега-за раз, обновить состояние потока шифрования, а затем перейти к следующему блоку. Глядя на документы на OpenSSL Cipher (которые отмечены наградами), я ожидаю, что призыв к обновлению должен просто продолжать поток данных. Тем не менее, простой тест говорит мне, что есть что-то очень неправильно:

Length = 256 
newaes = OpenSSL::Cipher::Cipher.new('aes-256-cbc') 
newaes.encrypt 
newaes.key= Digest::SHA256.digest("foo") 
puts Base64.encode64(newaes.update("a"*Length)) 
puts Base64.encode64(newaes.update("a"*Length)) 
puts Base64.encode64(newaes.final) 

Запуск этого с различными значениями длины не должны давать мне разные потоки. Однако после завершения первого обновления всегда есть проблема. Потоки расходятся. Я предполагал, что проблема заключается в том, что по какой-то необъяснимой причине завершающий символ нулевой ('\ 0') в конце строки был зашифрован. В конце концов, каждый вызов для обновления возвращает строку, которая ((string.length/16) + 1) * 16 байт, подразумевая, что она шифрует дополнительный байт при каждом обновлении.

Как получить шифрование и дешифрование OpenSSL для работы в режиме, в котором я могу проходить в блоках данных и получать тот же результат обратно, независимо от размера фрагментов, в которые я разбиваю данные?

EDIT:

Вопрос не зависит от base64 кодирования. Ниже приводятся 3 различных результата cyphertext:

require 'digest/sha2' 
require 'base64' 
require 'openssl' 

def base64(data) 
    Base64.encode64(data).chomp 
end 

def crypt_test(blocksize) 
    newaes = OpenSSL::Cipher::Cipher.new('aes-256-cbc') 
    newaes.encrypt 
    newaes.key= Digest::SHA256.digest("foo") 
    plaintext = "" 
    cyphertext = "" 
    File.open("black_bar.jpg") do |fd| 
     while not fd.eof 
      data = fd.read(blocksize) 
      cyphertext += data 
      cyphertext += newaes.update(data) 
     end 
    end 
    cyphertext += newaes.final 
    puts base64(Digest::SHA256.digest(plaintext)) 
    puts base64(Digest::SHA256.digest(cyphertext)) 
    puts 
end 

crypt_test(1024) 
crypt_test(512) 
crypt_test(2048) 
+0

Я считаю, что то, что вы видите, на самом деле является IV для шифрования CBC. Вы можете проверить это, сделав длину '255' вместо' 256'. Если он действительно шифрует '' \ 0'' байт в конце, то каждый из них должен иметь длину всего 256 байтов. – Omnifarious

+0

Я сделал то, что предложил, и доказал, что ошибаюсь. – Omnifarious

+0

Кстати, я вообще не программирую в Ruby. Было бы полезно, если бы вы ввели инструкции 'require 'openssl.rb'' и' require' base64.rb''. :-) – Omnifarious

ответ

3

У меня о нулевом знании на Ruby. Однако ваша проблема выглядит как проблема с дополнением.

AES/CBC шифрует данные блоками по 16 байт, не менее. Перетяжка о добавлении нескольких байт, что:

  1. то дополненная длина кратна 16;
  2. после дешифрования дополнительные байты могут быть однозначно удалены.

Второе условие означает, что не может быть «отступов нулевой длины» (по крайней мере, не прибегая к темным трюкам, таким как «шифрование зашифрованного текста»). Должен быть хотя бы один дополнительный байт заполнения. В противном случае дешифратор не знал бы, действительно ли конец полученных данных является некоторым дополнением или фактическим сообщением, которое заканчивается в некоторых байтах, которые выглядят как «заполнение».

схема очень распространена обивка является одним указано в PKCS#5 (смотри раздел 6.1.1): для блоков длины п (п = 16 для AES), по меньшей мере, 1 и в большинстве п байтов добавлены; если добавлены k байт, то все они имеют числовое значение k. После дешифрования нужно просто посмотреть числовое значение последнего байта, чтобы узнать, сколько добавленных байтов было добавлено. Схема заполнения PKCS # 5 подразумевает поведение, которое вы наблюдаете: шифрование m байт производит n * ((m/n) +1) выходные байты.

Если ваши вызовы действительно добавляют дополнение PKCS # 5 к каждому update, вы можете восстановить его, удалив последние 16 байт из того, что они возвращают. Вам также придется сбросить IV для следующего вызова update, так что следующий вызов update может быть просто добавлен. Говоря об этом, я ничего не вижу в вашем коде о IV, и это подозрительно. Режим CBC требует нового случайного IV (выбранного с генератором «достаточно сильный») для каждого сообщения; Затем IV должен передаваться вместе с зашифрованным сообщением (кто-то, кто расшифровывает данные, будет нуждаться в нем, IV может быть отправлен «в ясности»).

Приведенный выше параграф должен быть более четким, если вы знаете, как работает CBC. Wikipedia имеет хорошие схемы.

2

Вот ваша проблема:

Length = 256 
newaes = OpenSSL::Cipher::Cipher.new('aes-256-cbc') 
newaes.encrypt 
newaes.key= Digest::SHA256.digest("foo") 
s1 = newaes.update("a"*Length) 
s2 = newaes.update("a"*Length) 
s3 = newaes.final 
puts Base64.encode64(s1 + s2 + s3) 

Это теперь будет выводить точно такой же base64, как если бы вы хлюпать два обновления в один.

Вы используете проблему выравнивания с кодировкой base64. Кодировка Base64 занимает 3 байта за раз и преобразует их в 4 байта. Если вы дадите ему количество байтов, которое не кратно 3, оно прокладывает вещи с помощью знаков «=».

Это означает, что если у вас есть два последовательных прогона кодирования, которые не кратно 3 байтам, а затем закодировать ту же самую последовательность байтов всего за один прогон кодирования, вы получите другой выход base64. Второй прогон кодирования не выравнивается так же, как если бы эти данные были частью первого запуска кодирования. Вот несколько примеров:

Здесь данные кратные 3. В двух прогонах кодировщика производятся последовательности base64, которые могут быть объединены вместе для получения более или менее той же последовательности, что и один запуск кодировщика по объединенному строки.

> Base64.encode64('abc') 
=> "YWJj\n" 
> Base64.encode64('def') 
=> "ZGVm\n" 
> Base64.encode64('abcdef') 
=> "YWJjZGVm\n" 

Здесь данные разделены на последовательность из 4 байт, и 4 не кратен 3. конкатенация двух пробегов датчика не то же самое, как кодирование двух строк сцепленных.

> Base64.encode64('abcd') 
=> "YWJjZA==\n" 
> Base64.encode64('efgh') 
=> "ZWZnaA==\n" 
> Base64.encode64('abcdefgh') 
=> "YWJjZGVmZ2g=\n" 
+0

(+1 для определения базы64 выпуск.) – Sniggerfardimungus

+0

@ user30997 - спасибо. :-) Но у вашей тестовой программы выше есть проблема. Думаю, после того, как вы это исправите, вы обнаружите, что основная вещь - это единственная проблема, которая у вас есть. :-) – Omnifarious

Смежные вопросы