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

Создание кода купона

Я хотел бы генерировать коды купонов, например. AYB4ZZ2. Тем не менее, я также хотел бы отметить отметки используемых купонов и ограничить их глобальное число, скажем N. Наивный подход был бы чем-то вроде "генерировать N уникальные буквенно-цифровые коды, помещать их в базу данных и выполнять поиск db при каждой операции купона".

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

Насколько я понимаю, MakeCoupon должен заполнить следующие требования:

  • Быть биективным. Он обратный MakeNumber(coupon) должен быть эффективно вычислимым.

  • Вывод для MakeCoupon(n) должен быть буквенно-цифровым и иметь небольшую длину и константу, чтобы его можно было называть удобочитаемым человеком. Например. SHA1 digest не будет передавать это требование.

  • Практическая уникальность. Результаты MakeCoupon(n) для каждого естественного n <= N должны быть полностью уникальными или уникальными в тех же терминах, что, например, MD5 является уникальным (с той же чрезвычайно малой вероятностью столкновения).

  • (это сложно определить). Не должно быть очевидным, как перечислить все оставшиеся купоны из одного купонного кода - скажем, MakeCoupon(n) и MakeCoupon(n + 1) должны визуально отличаться.

    например. MakeCoupon(n),, который просто выводит N, заполненный нулями, не сможет выполнить это требование, потому что 000001 и 000002 фактически не отличаются "визуально".

Q

Существует ли какой-либо генератор функций или функций, который выполняет следующие требования? Мои попытки поиска приводят меня только к [CPAN] CouponCode,, но это не полностью соответствует требованию биективности соответствующей функции.

4b9b3361

Ответ 1

В принципе, вы можете разделить операцию на части:

  • Как-то "зашифровать" ваш начальный номер n, так что два последовательных номера дают (очень) разные результаты
  • Постройте свой "удобочитаемый" код из результата шага 1

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

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

Краткий пример в С#

const int BITCOUNT = 30;
const int BITMASK = (1 << BITCOUNT/2) - 1;

static uint roundFunction(uint number) {
  return (((number ^ 47894) + 25) << 1) & BITMASK;
}

static uint crypt(uint number) {
  uint left = number >> (BITCOUNT/2);
  uint right = number & BITMASK;
  for (int round = 0; round < 10; ++round) {
    left = left ^ roundFunction(right);
    uint temp = left; left = right; right = temp;
  }
  return left | (right << (BITCOUNT/2));
}

(Обратите внимание, что после последнего раунда нет подкачки, в коде замена происходит просто отменить в построении результата)

Помимо выполнения ваших требований 3 и 4 (функция является общей, поэтому для разных входов вы получаете разные выходы, а вход "полностью скремблирован" в соответствии с вашим неформальным определением), он также является собственным инверсным (таким образом, неявно выполняющим требование 1), т.е. crypt(crypt(x))==x для каждого x во входном домене (0..2^30-1 в этой реализации). Также это дешево с точки зрения требований к производительности.

Для шага 2 просто запишите результат в какую-то базу по вашему выбору. Например, чтобы закодировать 30-битное число, вы можете использовать 6 "цифр" алфавита из 32 символов (так что вы можете кодировать 6 * 5 = 30 бит).

Пример этого шага в С#:

const string ALPHABET= "AG8FOLE2WVTCPY5ZH3NIUDBXSMQK7946";
static string couponCode(uint number) {
  StringBuilder b = new StringBuilder();
  for (int i=0; i<6; ++i) {
    b.Append(ALPHABET[(int)number&((1 << 5)-1)]);
    number = number >> 5;
  }
  return b.ToString();
}
static uint codeFromCoupon(string coupon) {
  uint n = 0;
  for (int i = 0; i < 6; ++i)
    n = n | (((uint)ALPHABET.IndexOf(coupon[i])) << (5 * i));
  return n;
}

Для входов 0 - 9 это дает следующие коды купонов

