librown
@librown
На-все-руки-мастер и немного кодер

Могут ли возникнуть дубли хешей?

Доброго дня!
На проекте каждому заказу генерируется ссылка вида site.com/a715caeb
Чтобы пользователи без авторизации могли по этим ссылкам попадать в свои кабинеты.
Зачем использую crc32b? Чтобы ссылки были как можно короче - для использования в смс-рассылке.

Генерирую так:
$hash = hash('crc32b', md5($client_email.$id_item));

$client_email - email клиента
$id_item - ID товара

Это нормальное решение, как вы считаете? Терзают сомнения может ли получиться ситуация, когда для разных емейлов и товаров сгенерируется один и тот же хеш? Или маловероятно?
  • Вопрос задан
  • 6975 просмотров
Пригласить эксперта
Ответы на вопрос 6
torrie
@torrie
Всё знаю, всё умею
Скорее всего у вас не будет таких объемов, чтобы наткнуться на коллизию.
Но лучше перестраховаться - проходить по базе в поиске такого же хеша. Если имеется - использовать другой id. А лучше сделать вариант с несколькими солями и указанием соли в бд при генерации. Как можно чаще используйте соль при генерации любых хешей.
Ответ написан
Комментировать
FanatPHP
@FanatPHP
Чебуратор тега РНР
Решение отвратительное. Это уже что-то из серии про архиватор Бабушкина.

Ну разумеется, коллизии будут.

Лучше оставить MD5 (у которого вероятность коллизий вполне в пределех допустимого), но перевести его из неэффективного base16 в более короткую форму. Base64 вполне подойдёт, поскольку кодировщик есть в пхп из коробки. Вот только оба не буквенно-цифровых символа там не подходят для передачи через урл - лучше их заменить:
$base64 =  substr(strtr(base64_encode(hex2bin($md5)),'+/',"_-"),0,-2);

Итого экономим 10 символов из 32-х. Конечно, 22 хуже чем 8, но тут надо выбирать - или достаточная длина, или коллизии и отсутствие безопасности вообще.
Ответ написан
Комментировать
ivankomolin
@ivankomolin
Если кто-то узнает это:
Генерирую так:
$hash = hash('crc32b', md5($client_email.$id_item));

То сможет получить доступ к любому кабинету зная только email пользователя.
Это не очень хорошо.

В таких случаях нужно делать так:
1. При создании пользователя генерируете "токен" по которому можно заходить без пароля, например так:
$hash = hash('crc32b', md5(uniqid(rand(), true)));
2. При каждом заходе пользователя в кабинет по этому "токену" меняете его.

Ну а чтобы не получилось одинаковых "токенов", нужно при создании/смене "токена" проверять на наличие такого же. Т.е. генерировать "токен" до тех пор пока он не будет уникальным, тогда и записывать в бд.
Ответ написан
@eandr_67
web-программист (*AMP, Go, JavaScript, вёрстка).
Абсолютно НЕнормальное. Прочитай на wiki про "парадокс дней рождения". Вероятность коллизии для абсолютно равномерного распределения равна 1/sqrt(2^32)=1/(2^16)=1/65536. В реальности crc32 не обеспечивает равномерного распределения и потому вероятность коллизии будет намного больше.

К тому же столь короткий адрес приведет к тому, что злоумышленник сможет добраться до чужих заказов путём простого перебора.
Ответ написан
Комментировать
@throughtheether
human after all
Считая сначала md5, затем crc32, вы, по моему мнению, увеличиваете вероятность коллизии. Коллизия может возникнуть в функции md5 (одинаковые значения для разных входных данных), что сразу приведет к коллизии на выходе crc32 (одинаковые значения для одинаковых входных данных). Кроме того, коллизия может возникнуть в функции crc32b (одинаковые значения на выходе для двух разных результатов md5). Не знаю, как работает функция md5 в php, но мне представляется, что ее вывод имеет некую структуру (32 шестнадцатеричные цифры), что может увеличить вероятность коллизии crc32b.
Зачем использую crc32b? Чтобы ссылки были как можно короче - для использования в смс-рассылке.
На вашем месте, если нужен детерминизм, я бы:
1) использовал хэш-функции из семейства SHA-2 (SHA-256,SHA-512).
2) использовал бы N последних бит (например, 48, сложность подбора заданного хэша в среднем 2^48 попыток, сложность нахождения двух произвольных пользователей с совпадаюшими хэшми, как отметил @eandr_67 в комментарии, значительно меньше, 2^24 попыток в среднем, в силу квадратичного количества пар)
3) транслировал бы эти 48 бит в 8 символов 64-символьного алфавита (латинские буквы в обоих регистрах + цифры + 2 символа)
4) использовал бы полученную 8-символьную строку
Пункты 2,3,4 можно подогнать под ваши специфические требования.

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

Во-первых, непонятно, почему отслеживаются заказы, а хэш вы считаете от товара. Сегодня, например, клиент заказывает один товар одновременно, а завтра, когда концепция поменяется - два и более. Более логичным представляется отслеживание заказов (примерные характеристики заказа - номер телефона/email пользователя, список товаров, срок и адрес доставки и прочая).
Далее, непонятно, почему решено использовать хэширование. Я предполагаю, что проблем с хранением данных нет. Почему бы не хранить вместе с данными заказа соответствующую ему 'секретную' ссылку, сгенерированную при помощи ГПСЧ?
Далее, если вы планируете использовать короткую ссылку в смс, то имеет смысл использовать для ее составления специализированный алфавит, отобрав из набора a-z,A-Z,0-9 наиболее удобные в плане UX. Например, буквы I и l бывают трудноразличимы. Здесь важно также максимизировать мощность множества возможных значений строки-ссылки (равное A^l, где A - количество символов в алфавите, l - длина строки-ссылки). В случае 32 символов в алфавите и 8 символов в строке получаем 2^40 вариантов, сложность нахождения пары пользователей с совпадающими ссылками в среднем 2^20 попыток (вероятности релевантных коллизий, соответственно, обратны этим значениям).
Далее, можно разделить функциональность страницы заказа, при простом доступе по ссылке показывать только статус/общую информацию, а важные действия (отменить заказ, изменить адрес, и т.д.) разрешать после прохождения дополнительной проверки (например, на известный номер телефона/e-mail клиента пересылается короткоживущий секрет, который должен быть введен на странице для продолжения).
Такие мысли.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы