Увеличение целочисленного диапазона карты до шестизначного 26 макс., Но непредсказуемо

Я хочу спроектировать сокращение URL для конкретного варианта использования и типа конечного пользователя, на которого я нацелился. Я решил, что я хочу, чтобы URL-адреса были сохранены внутри в соответствии с автоматически увеличивающимся целочисленным ключом. Однако также необходимо, чтобы ключ был представлен пользователям в URL-адресе как шестизначное основание 26 (az * 6), и невозможно предсказать, что основа 26-го URL-ключа основана на увеличивающемся целочисленном ключе. Другими словами, первый ключ URL-адреса не должен быть aaaaaa, затем в следующий раз, когда кто-то создает URL-адрес, это не должен быть aaaaab и т. Д., И не должно быть цикла, генерирующего случайный ключ и отправляющего данные в базу данных, чтобы увидеть, существует ли он уже неоднократно.

Вторая часть требований (URL-адреса в базе 26, которые посторонний предсказать сложно) является более интересной. В идеале я хотел бы получить какое-то алгоритмическое 1-1 отображение всех чисел в диапазоне 26^6 на другое число в том же диапазоне, которое я могу просто затем распечатать в базе 26, и которое я могу отменить алгоритмически и не нужно хранить в отдельной таблице, когда я хочу посмотреть URL. Как я могу сделать это?

8 ответов

Почему бы просто не перемешать биты в определенном порядке перед преобразованием в базовое значение 26? Например, бит 0 становится битом 5, бит 1 становится битом 2 и т. Д. Чтобы декодировать, просто сделайте обратное.

Вот пример на Python. (Отредактировано сейчас, чтобы включить преобразование базы тоже.)

import random

# generate a random bit order
# you'll need to save this mapping permanently, perhaps just hardcode it
# map how ever many bits you need to represent your integer space
mapping = range(28)
mapping.reverse()
#random.shuffle(mapping)

# alphabet for changing from base 10
chars = 'abcdefghijklmnopqrstuvwxyz'

# shuffle the bits
def encode(n):
    result = 0
    for i, b in enumerate(mapping):
        b1 = 1 << i
        b2 = 1 << mapping[i]
        if n & b1:
            result |= b2
    return result

# unshuffle the bits
def decode(n):
    result = 0
    for i, b in enumerate(mapping):
        b1 = 1 << i
        b2 = 1 << mapping[i]
        if n & b2:
            result |= b1
    return result

# change the base
def enbase(x):
    n = len(chars)
    if x < n:
        return chars[x]
    return enbase(x/n) + chars[x%n]

# go back to base 10
def debase(x):
    n = len(chars)
    result = 0
    for i, c in enumerate(reversed(x)):
        result += chars.index(c) * (n**i)
    return result

# test it out
for a in range(200):
    b = encode(a)
    c = enbase(b)
    d = debase(c)
    e = decode(d)
    while len(c) < 7:
        c = ' ' + c
    print '%6d %6d %s %6d %6d' % (a, b, c, d, e)

Вывод этого скрипта, показывающий процесс кодирования и декодирования:

   0            0       a            0    0
   1    134217728  lhskyi    134217728    1
   2     67108864  fqwfme     67108864    2
   3    201326592  qyoqkm    201326592    3
   4     33554432  cvlctc     33554432    4
   5    167772160  oddnrk    167772160    5
   6    100663296  imhifg    100663296    6
   7    234881024  ttztdo    234881024    7
   8     16777216  bksojo     16777216    8
   9    150994944  mskzhw    150994944    9
  10     83886080  hbotvs     83886080   10
  11    218103808  sjheua    218103808   11
  12     50331648  egdrcq     50331648   12
  13    184549376  pnwcay    184549376   13
  14    117440512  jwzwou    117440512   14
  15    251658240  veshnc    251658240   15
  16      8388608   sjheu      8388608   16
  17    142606336  mabsdc    142606336   17
  18     75497472  gjfmqy     75497472   18
  19    209715200  rqxxpg    209715200   19

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

Это просто, эффективно и должно быть достаточно для ваших целей. Если вам действительно нужно что-то безопасное, я бы не стал этого рекомендовать. Это в основном наивный блочный шифр. Там не будет никаких столкновений.

Вероятно, лучше всего убедиться, что бит N никогда не отображается на бит N (без изменений), и, вероятно, лучше всего, если в целом некоторые младшие биты на входе отображаются в старшие биты на выходе. Другими словами, вы можете создать отображение вручную. Фактически, приличное отображение просто изменило бы порядок битов. (Это то, что я сделал для примера вывода выше.)

