Это в целях наличия хорошего короткого URL, который относится к хешу md5 в базе данных. Я хотел бы преобразовать что-то вроде этого:
a7d2cd9e0e09bebb6a520af48205ced1
во что-то вроде этого:
hW9lM5f27
Они содержат о том же объеме информации. Метод не должен быть прямым и обратимым, но это было бы хорошо (более гибкий). Самое меньшее я хотел бы случайным образом сгенерированную строку с шестнадцатеричным хешем как семя, таким образом, это восстанавливаемо. Я уверен, что существует много возможных ответов, мне любопытно видеть, как люди сделали бы это изящным способом.
О, это не должно иметь прекрасный 1:1 корреспонденция исходному хешу, но это было бы премией (я предполагаю, что уже подразумевал это с критериями обратимости). И я хотел бы избежать коллизий, если это возможно.
ОТРЕДАКТИРУЙТЕ я понял, что мои начальные вычисления были полностью неправильными (благодаря людям, отвечающим здесь, но это взяло меня некоторое время к подсказке в), и Вы не можете действительно уменьшить длину строки очень путем добавления всех строчных и прописных букв в соединение. Таким образом, я предполагаю, что захочу что-то, что непосредственно не преобразовывает от шестнадцатеричного числа для базирования 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 символа, потому что каждый байт кодируется как 2 шестнадцатеричные цифры (например, 00-FF). Итак, мы разбиваем их на байты и составляем из них 16-байтовую строку. Но поскольку этот код ASCII больше не доступен для чтения и не является допустимым, мы кодируем его обратно в формате base-64 в удобочитаемые символы. Но поскольку base-64 приводит к расширению ~ 4/3 (мы выводим только 6 бит на 8 бит ввода, поэтому для кодирования 24 бит требуется 32 бита), 16 байтов становятся 22 байтами. Но поскольку кодировка base-64 обычно дополняет длину, кратную 4, мы можем взять только первые 22 символа из 24 выходных символов (последние 2 из которых являются заполнителями). Затем мы заменяем небезопасные для URL-адресов символы, используемые в кодировке base-64, на безопасные для URL-адресов эквиваленты.
Это полностью обратимо, но это оставлено читателю в качестве упражнения.
Я думаю, что это лучшее, что вы можете сделать, если вы не заботитесь о читабельном коде / ASCII, и в этом случае вы можете просто использовать $ md5_bin_str напрямую.
А также вы можете использовать префикс или другое подмножество результата этой функции, если вам не нужно сохранять все биты. Выбрасывание данных - это, очевидно, самый простой способ сократить количество вещей! (Но тогда это необратимо)
P.S. для вашего ввода «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));
}
Я бы посоветовал против соответствия 1-1:
С кодировкой base-64 вы сможете уменьшить вход только до (4/8)/(6/8) -> 4/6 ~ 66% по размеру (и это при условии, что вы справитесь с "уродливыми" символами base64, не добавляя ничего нового).
Для получения действительно "красивых" значений я бы, вероятно, рассмотрел (вторичный) метод поиска. Как только вы установили этот альтернативный метод, выбор способа генерации значений в этом диапазоне - например, случайных чисел - может быть свободен от исходного хэш-значения (поскольку соответствие все равно теряется), и можно использовать произвольный "красивый" целевой набор, возможно, [a-z][A-Z][0-9].
Вы можете преобразовать в базу (62 выше), просто следуя методу divide-and-carry и переходу к массиву. Это должно быть небольшое забавное упражнение.
Примечание: Если вы выберете случайное число из [0, 62^5], то получите значение, которое полностью упакует закодированный вывод (и уложится в 32-битные целочисленные значения). Затем вы можете выполнить этот процесс несколько раз подряд, чтобы получить красивое значение результата, кратное 5, например xxxxxyyyyyyyyzzzzzz (где x,y,z - разные группы, а общее значение находится в диапазоне (62^5)^3 -> 62^15 -> "огромное значение")
Правка, для комментария:
Поскольку без соответствия 1-1 вы можете делать действительно короткие красивые вещи - возможно, такие "маленькие", как 8 символов в длину - с base62, 8 символов могут хранить до 218340105584896 значений, что, вероятно, больше, чем вам когда-либо понадобится. Или даже 6 символов, которые "только" позволяют хранить 56800235584 различных значений! (И вы все равно не сможете хранить это число в обычном 32-битном целочисленном формате :-) Если вы опуститесь до 5 символов, вы снова уменьшите пространство (до чуть менее одного миллиарда: 916 132 832), но теперь у вас есть что-то, что может поместиться в подписанном 32-битном целочисленном формате (хотя это и несколько расточительно).
БД должна гарантировать отсутствие дубликатов, хотя индекс на это значение будет "быстро фрагментироваться" со случайным источником (но вы можете использовать счетчики или что-то еще). Хорошо распределенный ГПСЧ должен иметь минимальные конфликты (читай: повторные попытки) в достаточно большом диапазоне (при условии, что вы сохраняете семя и не сбрасываете его, или сбрасываете его соответствующим образом) - Super 7 может даже гарантировать отсутствие дубликатов в течение цикла (всего ~32k), но, как вы можете видеть выше, целевое пространство все еще большое. Смотрите математику вверху о том, чего требует поддержание отношений 1-1 с точки зрения минимального кодированного размера.
Метод деления и переноса просто объясняет, как перевести исходное число в другое основание - возможно, в основание 62. Этот же общий метод можно применить для перехода от "естественного" основания (base10 в PHP) к любому основанию.
Это зависит от того, что такое a7d2cd9e0e09bebb6a520af48205ced1
. Предполагая, что вы говорите о шестнадцатеричном числе, поскольку оно получено из md5
, вы можете просто выполнить base64_encode
. Если у вас есть шестнадцатеричное число в виде строки, вы можете выполнить hexdec
. Будьте осторожны, чтобы не столкнуться с проблемами maxint.
Конечно, если я хочу, чтобы функция идеально удовлетворяла моим потребностям, мне лучше сделать ее самому. Вот что у меня получилось.
//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
Кто-нибудь видит какие-либо проблемы или возможности для улучшения?
Вы можете просто выполнить старое преобразование базы. Хэш выражается в шестнадцатеричной системе, и вы можете создать алфавит того размера, который вам нужен для выражения хэша. Base64 хорошо работает для этой цели, хотя вы, вероятно, захотите написать свою собственную функцию, чтобы в итоге кодировать значение, а не строку.
Обратите внимание, однако, что стандартный Base64 содержит символы, которые нежелательно вводить в URL: +, / и символ подстановки =. Вы можете заменить эти символы на другие при преобразовании туда и обратно, чтобы получить безопасную для URL кодировку Base64 (или использовать безопасный набор символов для начала, если вы пишете собственную функцию).