LINUX.ORG.RU

Криптографический хэш огромного массива/btree с быстрой вставкой в середину

 btree, ,


0

1

Есть, предположительно, огромный массив (или что-то более структурированное, например btree), из однотипных элементов (но могущих быть разного размера в байтах). У этого списка надо организовать криптографическую проверку целостности с помощью хэша, но с дополнительным условием: при вставке/удалении/замене элемента откуда-то из середины надо чтобы не требовалось заново пробегать по каждому из элементов и пересчитывать его.

(этот абзац для задачи не важен, просто информация для интереса) Использоваться будет для хранения огромных директорий в формате btree в системе контроля версий, чтобы с одной стороны хэш не зависел от того, как распределены ноды по дереву, а с другой - чтобы при замене одного файла не требовалось подгружать все ноды с диска и считать заново хэш по ним.

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

Предлагаемая схема. Берём некий уже готовый традиционный криптохэш, считающийся надёжным по условию извне задачи. Этим хэшом хэшируем каждый элемент списка отдельно. Итоговый хэш списка считаем как степенной ряд с хэшами элементов в качестве коэфициентов:

hash(item[0..N-1]) = hash(item[0]) + hash(item[1]) * k + hash(item[2]) * pow(k,2) + ... + hash(item[N-1]) * pow(k,N-1)

где k - некое большое простое число, разрядностью равной или чуть меньше разрядности базовой хэш-функции, N - количество элементов списка, item[] - массив элементов, pow() - функция возведения в степень большой разрядности, hash() - базовая хэш-функция. Вся арифметика считается по модулю разрядности хэша, разумеется.

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

И ещё вопрос: если базовая хэш-функция уже обладает какими-то недостатками, но ещё не проявляющимися на практике (т.е. отдалённо-теоретическими), может ли её использование в указанной выше схеме обострить значимость этих недостатков и сделать их практически эксплуатируемыми?

И ещё вопрос: а если k имеет заметно меньшую разрядность чем базовая хэш-функция, это чему-то навредит?

★★★★★

Последнее исправление: firkax (всего исправлений: 3)

У этого списка надо организовать криптографическую проверку целостности с помощью хэша

Я программист, а не криптозоолог, так что не уверен, что под этим подразумевается. Правильно ли я понимаю, что там у каждого элемента структуры есть свего рода контрольное значение, которое отвечает только за этот элемент? Или оно общее на всё дерево? Или это для элемента и его соседей?

Потенциальные решения сильно зависят от этого, очевидно.

Bfgeshka ★★★★★
()
Ответ на: комментарий от LLM-9000

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

hash(hash(0,1,2),hash(3,4)) != hash(hash(0,1),hash(2,3,4))

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

Хотя в криптографическом плане, конечно, merkle tree вопросов не вызывает и сходу одобряется.

firkax ★★★★★
() автор топика
Последнее исправление: firkax (всего исправлений: 2)
Ответ на: комментарий от Bfgeshka

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

firkax ★★★★★
() автор топика

И ещё вопрос: а если k имеет заметно меньшую разрядность чем базовая хэш-функция, это чему-то навредит?

Чётные простые числа - это мощный нажористый кудяблик. Например, 256-битный хеш, k = 2, N > 256; наглядно показывает, что нужно считать статистическое расстояние.

ratvier ★★
()
Ответ на: комментарий от ratvier

То что чётные числа не подходят это очевидно. Вообще под заметно меньшей разрядностью имелось ввиду не 1-2 а например 32-64 просто чтобы влезало в целый тип и не тратило зря ресурсы. Причём тут статистическое расстояние непонятно.

firkax ★★★★★
() автор топика
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

Причём тут статистическое расстояние непонятно.

При том, что hash(x) даёт равномерное распределение вероятностей.
А вот hash(x) * pow(k, n) уже нет.
И тут надо бы посчитать, а существуют ли такие k, для которых это распределение будет статистически близким к равномерному.

ratvier ★★
()
Ответ на: комментарий от ratvier

Почему это нет? Если k нечётное (по-моему, даже не обязательно простое) то всё должно равномерным оставаться: имеем сложение оригинального hash(x) с какой-то производной от него функцией, сдвинутой как минимум на 1 бит влево. Т.к. биты криптографического хэша друг с другом по определению не коррелируют, то слагаемое в виде сдвинутого на 1 бит влево хэша (+любых манипуляций над ним кроме сдвига вправо или деления) уже никак равномерность распределения исходного hash(x) испортить не может.

