PHP-каков хороший способ получения короткой буквенно-цифровой строки из длинного хэша md5?
Это для того, чтобы иметь хороший короткий URL, который ссылается на хэш md5 в базе данных. Я хотел бы преобразовать что-то вроде этого:
a7d2cd9e0e09bebb6a520af48205ced1
В что-то вроде этого:
hW9lM5f27
оба они содержат примерно одинаковое количество информации. Метод не должен быть прямым и обратимым, но это было бы неплохо (более гибким). По крайней мере, я бы хотите случайно сгенерированную строку с шестнадцатеричным хэшем в качестве семени, чтобы она воспроизводилась. Я уверен, что есть много возможных ответов, Мне любопытно посмотреть, как люди сделают это элегантным способом.
О, это не должно иметь идеального соответствия 1:1 с оригинальным хэшем, но это было бы бонусом (я думаю, я уже подразумевал, что с критериями обратимости). И я хотел бы избежать столкновений, если это возможно.
редактировать Я понял свое начальное вычисления были совершенно неправильными (благодаря людям, ответившим здесь, но мне потребовалось некоторое время, чтобы понять), и вы не можете действительно уменьшить длину строки, бросая все строчные и прописные буквы в микс. Поэтому я думаю, что мне нужно что-то, что напрямую не преобразуется из hex в base 62.
6 ответов
вот небольшая функция для рассмотрения:
/** 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 бит ввода, таким образом требуется 32 бита для кодирования 24 бит), 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 и именем файла safe alphabet , вы можете использовать эти функции:
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;+, / и символ заполнения =. Вы можете замените эти символы чем-то другим при преобразовании туда и обратно, чтобы получить кодировку Base64, безопасную для URL (или используйте безопасный набор символов для начала, если вы пишете свою собственную функцию).
Я бы посоветовал против a 1-1 соответствие:
с кодировкой base-64 вы только сможете уменьшить входной сигнал до (4/8)/(6/8) -> 4/6 ~ 66% по размеру (и это предполагает, что вы имеете дело с "уродливыми" символами base64 без добавления чего-либо нового).
Я бы, вероятно, рассмотрел (вторичный) метод поиска, чтобы получить действительно "красивые" значения. Как только вы установили этот альтернативный метод, выберите, как генерировать значения в этом диапазоне - например, random числа -- могут быть свободны от исходного хэш-значения (потому что соответствие все равно теряется), и может использоваться произвольный "красивый" целевой набор, возможно [a-z][A-Z][0-9].
вы можете преобразовать в базу (62 выше), просто следуя методу разделения и переноса и поиску в массив. Это должно быть забавное маленькое упражнение.
Примечание: Если вы выберете случайное число из [0, 62^5), то вы получите значение, которое полностью упакует кодированный вывод (и поместится в 32-бит целочисленное значение.) Затем вы можете выполнить этот процесс несколько раз подряд, чтобы получить хорошее значение, кратное-5, например xxxxxyyyyyyzzzzzz (где x,y, z-разные группы, а общее значение находится в диапазоне (62^5)^3 -> 62^15 -> "огромное значение")
Edit, для комментариев:
, потому что без 1-1 переписка вы можете сделать действительно короткие красивые вещи - возможно, как "маленький", как 8 символов длиной-с base62, 8 символов могут хранить вверх до 218340105584896 значений, что, скорее всего, больше, чем вам когда-либо понадобится. Или даже 6 символов, которые" только " позволяют хранить 56800235584 различных значений! (И вы все еще не можете сохранить это число в простом 32-битном целом: -) если вы опуститесь до 5 символов, вы снова уменьшите пространство (до чуть менее одного миллиарда: 916,132,832), но теперь у вас есть что-то, что может поместиться в 32-битное целое число со знаком (хотя это несколько расточительно).
БД не должна обеспечивать дубликатов, хотя и индекс на этом значении будет "быстрая фрагментация" со случайным источником (но вы можете использовать счетчики или что-то еще). Хорошо распределенный PRNG должен иметь минимальные конфликты (read: retries) в достаточно большом диапазоне (предполагая, что вы держите семенной ролик и не сбрасываете его или сбрасываете его соответствующим образом) - Super 7 может даже гарантировать отсутствие дубликатов во время цикла (только ~32k), но, как вы можете видеть выше, целевое пространство все еще большой. См. математику в верхней части того, что поддерживает отношение 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 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
кто-нибудь видел какие-либо проблемы с ним или есть возможности для улучшения?
Это зависит от того, что a7d2cd9e0e09bebb6a520af48205ced1
есть. Предполагая, что вы говорите о шестнадцатеричном числе, так как оно исходит от md5
, вы можете просто запустить base64_encode
. Если у вас есть hex в Строковой форме, вы хотите запустить hexdec
. Будьте осторожны,вы не столкнетесь с проблемами maxint.