0 => 5VZNKB
1 => HL766Z
2 => TMGSEY
3 => P28L4W
4 => EM5EWD
5 => WIACCZ
6 => 8DEPDA
7 => OQE33A
8 => 4SEQ5A
9 => AVAXS5

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

Также обратите внимание, что показанная функция является полной биективной функцией, в том смысле, что каждый возможный 6-символьный код (с символами из вашего алфавита) даст уникальный номер. Чтобы кто-либо не вводил какой-либо случайный код, вы должны определить какие-то ограничения на входном номере. Например. выпускают только купоны для первых 10.000 номеров. Тогда вероятность того, что какой-либо случайный код купона будет действительным, будет 10000/2 ^ 30 = 0,00001 (для этого потребуется около 50000 попыток найти правильный код купона). Если вам нужно больше "безопасности", вы можете просто увеличить размер бит/размер купона (см. Ниже).

РЕДАКТИРОВАТЬ: изменить длину кода купона

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

На втором этапе количество битов, которые могут быть закодированы с использованием заданного алфавита, зависит от "размера" выбранного алфавита и длины кода купона. Эта "энтропия", заданная в битах, является, вообще говоря, не целочисленным числом, а тем более четным целым числом. Например:

5-значный код с использованием 30-символьного алфавита дает 30 ^ 5 возможных кодов, что означает ld (30 ^ 5) = 24,53 бит/код купона.

Для четырехзначного кода существует простое решение: с учетом 32-символьного алфавита вы можете кодировать * ld (32 ^ 4) = 5 * 4 = 20 * Bits. Таким образом, вы можете просто установить BITCOUNT на 20 и изменить цикл for во второй части кода для запуска до 4 (вместо 6)

Генерация пятизначного кода немного сложнее и как-то "ослабляет" алгоритм: вы можете установить BITCOUNT в 24 и просто сгенерировать 5-значный код из алфавита из 30 символов (удалить два символа из ALPHABET и пробег цикла for до 5).

Но это не сгенерирует все возможные 5-значные коды: с 24 битами вы можете получить только 16 777 216 возможных значений с этапа шифрования, 5-значные коды могут кодировать 24 300 000 возможных номеров, поэтому некоторые возможные коды никогда не будут генерироваться. Более конкретно, последняя позиция кода никогда не будет содержать некоторые символы алфавита. Это можно рассматривать как недостаток, поскольку он сужает набор действительных кодов очевидным образом.

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

Если бит 25 не установлен, вы вызовете функцию crypt и получите исходный номер.

Ответ 2

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

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

Теперь давайте поговорим о процессе, в котором купон используется в вашем сценарии.

Когда клиент выкупит купон, будет какая-то система POS спереди справа? И это может быть даже онлайн-бизнес, где они могут просто ввести свой код купона против регистра, где кассир сканирует штрих-код справа (я предполагаю, что мы имеем дело здесь)? И теперь, как продавец, вы говорите, что если у вас есть действующий код купона, я собираюсь дать вам какую-то скидку и, потому что наша цель заключалась в создании кодов купонов, которые были обратимы нам не нужна база данных для проверки этого кода, мы можем просто отменить его правильно! Я имею в виду, что это просто математика? Ну, да и нет.

Да, вы правы, это просто математика. На самом деле, это также проблема, потому что это cracking SSL. Но я собираюсь предположить, что все мы понимаем, что математика, используемая в SSL, немного сложнее, чем все, что используется здесь и. существенно. p >

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

Следовательно, эта проблема излишне сложна и может быть решена так.

// insert a record into the database for the coupon
// thus generating an auto-incrementing key
var id = [some code to insert into database and get back the key]

// base64 encode the resulting key value
var couponCode = Convert.ToBase64String(id);

// truncate the coupon code if you like

// update the database with the coupon code
  • Создайте таблицу купонов с ключом автоматического увеличения.
  • Вставьте в эту таблицу и верните автоинкрементный ключ.
  • Base64 кодирует этот идентификатор в код купона.
  • Обрезайте эту строку, если хотите.
  • Сохраните эту строку в базе данных с только что вставленным купоном.

