10

アプリで顧客の請求書を印刷したいと考えています。各請求書には請求書 IDがあります。IDを次のようにしたい:

  • シーケンシャル (最近入力された ID は遅くなります)
  • 32 ビット整数
  • 1 2 3 のように簡単に追跡できないため、販売している商品の数がわからない。

私自身のアイデア:特定の日付と時刻 (例: 1/1/2010 00 AM) からの 数。

これらの数値を生成する方法は他にありますか?

4

10 に答える 10

7

あなたの質問には2つの問題があります。1つは解決可能で、1つはそうではありません(指定した制約により)。

解ける: 推測できない数

1 つ目は非常に単純です。顧客が一連の有効な請求書番号にアクセスできる場合、有効な請求書番号 (または次の有効な請求書番号) を推測するのは難しいはずです。

制約でこれを解決できます。

請求書番号を 2 つの部分に分割します。

  1. 増加する数のシーケンスから取得された 20 ビットのプレフィックス (自然数 0、1、2、...)
  2. ランダムに生成される 10 ビットのサフィックス

これらのスキームでは、約 100 万の有効な請求書番号があります。それらを事前に計算して、データベースに保存できます。請求書番号が表示されたら、それがデータベースにあるかどうかを確認します。そうでない場合は無効です。

数字の配布には SQL シーケンスを使用します。新しい (つまり、未使用の) 請求書番号を発行する場合、シーケンスをインクリメントし、事前に計算されたリスト (値順) から n 番目の番号を発行します。

解決不可: 顧客数の推測

多数の有効な請求書番号を持っている顧客が、あなたが発行した請求書番号の数を推測できないようにしたい場合: これは不可能です。

あなたはいわゆる「ドイツ戦車問題」の変種を持っています。第二次世界大戦中、同盟国はドイツの戦車のギアボックスに印刷されたシリアル番号を使用して、ドイツが生産した戦車の量を推測しました。シリアル番号が隙間なく増加していたため、これは機能しました。

しかし、隙間を空けて数を増やしても、ドイツ戦車問題の解決策は有効です。とても簡単です:

  1. ここで説明する方法を使用して、発行された請求書番号の最大値を推測します
  2. 連続する 2 つの請求書番号の平均差を推測し、この値で数を割ります。
  3. 線形回帰を使用して、安定したデルタ値を取得できます (存在する場合)。

これで、請求書の数の桁数 (200、15000、50 万など) を推測できます。

これは、(理論的には) 2 つの連続する請求書番号の平均値が存在する限り機能します。ほとんどの乱数ジェネレーターはそのような平均値を持つように設計されているため、乱数ジェネレーターを使用している場合でも、これは通常当てはまります。

対抗策があります: 連続する 2 つの数値のギャップに平均値が存在しないことを確認する必要があります。このプロパティを持つ乱数ジェネレーターは非常に簡単に構築できます。

例:

  1. 最後の請求書番号に現在の番号として 1 を加えたものから始めます
  2. 現在の数値に 2 以上の乱数を掛けます。これが新しい現在の番号です。
  3. ランダムなビットを取得する: ビットが 0 の場合、結果は現在の番号になります。それ以外の場合は、手順 2 に戻ります。

これは理論的には機能しますが、すぐに 32 ビットの整数を使い果たしてしまいます。

この問題の実用的な解決策はないと思います。連続する 2 つの番号のギャップには平均値があり (分散はほとんどありません)、発行された番号の量を簡単に推測できます。そうしないと、32 ビットの数値がすぐに不足してしまいます。

Snakeoil (機能しないソリューション)

時間ベースのソリューションは使用しないでください。タイムスタンプは通常、簡単に推測できます (おそらく、ほぼ正確なタイムスタンプが請求書のどこかに印刷されます)。通常、タイムスタンプを使用すると、攻撃者にとって攻撃が容易になりますが、難しくはありません。

安全でない乱数を使用しないでください。ほとんどの乱数ジェネレーターは、暗号的に安全ではありません。それらは通常、統計には良いがセキュリティには悪い数学的特性を持っています (たとえば、予測可能な分布、安定した平均値など)。

于 2013-08-15T13:10:39.877 に答える
5

