1. Python / Говнокод #16926

    −97

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    def constant_time_compare(val1, val2):
        """
        Returns True if the two strings are equal, False otherwise.
    
        The time taken is independent of the number of characters that match.
        """
        if len(val1) != len(val2):
            return False
        result = 0
        for x, y in zip(val1, val2):
            result |= ord(x) ^ ord(y)
        return result == 0

    Django.utils.crypto в Django 1.4

    Запостил: american_idiot, 24 Октября 2014

    Комментарии (36) RSS

    • а ведь и на самом деле смешно.
      Ответить
    • компаратор бабушкина
      Ответить
    • Сравнение длин что ли?
      Ответить
    • >The time taken is independent of the number of characters that match.
      Вообще охуеть. Не совпало в первом символе гигабайтной строки.
      А прочитаешь, на первый взгляд вроде как улучшение: алгоритм не зависит от некоего M.
      Жаль питонистам еще брейки не завезли.
      Ответить
      • > А прочитаешь, на первый взгляд вроде как улучшение
        utils.crypto же!
        Это функция для сравнения всяких ключей и паролей. Надо сравнивать все символы, чтобы было невозможно использовать время работы функции для определения числа правильных символов при переборе.
        Но косяк в ней всё равно есть. Если длины разные, то значение возвращается сразу.
        Ответить
        • > Если длины разные, то значение возвращается сразу.
          Да и хер с ним, если честно. Хакер, скорее всего, и без этого знает, что там какой-нибудь sha-256 длиной 256 бит.
          Ответить
      • Qwertiy и chtulhu правильно пишут, constant time это не O(1), как могло бы показаться, а независимость от количества совпавших/несовпавших символов. Для криптографии это важно. Из-за этого там и логические операции вместо тернарников или ветвлений.
        Ответить
        • Все равно это не константное время. Оно прямо зависит от количества символов в строке. Т.е. линейное. А вообще, тут сама по себе задумка херовая. В эту функцию нужно было передавать третьим параметром количество символов, которые нужно сравнивать, тогда все было бы замечательно, а так нужно принимать решения о том, длину какого из аргументов считать эталоном, и как именно заполнять недостающие / убирать лишние символы.
          Ответить
          • >Т.е. линейное. А вообще, тут сама по себе задумка херовая.
            Об этом и речь. Надо проверить как можно быстрее, и только если НЕ совпало - поставить sleep/сделать N итераций чтоб получилось фиксированное.
            Тут где-то уже обсуждали на примере линуксов.
            Ответить
          • >А вообще, тут сама по себе задумка херовая.
            Почему?

            >Все равно это не константное время. Оно прямо зависит от количества символов в строке.
            Оно не зависит от позиции первого несовпадающего символа, я хз как это правильно записать.
            Ответить
    • Django.utils.crypto как бы намекает на криптографию.
      Для криптографических задач важно, чтобы данные проверялись одинаковое время и хакер не мог с помощью статистики быстро подобрать токены, пароли итд
      Ответить
      • заметка про timing attack?
        Ответить
      • И что? Время зависит от длины строки (пороля).
        Вы или O(N) снимите или crypto наденьте. Константным временем там и пахнет.
        Ответить
        • > Время зависит от длины строки (пороля).
          Пароли сравнивать - хреновая идея (т.к. подразумевает хранение пароля в открытом или обратимо зашифрованном виде). А сравниваться будут, скорее всего, какие-нибудь хеши или рандомные блоки известной и постоянной (ну ок, зависящей от настроек) длины. Время тут константное в том смысле, что оно зависит только от этой константной длины, но никак не зависит от значений бит в самих строках.
          Ответить
          • я плюсанул, потому что описывает что это делает.

            но все равно ахинея. время сравнения скажем 256 бит хэшей (== 32 байта), тем более на современном железе, незначительно и сравнимо, например, с временем выполнения пролога/эпилога функции. вкинь один быстрый syscall и о длине совпадающих символом можно только гадать на картах.
            Ответить
            • Я судить не берусь, т.как деталей не знаю, и искать мне не хочется, но я сильно сомневаюсь, что строки в Питоне не интернированы, и сравниваются посимвольно обычным ==. многие скриптовые языки интернируют строки. Т.е. если уж мы создали две строки с одинаковым содержанием, то мы проверяли их на равенство на этапе создания. Дальнейшие сравнения будут сравнивать ссылки а не содержание. (По крайней мере в ж.скрипте так). Так что я думаю, что ничего хорошего эта функция в Питон не привнесла.
              Ответить
              • Такая схема годится для констант. А если мы изменяли строки?
                Ответить
              • > строки в Питоне не интернированы
                Да не должны быть они интернированы... Это же довольно дорогая операция, и память жрёт. Часть строк - запросто может. Но никак не все.
                Ответить
                • http://guilload.com/python-string-interning/ вобщем, интернируются только литералы, но при желании это можно было бы форсировать.
                  Ответить
                  • > это можно было бы форсировать
                    Но не нужно. В чем смысл интернировать всё подряд, включая временные переменные, которые живут пару функций, и никто никогда не будет их сравнивать?

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

                    > отнимает время при создании
                    Именно.
                    Ответить
                • А вот сравнение строк за константное время
                  def const_compare_strings(a, b):
                      hasher = { }
                      hasher[a] = True
                      return hasher.has_key(b)
                  Ответить
                  • А ничего, что has_key по-любому включает в себя то самое обычное сравнение строки (для борьбы с коллизиями), от которого тут пытались избавиться? :)
                    Ответить
                    • Но ведь лукап в хеш-таблице это O(1)!!!

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

                        > Деревья будут предпочтительнее всегда.
                        А у деревьев еще больше сравнений. Так что не всегда.

                        > нахера такие хеш-таблицы вообще нужны
                        Ну хеш-таблицы ведь далеко не везде юзают. К примеру, в реалтаймовых приложениях, вставка в дерево за log(N) может быть предпочительней амортизированного O(1) у хеш табличек. Для огромных дисковых структур, не влезающих в память, деревья тоже предпочительней.
                        Ответить
                        • Нет, у дерева не будет в таком случае больше сравнений. Чтобы сгенерировать хеш, нужно прочитать всю строку целиком. Дереву хеш не нужно генерировать, т.е. есть шанс, что всю строку читать не прийдется.

                          Интернированые строки нужно хранить в структуре с быстрым доступом - а остальные объекты не нужно? Если это делается на уровне языка, а не силами добровольцев, то на памяти это вообще никак не отразится. Примеры: V8 (NodeJS) и Луа. И ничего, живут как-то.
                          Ответить
                          • Я не знаю кто минуснул. Всё по делу ведь.
                            Ответить
                          • Только несколько небольших уточнений:
                            > Чтобы сгенерировать хеш, нужно прочитать всю строку целиком.
                            На коротких строках, с которые обычно встречаются на практике в виде ключей это не сильно ощутимо.
                            Если же смотреть контексте интернирования, то тут выгоднее повторно использовать ссылки на строки большей длины.

                            Ну и если строка немутабельная, то посчитать хеш придется всего 1 раз, и таким образом за O(1) проверять отсутствие строки в кеше. А ведь можно сделать и джва хеша, что прилично снизит вероятность полного сопоставления.

                            Кстати мы не так давно тут с Тарасом обсуждали префиксные деревья.
                            Ответить
                            • >мы не так давно тут с Тарасом обсуждали

                              Орально обсуждали?
                              Ответить
                              • >>Ищу хуястых кавказцев
                                >>Очко сильно раздолбано

                                >>Орально обсуждали
                                Не, это не в стиле Тараса. Непохоже на него.
                                Ответить
                        • В CHMv8 автор скрестил хешмапу с деревом, заменив связный список в букете на дерево, если число коллизий >6 емнип.
                          Ответить
      • def not_a_constant_time_compare(val1, val2):
        """
        Returns True if the two strings are equal, False otherwise.

        The time taken is independent of the number of characters that match.
        """
        time.sleep(random(500))
        return val1==val2
        Ответить
    • Въебал всем по "плюсу", чтоб неповадно было c++-ничать. Будь моя воля, я бы отправил в отхожее место все разделы (кроме php разумеется).
      Ответить

    Добавить комментарий