PHP. Каков хороший способ создать короткую буквенно-цифровую строку из длинного хэша md5?

Это для того, чтобы иметь хороший короткий URL-адрес, который ссылается на хеш-файл md5 в базе данных. Я хотел бы преобразовать что-то вроде этого:

a7d2cd9e0e09bebb6a520af48205ced1

в нечто подобное:

hW9lM5f27

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

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

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

    Вот небольшая функция для рассмотрения:

    /** Return 22-char compressed version of 32-char hex string (eg from PHP md5). */ function compress_md5($md5_hash_str) { // (we start with 32-char $md5_hash_str eg "a7d2cd9e0e09bebb6a520af48205ced1") $md5_bin_str = ""; foreach (str_split($md5_hash_str, 2) as $byte_str) { // ("a7", "d2", ...) $md5_bin_str .= chr(hexdec($byte_str)); } // ($md5_bin_str is now a 16-byte string equivalent to $md5_hash_str) $md5_b64_str = base64_encode($md5_bin_str); // (now it's a 24-char string version of $md5_hash_str eg "VUDNng4JvrtqUgr0QwXOIg==") $md5_b64_str = substr($md5_b64_str, 0, 22); // (but we know the last two chars will be ==, so drop them eg "VUDNng4JvrtqUgr0QwXOIg") $url_safe_str = str_replace(array("+", "/"), array("-", "_"), $md5_b64_str); // (Base64 includes two non-URL safe chars, so we replace them with safe ones) return $url_safe_str; } 

    В основном у вас есть 16-байтовые данные в хэш-строке MD5. Это 32 символа, потому что каждый байт кодируется как две шестнадцатеричные цифры (то есть 00-FF). Поэтому мы разбиваем их на байты и создаем 16-байтовую строку. Но поскольку это уже не читаемый человеком или действительный ASCII, мы base-64 кодируем его обратно в читаемые символы. Но так как base-64 приводит к расширению ~ 4/3 (мы выводим только 6 бит на 8 бит ввода, что требует 32 бита для кодирования 24 бит), 16-байты становятся 22 байтами. Но поскольку кодировка base-64 типично подходит для длин, кратных 4, мы можем взять только первые 22 символа 24-символьного вывода (последние 2 из которых дополняют). Затем мы заменяем не-URL-безопасные символы, используемые кодировкой base-64, с эквивалентными URL-эквивалентами.

    Это полностью обратимо, но это остается как упражнение для читателя.

    Я думаю, что это лучшее, что вы можете сделать, если вам не все равно, что читаемый человеком / ASCII, и в этом случае вы можете просто использовать $ md5_bin_str напрямую.

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

    PS для вашего ввода «a7d2cd9e0e09bebb6a520af48205ced1» (32 символа), эта функция вернет «VUDNng4JvrtqUgr0QwXO0Q» (22 символа).

    Вот две функции преобразования для преобразования Base-16 в Base-64 и обратные Base-64 в Base-16 для произвольной длины ввода:

     function base16_to_base64($base16) { return base64_encode(pack('H*', $base16)); } function base64_to_base16($base64) { return implode('', unpack('H*', base64_decode($base64))); } 

    Если вам нужна кодировка Base-64 с безопасным алфавитом URL и имени файла , вы можете использовать следующие функции:

     function base64_to_base64safe($base64) { return strtr($base64, '+/', '-_'); } function base64safe_to_base64($base64safe) { return strtr($base64safe, '-_', '+/'); } 

    Если теперь вам нужна функция для сжатия шестнадцатеричных значений MD5 с использованием безопасных символов URL, вы можете использовать это:

     function compress_hash($hash) { return base64_to_base64safe(rtrim(base16_to_base64($hash), '=')); } 

    И обратная функция:

     function uncompress_hash($hash) { return base64_to_base16(base64safe_to_base64($hash)); } 

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

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

    Я бы посоветовал не отвечать 1-1:

    При использовании кодировки base-64 вы сможете уменьшить размер ввода (4/8) / (6/8) -> 4/6 ~ 66% (и предполагается, что вы имеете дело с «уродливыми» символами base64 без добавления чего-либо нового).

    Вероятно, я бы рассмотрел (вторичный) метод поиска, чтобы получить действительно «красивые» значения. После того, как вы установили этот альтернативный метод, выбор способа генерации значений в этом диапазоне – например, случайные числа – может быть свободен от хеш-значения источника (поскольку соответствие все равно потеряно), и может использоваться произвольный «милый» целевой набор , возможно [az] [AZ] [0-9].

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

    Примечание. Если вы выберете случайное число из [0, 62 ^ 5), вы получите значение, которое будет полностью упаковать кодированный вывод (и будет соответствовать 32-битным целым значениям). Затем вы можете выполнить этот процесс несколько раз подряд, чтобы получить отличное кратное значение результата -5, например xxxxxyyyyyzzzzzz (где x, y, z – разные группы, а общее значение находится в диапазоне (62 ^ 5) ^ 3 -> 62 ^ 15 -> «огромное значение»)

    Редактировать, для комментариев :

    Потому что без соответствия 1-1 вы можете сделать действительно короткие красивые вещи – возможно, как «маленькие», как 8 символов, – с base62, 8 символов могут хранить до 218340105584896 значений, что, вероятно, больше, чем вам когда-либо понадобится. Или даже 6 символов, которые «только» позволяют хранить 56800235584 разных значений! (И вы все равно не можете сохранить это число в простом 32-битном целое 🙂 Если вы сбросите до 5 символов, вы еще раз уменьшите пространство (до чуть более одного миллиарда: 916,132,832), но теперь у вас есть что-то, что может вписывается в подписанное 32-битное целое число (хотя оно несколько расточительно).

    БД не должна содержать дубликатов, хотя индекс этого значения будет «быстро фрагментирован» со случайным источником (но вы можете использовать счетчики или еще что-то). Хорошо распределенный PRNG должен иметь минимальные конфликты (чтение: повторы) в достаточно большом диапазоне (при условии, что вы сохраняете перемещение семян и не сбрасываете его или не устанавливаете соответствующим образом) – Super 7 может даже гарантировать отсутствие дубликатов во время цикла (всего ~ 32 тыс.), но, как вы можете видеть выше, целевое пространство по-прежнему велико . См. Математику в верхней части того, что требуется для поддержания соотношения 1-1 в терминах минимального кодированного размера .

    Метод деления и переноса объясняет, как получить исходный номер в другую базу – возможно, base62. Один и тот же общий метод может применяться для перехода от «естественной» базы (base10 в PHP) к любой базе.

    Конечно, если я хочу, чтобы функция удовлетворила мои потребности, я лучше сделаю это сам. Вот что я придумал.

     //takes a string input, int length and optionally a string charset //returns a hash 'length' digits long made up of characters az,AZ,0-9 or those specified by charset function custom_hash($input, $length, $charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUFWXIZ0123456789'){ $output = ''; $input = md5($input); //this gives us a nice random hex string regardless of input do{ foreach (str_split($input,8) as $chunk){ srand(hexdec($chunk)); $output .= substr($charset, rand(0,strlen($charset)), 1); } $input = md5($input); } while(strlen($output) < $length); return substr($output,0,$length); } 

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

     custom_hash('1d34ecc818c4d50e788f0e7a9fd33662', 16); // 9FezqfFBIjbEWOdR custom_hash('Bilbo Baggins', 5, '0123456789bcdfghjklmnpqrstvwxyz'); // lv4hb custom_hash('', 100, '01'); // 1101011010110001100011111110100100101011001011010000101010010011000110000001010100111000100010101101 

    Кто-нибудь видит какие-либо проблемы с ним или какие-либо возможности для улучшения?

    Это зависит от того, что такое a7d2cd9e0e09bebb6a520af48205ced1 . Предполагая, что вы говорите о шестнадцатеричном номере, так как он поступает из md5 , вы можете просто запустить base64_encode . Если у вас есть шестнадцатеричный hexdec в форме строки, вы хотите запустить hexdec . Будьте осторожны, вы не столкнетесь с проблемами максита.