Использование функции Hash с семенем должно сделать его непредсказуемым.
Безопасность, очевидно, не является проблемой (иначе вы бы использовали криптографию).

На самом деле, вы можете сразу использовать MD5 и выбрать фиксированные 6 символов для простого решения, которое будет хорошо работать. Он доступен на большинстве языков и генерирует буквенно-цифровой хеш 128-битный хеш, который легко записывается в виде 32 шестнадцатеричных. Это на самом деле всего 16 символов (сводится к базе 16).

Создание собственного алгоритма непредсказуемого хеширования не рекомендуется.
Вот запись в блоге Coding Horror, которую вы тоже должны прочитать.


Я явно дважды цитирую упоминание Джеффа "Кодирующий ужас", чтобы подчеркнуть.

Предположим, вы используете что-то вроде MD5 (БОГ ХАШ). MD5 принимает любую длину строки входных байтов и выводит 128 бит. Биты постоянно случайные, в зависимости от входной строки. Если вы отправите одну и ту же строку дважды, вы получите точно такие же случайные 16 байтов. Но если вы сделаете даже небольшое изменение во входной строке - даже одно-битное изменение - вы получите совершенно другой выходной хеш.

Итак, когда вам нужно беспокоиться о столкновениях? Рабочее правило здесь происходит из парадокса дня рождения. По сути, вы можете ожидать первого столкновения после хэширования 2n/2 элементов или 2^64 для MD5.

2^64 - это большое число. Если в сети 100 миллиардов URL, а мы все их MD5, увидим ли мы столкновение? Ну, нет, так как 100 000 000 000 намного меньше, чем 2^64:

2^64 18,446,744,073,709,551,616
2 ^ 37 100 000 000 000


Обновление на основе комментариев.

  • При шестнадцатеричном представлении из 6 символов, как я предлагаю выше, вероятность столкновений уменьшается до 2^12 - это всего 4096! (прочитайте всю статью Coding Horror для нюансов).
  • Если вы не хотите повторения при сокращении (одна и та же сокращенная форма для URL-адреса каждый раз), случайное число должно подойти.

Это зависит от того, что вы подразумеваете под непредсказуемым. Если вам нужна криптографическая защита, вам может быть интересен алгоритм Blum Blum Shub, но, вероятно, нет.

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

Я не уверен, как использовать все пространство 26^6, если вы используете LFSR. LFRS имеет определенную битовую длину и циклически перебирает каждую возможную комбинацию этих битов (за исключением 00...0, я думаю). Вы можете использовать 28-битный LFSR, но вы потеряете 40 миллионов лучших комбинаций (что составляет около 13% из них).

Похоже, что можно сопоставить состояния LFSR с порядковыми номерами (т. Е. N-е состояние LFSR равно x), но на это есть патент... Но вы все равно хотите пойти в обратном направлении.

Как насчет LFSR? Регистр сдвига с линейной обратной связью используется для генерации псевдослучайных чисел в диапазоне - операция является детерминированной, учитывая начальное значение, но она может посещать каждое значение в диапазоне с длинным циклом.

Вы хотите изменить свой первоначальный автоинкрементный идентификационный номер в сети Feistel. Это сообщение (которое находится в списках PostgreSQL, но на самом деле не имеет ничего общего с PostgreSQL) описывает простую сеть Фейстеля. Конечно, существует множество вариаций, но в целом это правильный подход.

Недавно я задавал в основном тот же вопрос, и решением было увеличить его на простое число (по модулю max), чтобы получить красивую, казалось бы, случайную последовательность без повторения любых чисел: уникальный код в стиле Tinyurl: потенциальный алгоритм для предотвращения столкновений

26^6 составляет около 300 миллионов.

Проще всего использовать генератор случайных чисел, и, если у вас есть коллизия (то есть, если ваш случайно сгенерированный 6-буквенный идентификатор уже занят), увеличивать, пока у вас не будет свободного идентификатора.

Я имею в виду, конечно, что вы будете получать коллизии довольно рано (около 17 тысяч записей), но приращение, пока у вас не будет свободного идентификатора, будет достаточно быстрым, по крайней мере, до тех пор, пока ваше пространство ключей не начнет насыщаться (около 12 миллионов записей), и к тому времени вы все равно должны перейти на 7-буквенные идентификаторы.

Вам нужен блочный шифр с "Block Space" из 266.

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

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

Другие вопросы по тегам