53

クーポンコードを生成したいと思いAYB4ZZ2ます。ただし、使用したクーポンをマークして、そのグローバル番号を制限できるようにしたいと考えていますN. 単純なアプローチは、「一意の英数字コードを生成し、それらをデータベースに入れ、すべてのクーポン操作でデータベース検索を実行する」のようなものです。N

ただし、私が知る限り、指定された数値を事前定義された長さのクーポンのような文字列に変換する関数を見つけることもできます。 MakeCoupon(n)

私が理解している限りMakeCoupon、次の要件を満たす必要があります。

  • 全単射であること。逆MakeNumber(coupon)は効果的に計算可能でなければなりません。

  • の出力はMakeCoupon(n)英数字でなければならず、人間が読める形式と呼べるように、短くて一定の長さでなければなりません。たとえば、ダイジェストはこの要件を満たしません。SHA1

  • 実用的な独自性。MakeCoupon(n)for every naturalの結果は、n <= N完全に一意であるか、たとえばMD5is unique (同じ非常に小さな衝突確率で) と同じ条件で一意である必要があります。

  • (これは定義するのが難しいです) 1つのクーポン コードから残りのすべてのクーポンを列挙する方法は明らかではありませMakeCoupon(n)MakeCoupon(n + 1)

    たとえば、 とが実際には「視覚的に」異なるわけではないため、単にゼロを埋め込んでMakeCoupon(n),出力するだけでは、この要件は満たされません。n000001000002

Q:

次の要件を満たす関数または関数ジェネレータは存在しますか? 検索を試みても[CPAN]CouponCodeにたどり着くだけですが、対応する関数が全単射であるという要件を満たしていません。

4

5 に答える 5

84

基本的に、操作をパーツに分割できます。

  1. どういうわけか最初の番号を「暗号化」してn、2 つの連続した番号が (非常に) 異なる結果をもたらすようにします
  2. ステップ 1 の結果から「人間が読める」コードを作成します。

ステップ 1 では、単純なブロック暗号を使用することをお勧めします (たとえば、任意のラウンド関数を使用したFeistel 暗号)。この質問も参照してください。

Feistel 暗号は複数のラウンドで動作します。各ラウンド中に、いくつかのラウンド関数が入力の半分に適用され、結果がxor残りの半分で編集され、2 つの半分が交換されます。Feistel 暗号の優れた点は、ラウンド関数が双方向である必要がないことです (ラウンド関数への入力は各ラウンド後に変更されずに保持されるため、ラウンド関数の結果は復号化中に再構築できます)。したがって、好きなクレイジーな操作を選択できます:)。また、Feistel 暗号は対称であり、最初の要件を満たします。

C# での短い例

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を満たすこととは別に(関数はtotalであるため、入力が異なると出力が異なり、入力は非公式の定義に従って「完全にスクランブル」されます)、それは独自の逆でもあります(したがって、暗黙的に要件1を満たします)。つまり、入力ドメインのcrypt(crypt(x))==xそれぞれに対して(この実装では)。また、パフォーマンス要件の点で安価です。x0..2^30-1

ステップ 2 では、結果を任意のベースにエンコードします。たとえば、30 ビットの数値をエンコードするには、32 文字のアルファベットの 6 つの「桁」を使用できます (したがって、6*5=30 ビットをエンコードできます)。

C# でのこのステップの例:

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

このアプローチには 2 つの異なる内部「秘密」があることに注意してください。1 つ目はラウンド関数と使用されるラウンド数、2 つ目は暗号化された結果のエンコードに使用するアルファベットです。ただし、示されている実装は、暗号化の意味で決して安全ではないことに注意してください。

また、示されている関数は、可能なすべての 6 文字コード (アルファベット外の文字を含む) が一意の数値を生成するという意味で、全単射関数であることに注意してください。誰かがランダムなコードを入力するのを防ぐには、入力番号に対してある種の制限を定義する必要があります。たとえば、最初の 10.000 番号に対してのみクーポンを発行します。次に、ランダムなクーポン コードが有効になる確率は 10000/2^30=0.00001 になります (正しいクーポン コードを見つけるには、約 50000 回の試行が必要です)。さらに「セキュリティ」が必要な場合は、ビット サイズ/クーポン コードの長さを増やすことができます (以下を参照)。

