Карта, увеличивающая целочисленный диапазон до шестизначной базы 26 макс, но непредсказуемо

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

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

Ответ 1

Почему бы просто не перетасовать бит вокруг в определенном порядке до преобразования в значение базы 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)

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

   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 (без изменений) и, вероятно, лучше всего, если некоторые младшие биты на входе получают в целом более высокие биты на выходе. Другими словами, вы можете создать сопоставление вручную. Фактически, приличное сопоставление будет просто отменять порядок бит. (Это то, что я сделал для вывода образца выше.)

Ответ 2

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

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

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

Кажется, что можно сопоставить состояния LFSR с ординалами (т.е. n-е состояние LFSR равно x), но там patent на нем... Но вы все равно хотите идти в обратном направлении.

Ответ 3

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

Ответ 4

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

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

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


Я откровенно дважды цитирую ссылку Jeff Coding Horror, чтобы подчеркнуть.

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

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

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

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


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

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

Ответ 5

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

Ответ 6

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

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

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

Ответ 7

Вам нужен блок-шифр с "блочным пространством" из 26 6.

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

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

Ответ 8

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