Ответ 3

То, что вы хотите, называется Шифрование, сохраняющее формат.

Без ограничения общности, кодируя в базе 36, мы можем предположить, что речь идет о целых числах в 0..M-1, а не о строках символов. M, вероятно, должна быть мощностью 2.

После выбора секретного ключа и указания M FPE дает псевдослучайную перестановку 0..M-1 encrypt вместе с ее инверсным decrypt.

string GenerateCoupon(int n) {
    Debug.Assert(0 <= n && n < N);
    return Base36.Encode(encrypt(n));
}

boolean IsCoupon(string code) {
    return decrypt(Base36.Decode(code)) < N;
}

Если ваш FPE защищен, эта схема защищена: ни один злоумышленник не может генерировать другие коды купонов с вероятностью выше O (N/M), учитывая знание сколь угодно большого количества купонов, даже если ему удастся угадать номер, связанный с каждым купоном что он знает.

Это по-прежнему относительно новое поле, поэтому реализовано несколько таких схем шифрования. В этом вопросе crypto.SE упоминается Botan, библиотека С++ с Perl/Python, но не С#.

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

Ответ 4

Вы можете использовать базовую систему номер 36. Предположим, что вы хотите получить 6 символов в выводе coupen.

псевдокод для MakeCoupon

MakeCoupon (п) {

Имейте массив байтов фиксированного размера, скажем 6. Инициализируйте все значения до 0. преобразуйте число в базу - 36 и сохраните "цифры" в массиве (с использованием операций с целым делением и модулем) Теперь, для каждой "цифры" найдите соответствующий код ascii, цифры начинаются с 0..9, A..Z С этим контуром выведите шесть цифр в виде строки.

}

Теперь вычисление числа назад - это обратная операция.

Это будет работать с очень большими числами (35 ^ 6) с 6 допустимыми символами.

Ответ 5

  • Выберите криптографическую функцию c. Есть несколько требований к c, но теперь давайте возьмем SHA1.

  • выберите секретный ключ k.

Функция генерации кода купона может быть для номера n:

  • concatenate n и k как "n"+"k" (это известно как соление в управлении паролями)
  • вычислить c ( "n" + "k" )
  • результат SHA1 составляет 160 бит, кодирует их (например, с base64) как строку ASCII
  • если результат слишком длинный (как вы сказали, это относится к SHA1), усечь его, чтобы сохранить только первые 10 букв и называть эту строку s
  • ваш код купона printf "%09d%s" n s, то есть конкатенация с нулевым запасом n и усеченный хеш s.

Да, тривиально угадывать n номер купонного кода (но см. ниже). Но сложно создать еще один действительный код.

Ваши требования удовлетворяются:

  • Чтобы вычислить обратную функцию, просто прочитайте первые 9 цифр кода
  • Длина всегда 19 (9 цифр из n, плюс 10 букв хэша)
  • Это уникально, поскольку первые 9 цифр уникальны. Последние 10 символов тоже с большой вероятностью.
  • Непонятно, как генерировать хеш, даже если предположить, что вы использовали SHA1.


Некоторые комментарии:

  • Если вы обеспокоены тем, что чтение n слишком очевидно, вы можете слегка запутать его, например, кодировку base64, и чередовать в коде символы n и s.
  • Я предполагаю, что вам не понадобится более миллиарда кодов, поэтому печать n на 9 цифр, но вы можете, конечно, настроить параметры 9 и 10 на желаемую длину кода купона.
  • SHA1 - это просто вариант, вы можете использовать другую криптографическую функцию, такую ​​как шифрование с закрытым ключом, но вам нужно проверить, что эта функция остается сильной при усечении и когда предоставляется чистый текст.
  • Это не оптимально по длине кода, но имеет преимущество простоты и широко доступных библиотек.