編集:クーポンコードの長さを変更する

結果のクーポン コードの長さを変更するには、いくつかの計算が必要です。最初の (暗号化) ステップは、ビット数が偶数のビット文字列でのみ機能します (これは、Feistel 暗号が機能するために必要です)。

2 番目のステップでは、特定のアルファベットを使用してエンコードできるビット数は、選択したアルファベットの「サイズ」とクーポン コードの長さに依存します。この「エントロピー」はビット単位で与えられますが、一般に整数ではなく、偶数の整数ではありません。例えば:

30 文字のアルファベットを使用した 5 桁のコードは、30^5 の可能なコードになります。これは、ld(30^5)=24.53ビット/クーポン コードを意味します。

4 桁のコードの場合、簡単な解決策があります。32 文字のアルファベットを指定すると、*ld(32^4)=5*4=20* ビットをエンコードできます。したがって、BITCOUNTを 20に設定し、コードの 2 番目の部分のループを(の代わりに)for まで実行するように変更できます。46

5 桁のコードを生成するのは少しトリッキーで、どういうわけかアルゴリズムを「弱体化」させます: を 24 に設定して、30 文字のアルファベットから 5 桁のコードを生成するだけです (文字列BITCOUNTから 2 文字を削除し、ループを実行させます)。まで)。ALPHABETfor5

しかし、これはすべての可能な 5 桁コードを生成するわけではありません: 24 ビットでは、暗号化段階から 16,777,216 の可能な値しか取得できず、5 桁のコードは 24,300,000 の可能な数字をエンコードできるため、一部の可能なコードは決して生成されません。より具体的には、コードの最後の位置にアルファベットの文字が含まれることはありません。これは、有効なコードのセットを明白な方法で絞り込むため、欠点と見なすことができます。

クーポン コードをデコードするときは、まず関数を実行してからcodeFromCoupon、結果のビット 25 が設定されているかどうかを確認する必要があります。これにより、すぐに拒否できる無効なコードがマークされます。実際には、アルゴリズムのすべての内部を公開することなく、コードの有効性を (クライアント側などで) 迅速にチェックできるため、これは利点でさえあることに注意してください。

ビット 25 が設定されていない場合は、crypt関数を呼び出して元の番号を取得します。

于 2012-08-01T09:20:04.590 に答える
14

私はこの答えに夢中になるかもしれませんが、私は応答する必要があると感じています-多くのつらい経験から来ているので、私が言っていることを聞いてくれることを本当に願っています.

このタスクは学問的に非常に困難であり、ソフトウェア エンジニアは問題解決よりも知性に挑戦する傾向がありますが、可能であれば、これについて何らかの方向性を示す必要があります。世界には、生成されたすべてのエンティティを十分に追跡していない、とにかく成功している小売店はありません。在庫の各部分から、クーポンやギフトカードのすべてに至るまで、彼らはそれらのドアを発送します。誰かがあなたをだますかどうかではなく、いつだかということなので、武器庫に可能なすべてのアイテムがあれば、準備ができています.

それでは、シナリオでクーポンが使用されるプロセスについて説明しましょう。

顧客がクーポンを引き換えると、正面にある種のPOSシステムが表示されますよね? そしてそれは、レジ係がバーコードを正しくスキャンするレジスタに対して、クーポンコードを入力するだけで済むオンラインビジネスでさえあるかもしれません(ここで扱っているのはそれだと思います)?ベンダーとして、有効なクーポン コードを持っていれば何らかの割引を提供すると言っています。たちの目標は元に戻せるクーポン コードを生成することだったので、データベースは必要ありません。そのコードを確認するには、正しく逆にするだけです! つまり、それはただの数学ですよね?はい、いいえ。

はい、そうです、それは単なる数学です。実際、 SSL のクラックも同様の問題です。しかし、SSL で使用される計算は、ここで使用されるものよりも少しだけ複雑であり、鍵はかなり大きいことを誰もが認識していると思います。