1 つのソリューションには、排他的 OR (XOR) バイナリ ビットマップが含まれる場合があります。結果関数は可逆であり、 (最下位バイトの最初のビットが 1 に設定されている場合)不連続な数値を生成する可能性があり、実装が非常に簡単です。また、信頼できるシーケンス ジェネレーター (データベースなど) を使用している限り、スレッドの安全性を考慮する必要はありません。

MSDN によると、「オペランドの 1 つだけが true である場合にのみ、[排他的 OR 演算の] 結果が true になります。」逆論理は、等しいオペランドは常に false になることを示しています。

例として、Random.org で 32 ビット シーケンスを生成しました。これです:

11010101111000100101101100111101

この 2 進数は、10 進数で3588381501、16進数で0xD5E25B3Dに変換されます。これをbase keyと呼びましょう。

ここで、 ([base key] XOR [ID])式を使用していくつかの値を生成してみましょう。C# では、暗号化関数は次のようになります。

    public static long FlipMask(long baseKey, long ID)
    {
        return baseKey ^ ID;
    }

次のリストには、生成されたコンテンツが含まれています。その列は次のとおりです。

  • ID
  • ID のバイナリ表現
  • XOR演算後のバイナリ値
  • 最終的な「暗号化された」10 進値

    0 | 000 | 11010101111000100101101100111101 | 3588381501
    1 | 001 | 11010101111000100101101100111100 | 3588381500
    2 | 010 | 11010101111000100101101100111111 | 3588381503
    3 | 011 | 11010101111000100101101100111110 | 3588381502
    4 | 100 | 11010101111000100101101100111001 | 3588381497
    

生成されたキーを逆にして元の値を決定するには、同じ基本キーを使用して同じ XOR 操作を実行するだけです。2 行目の元の値を取得したいとします。

    11010101111000100101101100111101 XOR
    11010101111000100101101100111100 =
    00000000000000000000000000000001

それは確かにあなたの元の値でした。

さて、ステファンは非常に良い点を指摘しました。最初のトピックは非常に重要です。

彼の懸念をカバーするために、最後の、たとえば 8 バイトを純粋にランダムなガベージ ( nonceと呼ばれると思います) に予約することができます。これは、元の ID を暗号化するときに生成し、元に戻すときに無視します。これは、可能なすべての正の整数を 32 ビット (4,294,967,296 ではなく 16,777,216、またはその 1/256) で十分にスライスすることを犠牲にして、セキュリティを大幅に向上させます。

それを行うクラスは次のようになります。

public static class int32crypto
{
    // C# follows ECMA 334v4, so Integer Literals have only two possible forms -
    // decimal and hexadecimal.
    // Original key:               0b11010101111000100101101100111101
    public static long baseKey = 0xD5E25B3D;

    public static long encrypt(long value)
    {
        // First we will extract from our baseKey the bits we'll actually use.
        // We do this with an AND mask, indicating the bits to extract.
        // Remember, we'll ignore the first 8. So the mask must look like this:
        // Significance mask:      0b00000000111111111111111111111111
        long _sigMask = 0x00FFFFFF;

        // sigKey is our baseKey with only the indicated bits still true.
        long _sigKey = _sigMask & baseKey;

        // nonce generation. First security issue, since Random()
        // is time-based on its first iteration. But that's OK for the sake
        // of explanation, and safe for most circunstances.
        // The bits it will occupy are the first eight, like this:
        // OriginalNonce:          0b000000000000000000000000NNNNNNNN
        long _tempNonce = new Random().Next(255);

        // We now shift them to the last byte, like this:
        // finalNonce:             0bNNNNNNNN000000000000000000000000
        _tempNonce = _tempNonce << 0x18;

        // And now we mix both Nonce and sigKey, 'poisoning' the original
        // key, like this:

        long _finalKey = _tempNonce | _sigKey;

        // Phew! Now we apply the final key to the value, and return
        // the encrypted value.

        return _finalKey ^ value;


    }

    public static long decrypt(long value)
    {
        // This is easier than encrypting. We will just ignore the bits
        // we know are used by our nonce.
        long _sigMask = 0x00FFFFFF;
        long _sigKey = _sigMask & baseKey;

        // We will do the same to the informed value:
        long _trueValue = _sigMask & value;

        // Now we decode and return the value:
        return _sigKey ^ _trueValue;

    }

}
于 2013-08-17T04:24:11.383 に答える
2

注文が毎朝 1 人の担当者が処理するまで受信トレイに残っている場合、その担当者が請求書を作成するまでに 16:00 までかかったのを見ると、彼は忙しいという印象を受けます。9:01 の請求書を受け取ると、今日の顧客は自分だけのような気がします。

