AES CBC шифрование потоков в рубине? - PullRequest
4 голосов
/ 05 февраля 2011

Я использовал довольно стандартный пример (который сильно сломан для моих целей) шифрования cbc в ruby:

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') в конце строки шифровался.В конце концов, каждый вызов update возвращает строку длиной ((string.length / 16) + 1) * 16 байт, что подразумевает шифрование дополнительного байта с каждым обновлением.

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

РЕДАКТИРОВАТЬ:

Эта проблема не зависит от кодировки base64.Следующее дает 3 разных результата зашифрованного текста:

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)

Ответы [ 2 ]

3 голосов
/ 10 февраля 2011

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

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

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

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

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

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

Приведенный выше абзац должен быть понятнее, если вы знаете, как работает CBC, Википедия имеет хорошие схемы на этом.

2 голосов
/ 10 февраля 2011

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

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"
...