Разумеется, надо оговорить что разрядность хэша какая была такая и остаётся, и лишние старшие биты от умножения мы выкидываем. Если их не выкинуть - да, с их учётом будет какое-то неравномерное распределение, но оно и понятно - у нас только 256/512/1024 бит энтропии было, умножением мы её не увеличим.

Хм, думаю это и ответ на один из моих вопросов. Поскольку условие - отсутствие корреляции между битами, то если её действительно нет ни в каком виде и hash(x) идеально в этом плане - можно взять хоть k=3. Если же уверенности нет, то лучше взять что-то побольше для лучшего перемешивания битов между собой при каждом умножении.

firkax ★★★★★
() автор топика
Последнее исправление: firkax (всего исправлений: 1)

Так у тебя там дерево или таки массив?

Для дерева (если ты его не ребалансишь) чем не устраивает классический подход где хеш узла вычисляется от хешей связанных узлов? Тогда при апдейте одного узла придется пересчитать хеши только по ветке вниз до корня.

ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 2)
Ответ на: комментарий от ratvier

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

Рассматривать корреляции битов по-моему продуктивнее, и имеется ощущение что плохие (маленькие?) k могут их усугубить если они есть в базовой хэш-функции (а если их не было - то не появятся).

firkax ★★★★★
() автор топика
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от realloc

Там merkle tree внутри (и его можно с любым хэшом сделать), я уже написал выше почему это не то.

firkax ★★★★★
() автор топика
Ответ на: комментарий от ya-betmen

Для дерева (если ты его не ребалансишь)

В этом и загвоздка. Хочется чтобы у (0,1),(2,3,4) и (0,1,2),(3,4) были одинаковые хэши. И дело даже не только в ребалансе. К одному и тому же состоянию можно прийти разными дорогами (добавлять элементы в разном порядке, удалять ранее добавленные итд), и хочется чтобы хэш в итоге зависел только от того что получилось, а не от того каким путём к этому пришли.

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

firkax ★★★★★
() автор топика
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

хочется чтобы хэш в итоге зависел только от того что получилось

Это выполняется.

чтобы у (0,1),(2,3,4) и (0,1,2),(3,4)

А вот тут я не понимаю почему если эти записи из бтри должны иметь одинаковый хеш? Может ты там другую структуру хочешь?

ya-betmen ★★★★★
()
Ответ на: комментарий от Psilocybe

Вместо сложения, или ты предлагаешь множители убрать? Множители нельзя убирать, тогда порядок элементов потеряется и можно дубликаты вставлять любые будет.

firkax ★★★★★
() автор топика
Ответ на: комментарий от ya-betmen

Это выполняется.

нет не выполняется.

А вот тут я не понимаю почему если эти записи из бтри должны иметь одинаковый хеш?

Потому что пофиг на btree, хэш от данных а не от структуры. То есть хэш от (0,1,2,3,4), а как оно на ноды дерева поделено влиять не должно. btree нужно для того чтобы упростить модификацию в середине и поиск, но на хэш оно влиять не должно. Хэш, логически, от массива а не от дерева.

Предлагаемая схема при этом очень хорошо ложится на btree-операции: каждой ноде можно присвоить свой частичный хэш (с той же формулой), и хэш ноды будет равен сумме хэшей нижележащих нод с множителями, зависящими от количества элементов в них.

firkax ★★★★★
() автор топика
Последнее исправление: firkax (всего исправлений: 1)

Да прост складывай хэши данных. Сложение коммутативно. То есть на результат не влияет в каком порядке ты дерево обходишь. Замена тоже тривиальна - вычел из суммы хэш удаленного элемента и добавил хэш добавленного элемента

cobold ★★★★★
()
Ответ на: комментарий от cobold

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

«Просто сложение» это её частный тривиальный случай с k=1, оно заведомо наследует любые проблемы, которые у неё могут быть + может добавить свои (кроме уже указанной потери порядка).

firkax ★★★★★
() автор топика
Ответ на: комментарий от cobold

Да прост складывай хэши данных.

Понадобится больше чем 256 или 512 бит для представления, кроме того это уже и не хэш будет, отсутстве коллизий под вопросом.

LLM-9000
()

Когда вставляешь лист, посчитай хэш сообщения, sha256(m1) и используй его как скаляр для вычисления точки на эллиптической кривой, например ed25519. H1 = G * sha256(m1).

Точка для вершины в дереве будет равна сумме точек её прямых потомков.

Т.ч. по итогу получается схема, которая

  • криптографически безопасна (не использует самостоятельно придуманные алгоритмы)
  • очевидно не зависит от внутренней структуры дерева, т.к. (1 + 2) + 3 == (1 + (2 + 3))
  • обрабатывает любые вставки и удаления за О(высоты дерева)

