Как стать автором
Обновить

Комментарии 28

Самое важное, что в этой статье упущено, это то, что криптографию никогда не нужно писать самому в прикладных программах

Обучение или библиотеки, реализующие криптографию - это другое дело, конечно, а во всех остальных случаях надо себя по рукам бить, когда они тянутся писать свои реализации

НЛО прилетело и опубликовало эту надпись здесь

если она не используется в коммерческих или государственных продуктах

То есть, во всех продуктах, которыми кто-то пользуется.

Если коротко, то "маленькие" баги/допущения/пр. могут пройти незаметно или иметь ничтожные последствия, если это прикладной код. Но в криптоалгоритмах любые "маленькие" проблемы практически гарантированно приводят к упрощению эксплуатации уязвимостей. Понижение энтропии, replay атаки и т.д.

Вы же наверняка не думаете, что библиотеку openssl пишут глупые программисты? Так вот, heartbleed - это следствие такой "маленькой" ошибки. Но если в библиотеке, которую используют все и везде, существует эта ошибка, не думаете ли вы, что ворвётесь с ноги в криптоалгоритмы с безупречным кодом, лишённым недостатков?

Но постойте, скажете вы, heartbleed - это не про плохую реализацию алгоритма. Правильно, но алгоритмы ради алгоритмов никому не нужны кроме обучения, а про это см. выше, и ваш код обязательно будет иметь как минимум прослойку для использования где-то ещё.

И даже если кроме алгоритма ничего нет, то несовершенства в самом алгоритме могут сделать его бесполезным и, следовательно, всё что им было зашифровано, тоже.

Но даже если и алгоритм безупречен математически, может оказаться что это абсолютно не важно, т.к. есть side-channel attacks.

НЛО прилетело и опубликовало эту надпись здесь

С таким подходом самому вообще ничего писать нельзя. А вдруг ошибёшься?

Non sequitur

Лично вам никто ничего не может запретить. Вы спросили "почему", я ответил. Хотите еще глубже понять, гуглите "rolling your own crypto"

НЛО прилетело и опубликовало эту надпись здесь

Мне кажется тут стоит внести ясность. Реализовать свою криптографическую библиотеку можно, но желательно, чтобы были следующие вещи:

  1. Причина. Например, это какой-то специфический язык, на котором еще не написаны нужные вам алгоритмы. Brainfuck если хотите.

  2. Основательные знания в криптографии. Вы понимаете причину использования каждой функции, что такое коллизии, что такое прообраз, как их найти и противодействовать этому.

  3. Основательные знания в программировании. Вы понимаете как ваша программа утилизирует память, где могут быть опасные места.

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

Разумеется желательно, чтобы был квалифицированный человек, который сможет провести аудит. Проверка в этой области чрезвычайно важна.

НЛО прилетело и опубликовало эту надпись здесь

Математики разные бывают, как и программисты

Программист, который ничего не знает о безопасности криптографии тоже практически наверняка понятия не имеет о временных атаках

Как и математики не живут в пещерах, иногда выдавая наружу манускрипты. Это такие же люди, у них интернет есть, они читают статьи, посвященные алгоритмам и уязвимостям, с ними связанными, в конце концов

НЛО прилетело и опубликовало эту надпись здесь

Почему то вспомнилось:

"Ноев ковчег" сделал любитель, а "Титаник" построили профессионалы.

Впрочем, отчасти вы несомненно правы.

Справдливости ради, у любителя был прораб от бога...

Строили профессионалы, а смету на материалы составляли менеджеры.

На резервные шлюпки забили, капитан вообще тусить свалил, а предупреждения погасили.

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

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

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

Используя команду echo "hello world" | sha256sum вы отправляете в утилиту строку "hello world\n" , то есть с переносом строки.

Добавляйте ключик -n чтобы это не происходило:

$ echo -n "" | wc -c

0

$ echo -n "" | sha256sum

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -

Благодарю за совет ?

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

Видимо именно поэтому по всему коду килограммы копипасты из магических значений. Вы хоть бы констант наделали.

.map(|byte| format!("{:08x}", byte))

А тут ещё и имя неудачное ибо вы не по байтам итерируетесь, а по u32/u64.

А вообще вам бы почитать маны к используемым вами API. Код был бы поприятнее.

Спасибо за ваш комментарий! На самом деле я наделся, что кто-то более компетентный оставит пару замечаний к моему коду. Не могли бы вы оставить немного более подробное замечание - я хотел бы разобраться в очевидных глупостях.

Про название переменной в .map(|byte| format!("{:08x}", byte)) вы ловко подметили. Исправлю.

