Подтвердить что ты не робот

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

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

a7d2cd9e0e09bebb6a520af48205ced1

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

hW9lM5f27

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

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

ИЗМЕНИТЬ Я понял, что мои первоначальные вычисления были абсолютно неправильными (спасибо людям, которые отвечали здесь, но мне потребовалось некоторое время, чтобы понять), и вы не можете очень сильно уменьшить длину строки, вставив все нижние и верхние буквы в микс. Поэтому я предполагаю, что мне захочется что-то, что напрямую не преобразует из гексафона в базу 62.

4b9b3361

Ответ 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. Будьте осторожны, вы не сталкиваетесь с проблемами с максимальным значением.