特にお金に関しては、誰も破るのに十分気にしないと確信しているある種の計画を考え出すのは賢明ではありません。クーポンコードを使用する人から身を守る必要があるため、実際に解決しようとするべきではない問題を解決しようとして、人生を非常に困難にしています.

したがって、この問題は不必要に複雑であり、このように解決できます。

// 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
  1. 自動インクリメント キーを持つクーポン テーブルを作成します。
  2. そのテーブルに挿入し、自動インクリメント キーを取得します。
  3. その ID を Base64 でエンコードしてクーポン コードにします。
  4. 必要に応じて、その文字列を切り捨てます。
  5. クーポンが挿入された状態で、その文字列をデータベースに保存します。
于 2012-08-07T10:49:03.703 に答える
5

あなたが望むものはFormat-preserving encryptionと呼ばれます。

0..M-1一般性を失うことなく、base 36 でエンコードすることにより、シンボルの文字列ではなく整数について話していると想定できます。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 の質問では、Perl/Python バインディングを備えた C++ ライブラリである Botan のみが言及されていますが、C# は言及されていませ

注意: FPE にはまだ十分に受け入れられている標準がないという事実に加えて、実装にバグがある可能性を考慮する必要があります。多額の資金が必要な場合は、そのリスクと、データベースを回避することによる比較的わずかなメリットを比較検討する必要があります。

于 2012-08-01T11:53:27.793 に答える
3

base-36 数値システムを使用できます。Coupen 出力に 6 文字が必要であるとします。

MakeCoupon の擬似コード

MakeCoupon(n) {

6 などの固定サイズのバイト配列を用意します。すべての値を 0 に初期化します。数値を基数 36 に変換し、「桁」を配列に格納します (整数除算と mod 演算を使用)。数字が 0..9,A..Z から始まると仮定した対応する ASCII コード この変換では、文字列として 6 桁が出力されます。

}

数値を逆に計算することは、この操作の逆です。

これは、許容される文字数が 6 の非常に大きな数 (35^6) で機能します。

于 2012-07-29T11:31:50.410 に答える
2
  • 暗号化機能を選択しますc。c にはいくつかの要件がありますが、ここでは SHA1 を取り上げます。

  • 秘密鍵を選択しますk

クーポンコード生成関数は、 number に対して次のようになりますn

  • n と k を連結"n"+"k"します (これは、パスワード管理ではソルティングとして知られています)
  • 計算 c("n"+"k")
  • SHA1 の結果は 160 ビットです。それらを (たとえば base64 で) ASCII 文字列としてエンコードします。
  • 結果が長すぎる場合 (あなたが言ったように SHA1 の場合)、最初の 10 文字のみを保持するように切り捨て、この文字列に名前を付けますs
  • あなたのクーポン コードは ですprintf "%09d%s" n s。つまり、ゼロで埋められnたものと切り捨てられたハッシュを連結したものsです。

nはい、クーポン コードの番号を推測するのは簡単です(ただし、以下を参照してください)。しかし、別の有効なコードを生成するのは困難です。

あなたの要件は満たされています:

  1. 逆関数を計算するには、コードの最初の 9 桁を読み取るだけです
  2. 長さは常に 19 です (9 桁の n と 10 文字のハッシュ)
  3. 最初の 9 桁が一意であるため、一意です。最後の10文字も、高確率で。
  4. SHA1 を使用したと推測されたとしても、ハッシュを生成する方法は明らかではありません。


いくつかのコメント:

  • n読み取りが明白すぎることが心配な場合は、base64 エンコーディングのように軽く難読化し、コード内でnとの文字を交互にすることができますs
  • 10 億を超えるコードは必要ないため、n を 9 桁で表示する必要はないと想定していますが、もちろん、パラメータ 9 と 10 を希望のクーポン コードの長さに調整することもできます。
  • SHA1 は単なるオプションです。秘密鍵の暗号化などの別の暗号化機能を使用することもできますが、切り捨てられた場合や平文が提供された場合でも、この機能が強力であることを確認する必要があります。
  • これはコード長の点で最適ではありませんが、単純さと広く利用可能なライブラリという利点があります。
于 2012-08-01T10:55:14.147 に答える