if 128 - m.len() % 128 < 8
...
message.len() as u64 * 8)

Первая восьмёрка 8 байт aka u64 , которые заполняются второй восьмёркой про 8 бит в байте. Неплохо было бы их сделать отдельными константами с разными именами.

m.append(&mut (message.len() as u64 * 8).to_be_bytes().to_vec());

Такие штуки кажется сложнее читать. У Vec есть метод extend который принимает итератор на вход, благодаря чему код превращается в что-то чуть более читаемое

m.extend((message.len() * 8).to_be_bytes().into_iter());
  • u64 и usize одного размера и кажется явное приведение типа не слишком нужно

  • to_be_bytes вернёт массив, который проще следом поглотить, нежели тратиться на аллокации из-за .to_vec(). Потенциально аллокации оптимизирует на этапе компиляции, но это не гарантировано. Поэтому отдаём итератор.

  • аналогично стоит отдавать итераторы в прочих местах. repeat(0x0).take(штук). Штуки считаем перед тем как что-то добавлять в массив. Даже если оставить append.

u32::from_be_bytes

Думал можно сделать как-то превращение байт в число красиво используя функцию read_be_u32 из документации. Но после некоторых изысканий понял, что овчинка выделки не стоит ради пары мест. А std::mem::transmute обозначен unsafe.

Выравнивание по границе 64 и 80 слов как минимум добавляет вопросов к магическим числам. Опять же, чтобы не возиться с append, можно просто сделать

use std::iter::repeat;
let w = block
  .chunk_exact(/* size_of::<T> */)
  .map(|chunk| /*from_be_bytes*/)
  // в соответствии с вашим кодом
  // sha256 - WORDS = 64, sha512 - WORDS = 80
  // PADDING = 16 для обоих
  .chain(repeat(0).take(WORDS - PADDING)) 
  .collect();

По идее такой код будет чуть оптимальнее за счёт отсутствия необходимости аллокаций и реаллокаций вектора.

По идее можно tmp_h вынести в кучку маленьких переменных чтобы код был немного компактнее, но тут главное не запутаться кто есть кто.

let [a,b,c,d,e,f,g,h] = tmp_h[0..8] else {unreachable!()};
let s1 = e.rotate_left(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
let ch = (e & f) ^ (!e & g);
... /* the rest*/

tmp_h[7] = g;
tmp_h[6] = f;
tmp_h[5] = e;
tmp_h[4] = d.wrapping_add(temp1);
tmp_h[3] = c;
tmp_h[2] = b;
tmp_h[1] = a;
tmp_h[0] = temp1.wrapping_add(temp2);

Все рекомендации по рефакторингу моё ИМХО и не являются обязательным. Обязательным стоило бы сделать наличие тестов.

Большое спасибо за ваш подробный комментарий! Я постараюсь учесть все замечания, чтобы внести правки в этот пост и в следующий по Стрибог. Опыта в Rust мне пока не достает, но такие комментарии как ваш, помогают разобраться в некоторых местах. Спасибо!

Забыл добавить про тесты. Они есть в репозитории, здесь их приводить не стал. Поскольку я реализовал по сути всего две функции: sha256 и sha512, то тестирование выполняется только для них.

Тестирование выполнено максимально просто: с помощью утилит из coreutils я посчитал эталонный хэш для текстов и для видео и просто сравниваю с результатом моей функции.

Упоминание в комментарии выше про утечку времени я так и не понял, однако в этом сценарии использования сравнение хэшей работает.

А, я смотрел репу и видел тесты, но забыл стереть комменатрий про них.

Вы сильно теряете в перформансе на операции vec![0u8; 128 - m.len() % 128])

vec! выделяет массив в куче, но у вас довольно небольшое количество элементов, не более 128 байт, так что можно использовать small vector optimization - сделать массив [0u8;128] (аллокация на стеке) и от него уже взять слайс в 128 - m.len() % 128 элементов(можно взять готовый крейт smallvec если не хотите писать сами). Разница между аллокацией на стеке и в куче примерно в 15 раз, например на моей машине это 30ns vs 2.2ns. Для хэш-функции это может быть значимо. Есть, конечно, шанс что LLVM сделает это за вас, но я бы не закладывался.

let mut m = message.to_vec();

Перед эти надо проверить, вдруг выравнивание не надо делать - тогда у вас лишние копирование. Но даже если надо, то лучше использовать Vec::with_capacity и extend_from_slice чтобы избежать лишних копирований и переаллокаций на следующих шагах - нужный размер вектора вы с самого начала знаете.

Спасибо за подробный комментарий и разъяснения. Внесу изменения в код, а потом и в статью.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории