PHP - Что хороший путь состоит в том, чтобы произвести короткую алфавитно-цифровую строку из длинного хеша md5?

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

a7d2cd9e0e09bebb6a520af48205ced1

во что-то вроде этого:

hW9lM5f27

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

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

ОТРЕДАКТИРУЙТЕ я понял, что мои начальные вычисления были полностью неправильными (благодаря людям, отвечающим здесь, но это взяло меня некоторое время к подсказке в), и Вы не можете действительно уменьшить длину строки очень путем добавления всех строчных и прописных букв в соединение. Таким образом, я предполагаю, что захочу что-то, что непосредственно не преобразовывает от шестнадцатеричного числа для базирования 62.

17
задан girng rodriguez 10 February 2019 в 13:28
поделиться

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 бит ввода, поэтому для кодирования 24 бит требуется 32 бита), 16 байтов становятся 22 байтами. Но поскольку кодировка base-64 обычно дополняет длину, кратную 4, мы можем взять только первые 22 символа из 24 выходных символов (последние 2 из которых являются заполнителями). Затем мы заменяем небезопасные для URL-адресов символы, используемые в кодировке base-64, на безопасные для URL-адресов эквиваленты.

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

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

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

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

8
ответ дан 30 November 2019 в 14:11
поделиться

Вот две функции преобразования 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));
}
5
ответ дан 30 November 2019 в 14:11
поделиться

Я бы посоветовал против соответствия 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) к любому основанию.

1
ответ дан 30 November 2019 в 14:11
поделиться

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

0
ответ дан 30 November 2019 в 14:11
поделиться

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

//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

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

1
ответ дан 30 November 2019 в 14:11
поделиться

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

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

1
ответ дан 30 November 2019 в 14:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: