Алгоритм кодов подарочных карт

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

предположим, я принимаю некоторый формат кода-скажем, 10 символов от A до Z для простоты, и я начинаю генерировать ваучеры. Что такое правильный алгоритм для этого?!

мой первый подход-пронумеровать все возможные коды от 0 до 308,915,776, а затем начать генерировать случайные числа в этом диапазоне. У этого, очевидно, есть большая проблема - я должен проверить свое случайное число против всех ранее сгенерированных кодов ваучеров, и если он столкнется с существующим, мне придется отказаться от кода и попробовать другой. По мере накопления данных система будет замедляться. В крайнем случае, когда останется только один код, он будет почти невозможно, чтобы система угадала это правильно.

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

таким образом, это оставляет меня с использованием кодов последовательно. Однако я не хочу угадывать коды ваучеров. Пользователь, который покупает ваучер " AAAAAAAAAY" не должно быть хороших шансов получить другой действительный код, если они вводят "AAAAAAAAAZ".

Я могу перетасовать свой алфавит и мои позиции, чтобы вместо

'ABCDEFGHIJKLMNOPQRSTUVWXYZ' я использую

'LYFZTGKBNDRAPWEOXQHVJSUMIC'

и так, что вместо должности

9 8 7 6 5 4 3 2 1 0 позиции

1 8 0 7 5 4 3 9 2 6

используя эту логику, учитывая код

LNWHDTECMA

следующий код будет

LNEHDTECMA

Это определенно менее угадываемо. Но они все еще только один символ друг от друга, и, учитывая только два из этих ваучеров, вы будете знать, какая позиция увеличивается, и у вас будет 90% шанс получить следующий код в 24 догадках или меньше.

мой "аварийный люк" - это бросить все это и пойти с GUIDs. У них больше характеров, чем я хотел. пользователи должны вводить и содержать похожие символы, такие как I/1 и O/0, но они волшебным образом заставляют все вышеперечисленные головные боли уходить. Тем не менее, мне весело думать об этом, может быть, и тебе тоже. Я бы хотел услышать несколько альтернативных предложений. Что у тебя?

спасибо!

13 ответов


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


используйте N-битный серийный номер R в сочетании с M-битным хэшем H сцепленной пары (R, S), где S-некоторая секретная "соль", которую вы делаете не публикации. Затем кодируйте пару (R,H) буквенно-цифровым способом любым обратимым способом. Если вам нравятся алгоритмы, такие как MD5* или SHA, но количество битов слишком велико, просто возьмите M наименее значимых битов стандартного хэш-алгоритма.

вы можете легко проверить: декодировать буквенно-цифровую кодировку, чтобы вы могли видеть R и H. Затем вычислите H '= hash (R+S) и убедитесь, что H = H'.

edit: R может быть увеличивающимся серийным номером или случайным или любым другим, просто убедитесь, что вы используете каждое значение не более одного раза.

*прежде чем кто-то скажет "MD5 сломан", позвольте мне напомнить вам, что известные слабости MD5-это атаки на столкновения и не атаки прообраза. Кроме того, используя неопубликованное секретное значение salt, вы лишаете злоумышленника возможности проверить механизм безопасности, если он / она не может угадать значение соли. Если вы чувствуете себя параноиком, выберите два значения соли Sprefix и Ssuffix и вычислите хэш сцепленной тройки (Sprefix, R,Ssuffix).


некоторые генераторы случайных чисел имеют интересное свойство: используемые справа они не генерируют повторяющиеся числа в течение длительного времени. Они производят нечто, называемое полное. Используйте один из алгоритмов, описанных там, посейте его, и у вас будет много уникальных чисел,

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


Я бы сказал, чтобы использовать "идеальный хэш" -http://en.wikipedia.org/wiki/Perfect_hash_function в сочетании с 4-значным случайным числом...

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

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


Жемчужины Программирования имеет несколько примеров алгоритмов для генерации наборов случайных чисел, вы должны прочитать его, если вас интересует такая проблема.

книга показывает, что если вы генерируете m случайные числа со значением меньше n, простой подход генерации чисел и выбрасывания дубликатов будет генерировать не более 2m случайные числа, если m < n / 2. Вот он, в C++:

void gensets(int m, int n)
{
    set<int> S;
    set<int>::iterator i;
    while (S.size() < m) {
        int t = bigrand() % n;
        S.insert(t);
    }
    for (i = S.begin(); i != S.end(); ++i)
        cout << *i << "\n";
}

очевидно, если вы беспокоясь о людях, угадывающих значения, вы захотите m значительно меньше, чем n / 2.

есть даже алгоритм на основе набора для генерации m случайные числа меньше!--3--> при каждом значении одинаково вероятно, нет дубликатов, и гарантия не генерировать больше, чем m случайных чисел:

void genfloyd(int m, int n)
{
    set<int> S;
    set<int>::iterator i;
    for (int j = n-m; j < n; j++) {
        int t = bigrand() % (j+1);
        if (S.find(t) == S.end())
            S.insert(t); // t not in S
        else
            S.insert(j); // t in S
    }
    for (i = S.begin(); i != S.end(); ++i)
        cout << *i << "\n";
}

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


Я ответил и на другой вопрос: P

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

В идеале лучшим способом было бы выбрать последовательность достаточно долго, чтобы вы могли безопасно предположить, будут ли дубликаты. Обратите внимание, что, возможно, вопреки интуиции, это происходит чаще, чем вы думаете, из-за проблема рождения.

для например, с 8 символами у вас есть 1785793904896 возможных комбинаций, но если вы создадите только 1,573,415 ваучеров, у вас будет 50% шанс иметь дубликат.

Итак, все зависит от того, сколько вы хотите создать, и максимальной длины кода, с которым вам удобно. Если вы генерируете много, и вы хотите сохранить его коротким, вы должны сохранить те, которые вы ранее сгенерировали, и проверить базу данных на наличие дубликатов.


Это резюме лучших битов всех других ответов. :)

вам нужно создать номера подарочных карт, которые являются:

  • уникальный
  • не угадать

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

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


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

  • Я выбрал солевой префикс из 4 чисел
  • и суффикс из 4 чисел
  • затем основной код 9 числа заменимых
  • затем, используя этот формат sprefix +random_numbers+ssuffix
  • Я немедленно хэширую формат, хранящий его в базе данных
  • запрос может помочь удалить аналогичные коды
  • и суффикс и префикс должны быть изменены, как только вы очень напечатали 9! (362880) раз.

Я думаю, что лучший способ пойти-это то, что предложил Андреас. Но мой ответ касается интересного обсуждения.

вы хотите создать последовательность чисел, которые вместе образуют перестановку S = {1, ..., МАКС.} Один из способов сделать это-взять элементы циклической группы над S. например, числа R = {x modulo p, x^2 modulo p, x^3 modulo p, ..., x^(p-1) modulo p} сформировать циклическую группу над {1, ..., p-1}, предоставленной p является простым и x - это взаимно простой с p. Поэтому, если вы выбираете MAX в качестве простого числа, вы используете эта последовательность.

вы хотите последовательность" трудно взломать". Генератор для достаточно жесткой к трещине последовательности называется псевдослучайным генератором (конечно, вам, вероятно, не нужно это жесткая-для-кряк). Примером является последняя цифра элементов в R выше, при условии p хранится в секрете (я прав?). Но ответ Андреаса уже использует источник (псевдо -) случайных чисел, поэтому его нельзя назвать псевдослучайным генератором.

если вы не интересующиеся псевдослучайными генераторами, они подробно обсуждаются во втором томе известной книги кнута.