しかし、注文時に ID を生成しても、タイムスタンプからは何もわかりません。

したがって、2 人の顧客が同時に作成された ID を必要とする衝突はまれであると仮定すると、タイムスタンプが実際に気に入っていると思います。

于 2013-08-16T23:37:27.237 に答える
2

おそらくアイデアは軍から来るのでしょうか? 次のよう なブロックで請求 書

グループ
化 し ます 。第 2 旅団 ---第 1 BN ----A Co ----B Co ---第 2 BN ----A Co ----B Co --第 3 旅団 ---第 1 BN ----A Co ----B Co ---2nd BN ----A Co ----B Co http://boards.straightdope.com/sdmb/showthread.php?t=432978 グループは必ずしもそうである必要はありませんシーケンシャルですが、グループ内の数字はそうです




















アップデート

上記を場所、時間、人などで区分けしたグループと考えてください。例:出品者の仮IDでグループを作成し、10日ごとに変更したり、オフィスやショップごとに変更したりします。

もうひとつアイデアがあって、ちょっと変な言い方をするかもしれませんが... 考えてみるとますます好きです。これらの請求書をカウントダウンしてみませんか? 大きな数字を選んでカウントダウンしてください。カウントアップするとアイテム数をたどりやすいけど、カウントダウン?出発点がどこにあるのか、誰がどのように推測するでしょうか? 実装も簡単です。

于 2013-08-10T06:50:42.120 に答える
1

以下のコードからわかるように、newsequentialid() を使用して連番を生成し、それを [bigint] に変換しています。これにより一貫して 4294967296 の増分が生成されるため、その数値をテーブルの [id] で単純に除算します (ナノ秒などでシードされた rand() である可能性があります)。結果は常に 4294967296 未満の数値になるため、安全に追加でき、次の数値の範囲と重複していないことを確認できます。

ピースキャサリン

declare @generator as table ( 
[id] [bigint], 
[guid] [uniqueidentifier] default( newsequentialid()) not null, 
[converted] as (convert([bigint], convert ([varbinary](8), [guid], 1))) + 10000000000000000000,
[converted_with_randomizer] as (convert([bigint], convert ([varbinary](8), [guid], 1))) + 10000000000000000000 + cast((4294967296 / [id]) as [bigint])
);

insert into @generator ([id])
values      (1), (2), (3), (4), (5), (6), (7), (8), (9), (10);

select [id],
   [guid],
   [converted],
   [converted] - lag([converted],
                     1.0)
                     over (
                         order by [id])                 as [orderly_increment],
   [converted_with_randomizer],
   [converted_with_randomizer] - lag([converted_with_randomizer],
                                     1.0)
                                     over (
                                         order by [id]) as [disorderly_increment]
from   @generator
order  by [converted]; 
于 2013-08-13T20:27:59.937 に答える
0

Na Na は、大きな数を選んでカウントダウンするという正しい考えを持っていると思います。大きな値のシードから始めて、カウントアップまたはカウントダウンしますが、最後のプレースホルダーから始めないでください。他のプレースホルダーのいずれかを使用すると、請求書の数が多いという錯覚が生じます.... とにかく実際にそれを見ている場合.

ここでの唯一の注意点は、数値の最後の X 桁を定期的に変更して、変更の外観を維持することです。

于 2013-08-13T19:19:04.953 に答える
0

次のように構成された読みやすい数値を使用してみませんか

  • 最初の 12 桁は yyyymmddhhmm 形式の日時です (これにより、請求書 ID の順序が保証されます)。
  • 最後の x 桁は注文番号です (この例では 8 桁)。

そのとき得られる数字は 20130814140300000008 のようなものです

次に、最初の 12 桁などの簡単な計算を行います。

(201308141403) * 3 = 603924424209

2 番目の部分 (オリジナル: 00000008) は、次のように難読化できます。

(10001234 - 00000008 * 256) * (minutes + 2) = 49995930  

それを読みやすい数字に戻すのは簡単ですが、顧客がまったく手がかりがないことを知らない限り.

2013 年 8 月 14 日の 14:03 の請求書の内部請求書番号 00000008 の場合、この番号は全体で603924424209-49995930のようになります。

于 2013-08-14T12:21:43.430 に答える