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

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

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

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

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

Это нормальное решение, как вы считаете? Терзают сомнения может ли получиться ситуация, когда для разных емейлов и товаров сгенерируется один и тот же хеш? Или маловероятно?
  • Вопрос задан
  • 6748 просмотров
Пригласить эксперта
Ответы на вопрос 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 клиента пересылается короткоживущий секрет, который должен быть введен на странице для продолжения).
Такие мысли.
Ответ написан
Ваш ответ на вопрос

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

Войти через центр авторизации
Похожие вопросы