на основе ответ Джейсона Орендоффа, Я составил алгоритм для генерации кодов подарочных карт. В принципе, он имеет два 40-битных номера: один из них, чтобы убедиться, что он уникален, а другой, чтобы убедиться, что это трудно угадать.

  • 40-разрядная часть случайных чисел достаточно для 1 в 2^40 шансы гадать;
  • 40-разрядная часть последовательного номера достаточно для 34,8 года уникальности (предполагая, что мы генерируем одну подарочную карту на ms.)

общая 80-разрядная последовательность затем преобразуется в 16-символьную строку с помощью Base32.

import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.codec.binary.Base32;

public class GiftCardUtil {

    private AtomicLong sequence;
    private Random random;

    public GiftCardUtil() {
        // 1325383200000L == 1 Jan 2012
        sequence = new AtomicLong(System.currentTimeMillis() - 1325383200000L);
        random = new SecureRandom();
    }

    public String generateCode() {
        System.out.println(sequence.get());
        byte[] id = new byte[10];
        longTo5ByteArray(sequence.incrementAndGet(), id);
        byte[] rnd = new byte[5];
        random.nextBytes(rnd);
        System.arraycopy(rnd, 0, id, 5, 5);
        return new Base32().encodeAsString(id);
    }

    private void longTo5ByteArray(long l, byte[] b) {
        b[0] = (byte) (l >>> 32);
        b[1] = (byte) (l >>> 24);
        b[2] = (byte) (l >>> 16);
        b[3] = (byte) (l >>> 8);
        b[4] = (byte) (l >>> 0);
    }
}

то, что может эффективно работать, просто использует время создания в ваших интересах. Скажем, последние две цифры года, двухзначный месяц, двухзначный день, двузначный час, двузначные минуты, двузначные секунды, затем перенесите секунды, скажем, на микросекунду. Если дальнейшего запутывания нужные, у них prescrambled (например, MYmdshhdMmYs вместо YYMMddhmmss). Затем измените основание (для pentadecimal, возможно) отвернуться дальше гадать попытки. Это кариес два основных выгоды: 1-Использование даты, включая год, уничтожит любое дублирование, так как одно и то же время не пройдет дважды. Только через сто лет есть риск. Единственное, что может беспокоить, - это возможность создания двух за одну микросекунду, для чего было бы просто запретить создание более одного за раз. Миллисекундная задержка решит проблему.

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

возражение может быть "Подожди! Это 17 цифр (YYMMDDhhmmss.sssss), но выведенный на большую базу впоследствии уменьшит его. Переход к базе 36, используя 10 цифр и 26 букв, означает, что 11-значный код будет охватывать все возможности. Если верхний и Нижний регистры не являются взаимозаменяемыми, данные могут быть сжаты до цели 10 цифр с нулевыми проблемами.


вот это правда:

  • ID = каждый ваучер имеет уникальный (автоматически увеличенный?) ID
  • контрольная сумма = применить n итераций Верхоеф или Луна алгоритм на ID
  • ваучер = база конвертируйте сгенерированную контрольную сумму из базы 10 в базу 36

см. Также этот связанный вопрос SO:идеи для создания небольшого (.


один простой способ сделать этот метод более безопасным-использовать значение ID без автоматического увеличения, одним из вариантов может быть использование ID в качестве последних 6 или 7 цифр метки времени UNIX и вычисление контрольной суммы.


Я второй использование криптографического хэша-взятия битов из MD5 очень просто. Чтобы сделать вещи читабельными, я натолкнулся на следующую идею: возьмите список слов и используйте биты ключа для индексации списка слов. Мой список слов составляет около 100 000 слов, поэтому около 16 бит на слово, что для четырех слов дает 64-битное пространство ключей. Результаты обычно вполне читабельны.

например, криптографическая подпись предыдущего абзаца

камикадзе по freshet mansion отхаркивает

(мой список слов наклонен в сторону большего пространства клавиш; Если вы хотите более короткие фразы, у вас меньше слов.)

Если у вас есть библиотека MD5, эта стратегия очень проста в реализации-я делаю это примерно в 40 строках Lua.