PHP. Каков хороший способ создать короткую буквенно-цифровую строку из длинного хэша md5?
Это для того, чтобы иметь хороший короткий URL-адрес, который ссылается на хеш файл md5 в базе данных. Я хотел бы преобразовать что-то вроде этого:
a7d2cd9e0e09bebb6a520af48205ced1
в нечто подобное:
hW9lM5f27
В обоих случаях содержится примерно столько же информации. Метод не должен быть прямым и обратимым, но это было бы неплохо (более гибко). По крайней мере, я хотел бы получить случайно генерируемую строку с шестнадцатеричным хешем в качестве семени, чтобы он был воспроизводимым. Я уверен, что есть много возможных ответов, мне любопытно посмотреть, как люди будут делать это элегантным способом.
О, это не обязательно должно иметь идеальную переписку 1:1 с исходным хэшем, но это будет бонус (я думаю, я уже подразумевал, что с критериями обратимости). И я хотел бы избежать столкновения, если это возможно.
ИЗМЕНИТЬ
Я понял, что мои первоначальные вычисления были абсолютно неправильными (спасибо людям, которые отвечали здесь, но мне потребовалось некоторое время, чтобы понять), и вы не можете очень сильно уменьшить длину строки, вставив все нижние и верхние буквы в микс. Поэтому я предполагаю, что мне захочется что-то, что напрямую не преобразует из гексафона в базу 62.
Ответы
Ответ 1
Конечно, если я хочу, чтобы функция удовлетворяла мои потребности, я лучше сама это делала. Вот что я придумал.
//takes a string input, int length and optionally a string charset
//returns a hash 'length' digits long made up of characters a-z,A-Z,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
Кто-нибудь видит какие-либо проблемы с этим или какие-либо возможности для улучшения?
Ответ 2
Здесь небольшая функция для рассмотрения:
/** 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 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 символа, потому что каждый байт кодируется как 2 шестнадцатеричных цифры (то есть 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 напрямую.
А также вы можете использовать префикс или другое подмножество результата из этой функции, если вам не нужно сохранять все биты. Выброс данных - это, пожалуй, самый простой способ сократить количество вещей! (Но тогда это не обратимо)
P.S. для ввода "a7d2cd9e0e09bebb6a520af48205ced1" (32 символа), эта функция вернет "VUDNng4JvrtqUgr0QwXO0Q" (22 символа).
Ответ 3
Вот две функции преобразования для преобразования 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));
}
Ответ 4
Вы можете просто сделать обычное старое базовое преобразование. Хэш выражается в шестнадцатеричном виде, и тогда вы можете создать алфавит размера, который хотите выразить хэш. Base64 работает хорошо для этой цели, хотя вы, вероятно, захотите написать свою собственную функцию, чтобы вы в конечном итоге кодировали значение, а не строку.
Обратите внимание, однако, что стандартный Base64 содержит символы, которые вы не хотели бы размещать в URL-адресе; +,/и символ заполнения =. Вы можете заменить эти символы чем-то другим при преобразовании взад и вперед, чтобы получить безопасную для URL кодировку Base64 (или использовать безопасный набор символов для начала, если вы напишете свою собственную функцию).
Ответ 5
Я бы посоветовал не отвечать 1-1:
При использовании кодировки base-64 вы сможете уменьшить вход (4/8)/(6/8) → 4/6 ~ 66% (и это предполагает, что вы имеете дело с "уродливым" "base64 персонажей без добавления чего-либо нового).
Я бы, скорее всего, рассмотрел (вторичный) метод поиска, чтобы получить действительно "красивые" значения. После того, как вы установили этот альтернативный метод, выберите способ создания значений в этом диапазоне - например, случайные числа - могут быть свободны от хеш-значения источника (поскольку соответствие все равно потеряно), и может использоваться произвольный "милый" целевой набор, возможно [a-z] [A-Z] [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 должен иметь минимальные конфликты (чтение: повторы) в достаточно большом диапазоне (при условии, что вы сохраняете скотч и не выполняете reset, или reset соответственно) - Super 7 может даже гарантировать отсутствие дублирует во время цикла (всего ~ 32 тыс.), но, как вы можете видеть выше, целевое пространство по-прежнему велико. См. Математику в верхней части того, что требует отношения 1-1 в отношении минимального кодированного размера.
Метод деления и переноса объясняет, как получить исходный номер в другую базу - возможно, base62. Один и тот же общий метод может применяться для перехода от "естественной" базы (base10 в PHP) к любой базе.
Ответ 6
Это зависит от того, что a7d2cd9e0e09bebb6a520af48205ced1
. Предполагая, что вы говорите о шестнадцатеричном номере, так как оно происходит от md5
, вы можете просто запустить base64_encode
. Если у вас есть шестнадцатеричный код в форме строки, вы должны запустить hexdec
. Будьте осторожны, вы не сталкиваетесь с проблемами с максимальным значением.