Только я не понял, важен ли в исходной задаче порядок листов в дереве. Если не важен, то эта схема подходит. Если важен, то надо улучшить. Но, думаю, с индексами что-нибудь можно придумать.

Итоговый хэш списка считаем как степенной ряд с хэшами элементов в качестве коэфициентов

полиномиальные хэши не считаются криптографически безопасными. Но если такого требования нет то в целом подходит, да.

Lrrr ★★★★★
()
Последнее исправление: Lrrr (всего исправлений: 2)
Ответ на: комментарий от cobold

Про отсутствие коллизий четко завернул

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

LLM-9000
()
Ответ на: комментарий от LLM-9000

не является значением хэш функции

Иии? Предложенный опом вариант так же им не является, а значит это не является значимым свойством алгоритма.

Что вы имеете ввиду

Это троллинг чтоль? Вы аргументируете наличием коллизий. Так они в любом случае будут. Просто в силу того что исходных данных сильно больше 512 бит, а это следует из описания задачи

cobold ★★★★★
()
Ответ на: комментарий от firkax

так сделай отдельно список и отдельно дерево со ссылками на этот список.

ckotctvo
()
Ответ на: комментарий от cobold

Так они в любом случае будут.

Верно. Точнее говоря, они теоретически возможны, ведь, к примеру, для sha256 на сегодняшний день неизвестно об обнаруженных коллизиях.

Вы аргументируете наличием коллизий.

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

LLM-9000
()
Ответ на: комментарий от LLM-9000

Верно. Точнее говоря, они теоретически возможны, ведь, к примеру, для sha256 на сегодняшний день неизвестно об обнаруженных коллизиях.

Они не просто «возможны», они гарантированно имеются, если длина данных в битах больше длины хэша (то есть почти всегда). Когда говорят про поиск коллизий - вопрос стоит не про их наличие, а именно про возможность целенаправленно их найти за обозримое время.

Имелось ввиду, что это неочевидно, насколько арифметические действия над значениями хэш-функции изменят вероятность коллизии.

Это да, тут требуется криптографическое исследование или просто мнение кого-то кто уже знает ответ. Но тут можно заявить два соображения: 1) если вместо сложения взять xor - то коллизии становится найти просто элементарно (добавляем пару одинаковых элементов, хэш остаётся как был); 2) cо сложением теоретически можно провернуть то же самое, если добавить 2^w одинаковых элементов, но если добавить даже просто два - разрядность изменения хэша станет на 1 бит меньше чем должна, скорее всего это тоже ослабление.

firkax ★★★★★
() автор топика
Ответ на: комментарий от Lrrr

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

firkax ★★★★★
() автор топика
Ответ на: комментарий от vbr

Нет, «просто xor» создаёт очевидные уязвимости, «просто сложение» - что-то похожее на них (выше уже писал два раза). Степенные множители эти очевидные уязвимости убирают, но может быть остаются какие-то неочевидные, о чём я и создал тему чтобы узнать.

firkax ★★★★★
() автор топика
Ответ на: комментарий от firkax

Тут у тебя сумма (функций от) итемов считается, а там как раз надо найти как составить сумму (второй прообраз).

ratvier ★★
()
Ответ на: комментарий от ratvier

Всё ещё не вижу связи. Ну то что сложение (или, скажем, арифметика) и там и там используется это да, но по такому признаку что-то обобщать неразумно.

firkax ★★★★★
() автор топика
Ответ на: комментарий от firkax

Никак не хочешь в криптографию? Давай тогда в теорию чисел.

Вот например нечётные простые числа:
2261564242916331941866620800950935700259179388000792266395655937654553313279
93628759656736142393278101159368737990730026663232799828780155818898507169793
Они являются 256-ыми примитивными корнями из единицы в кольце Z/(2^256)

То есть с ними,
pow(k, 256) = 1 (mod 2^256)
И как бы теперь понять, существуют ли подходящие k?

ratvier ★★
()
Ответ на: комментарий от ratvier

Это уже интереснее. Я как-то не подумал про этот аспект. Значит k нельзя выбирать наобум, и разумеется корни из единицы не только 256 степени бывают и всех их надо избегать. Но могут быть и какие-то другие плохие.

И как бы теперь понять, существуют ли подходящие k?

Очевидно, какие-то существуют.

Только я не пойму зачем сопровождать свои сообщения какими-то левыми наездами и вообще саркастическим тоном.

firkax ★★★★★
() автор топика
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.