IDを難読化する


85

整数IDを別の整数に暗号化/難読化する方法を探しています。もっと正確に言えば、私は関数が必要なint F(int x)ので、

  • x <-> F(x)は1対1の対応です(x!= yの場合、F(x)!= F(y))
  • F(x)が与えられると、xを見つけるのは簡単です-したがって、Fはハッシュ関数ではありません
  • xとF(x)が与えられると、F(y)を見つけるのは困難/不可能であり、次のようなものx ^ 0x1234は機能しません

明確にするために、私は強力な暗号化ソリューションを探していません。それは難読化だけです。以下のようなURLを使用したWebアプリケーションを想像しexample.com/profile/1example.com/profile/2自身が秘密でないなどのプロファイルを、私は、私のようなものの後ろにそれらを隠すというと思いますので、すべてのプロファイルを次々にフェッチ/ビューにカジュアル覗きを防ぐしたいexample.com/profile/23423example.com/profile/80980234などが、データベースに保存されたトークンは非常に簡単に仕事をすることができます、これに利用できるいくつかの簡単な数学があるかどうか私は興味があります。

私が明確にしなかった重要な要件の1つは、結果が「ランダム」に見える必要があることです。つまり、シーケンスが与えられた場合x,x+1,...,x+nF(x),F(x+1)...F(x+n)いかなる種類の進行も形成しないようにする必要があります。


int F(int x)は要件ですか、それともint [2] F(int x)でしょうか?
Eugen Rieck 2011

@Eugen Rieck、理想的には、私は数範囲内であると、xおよびF(X)たい
ゲオルグ

@ toon81、はい機能は保持されます秘密
ゲオルク・

トークンなしで行きたいと言ったので、それはどんな種類のルックアップテーブルも避けたいという意味ですか?
ダニエルモシュモンドール2011

16
男、この質問は完全に述べられており、まさに私が探しているものです。良くやった。
snekse 2013年

回答:


39

2つまたは3つの簡単な方法を組み合わせて難読化します。

  • XOR
  • 個々のビットをシャッフルします
  • モジュラー表現に変換する(D.Knuth、Vol。2、Chapter 4.3.2)
  • 各サブセット(サブセットのパリティビット)で32(または64)の重複するビットのサブセットとXORビットを選択します
  • 可変長の数値システムで表し、数字をシャッフルします
  • 奇数の整数の組を選択xし、y互いに(モジュロ2の逆数であること32)、その後、乗算によってx難読化すると乗算することによってy復元する、全ての乗算はモジュロ2である32(ソース:エリックによって「逆数の実用」リッパート

可変長の数値システム方式は、それ自体では「進行」要件に準拠しません。それは常に短い等差数列を生成します。しかし、他の方法と組み合わせると、良い結果が得られます。

モジュラー表現法についても同じことが言えます。

これらの3つのメソッドのC ++コード例を次に示します。シャッフルビットの例では、予測できないようにいくつかの異なるマスクと距離を使用する場合があります。他の2つの例は、少数の場合に適しています(アイデアを与えるためだけです)。これらは、すべての整数値を適切に難読化するように拡張する必要があります。

// *** Numberic system base: (4, 3, 5) -> (5, 3, 4)
// In real life all the bases multiplied should be near 2^32
unsigned y = x/15 + ((x/5)%3)*4 + (x%5)*12; // obfuscate
unsigned z = y/12 + ((y/4)%3)*5 + (y%4)*15; // restore

// *** Shuffle bits (method used here is described in D.Knuth's vol.4a chapter 7.1.3)
const unsigned mask1 = 0x00550055; const unsigned d1 = 7;
const unsigned mask2 = 0x0000cccc; const unsigned d2 = 14;

// Obfuscate
unsigned t = (x ^ (x >> d1)) & mask1;
unsigned u = x ^ t ^ (t << d1);
t = (u ^ (u  >> d2)) & mask2;
y = u ^ t ^ (t << d2);

// Restore
t = (y ^ (y >> d2)) & mask2;
u = y ^ t ^ (t << d2);
t = (u ^ (u >> d1)) & mask1;
z = u ^ t ^ (t << d1);

// *** Subset parity
t = (x ^ (x >> 1)) & 0x44444444;
u = (x ^ (x << 2)) & 0xcccccccc;
y = ((x & 0x88888888) >> 3) | (t >> 1) | u; // obfuscate

t = ((y & 0x11111111) << 3) | (((y & 0x11111111) << 2) ^ ((y & 0x22222222) << 1));
z = t | ((t >> 2) ^ ((y >> 2) & 0x33333333)); // restore

ご回答ありがとうございます。いくつかの擬似コードの例を提供できれば、それは素晴らしいことです。
georg 2011

3
@ thg435擬似コードの代わりにC ++を使用しました。テストされていない例を挙げたくありませんでした。
Evgeny Kluev 2011

1
上記の数値システムの基本コードをx = 99で試してみると、z = 44になります。
ハーベイ

@Harvey:リバーシブル難読化ツールを入手するには、すべてのベースの積を難読化する数よりも大きくする必要があります。この例では3 * 4 * 5 = 60であるため、99などの大きな数値は必ずしも同じ値に復元されるとは限りません。
Evgeny Kluev 2013年

1
@Harvey:また、すべてのベースの積を小さくして2 ^ 32に非常に近い値にし、小さなテーブルを使用して残りの値を難読化することもできます。この場合、すべてが32ビットの数値のままです。
Evgeny Kluev 2013年

8

あなたは、変換が可逆的であり、明白ではないことを望んでいます。これは、特定の範囲の数値を取得し、同じ範囲の異なる数値を生成する暗号化のように聞こえます。範囲が64ビット数の場合は、DESを使用してください。範囲が128ビット数の場合は、AESを使用してください。別の範囲が必要な場合は、おそらくHasty Pudding暗号を使用することをお勧めします。これは、さまざまなブロックサイズや、100,000〜999,999などのブロックにうまく収まらない数値範囲に対応するように設計されています。


興味深いことですが、1)十分にテストされておらず、2)理解しにくいために十分にテストされていない暗号を実装するように誰かに依頼するのは少し難しいかもしれません:)
Maarten Bodewes 2011

ありがとう!私はそれをできるだけシンプルにしようとしています。
georg 2011

Hasty Puddingの実装が見つからない場合(許可されているサイズの1つのみが必要)、単純な4ラウンドFeistel暗号(en.wikipedia.org/wiki/Feistel_cipher)を偶数のブロックサイズで簡単に実装できます。Hasty Puddingの場合と同様に、出力が正しい範囲になるまで暗号化を続けます。安全ではありませんが、難読化するには十分です。
rossum 2011

NSAは、32ビットおよび48ビットのブロックサイズを含むバージョンを含むSpeck暗号をリリースしました。これは、これらのサイズで数値を難読化する場合にも役立つ場合があります。特に32ビットバージョンが役立つ可能性があります。
rossum 2016年

5

難読化は、セキュリティの観点からは実際には十分ではありません。

ただし、カジュアルな見物人を阻止しようとしている場合は、次の2つの方法を組み合わせることをお勧めします。

  • それらを一緒に排他的論理和することによってIDと組み合わせる秘密鍵
  • キーが適用される前と後の両方でビットを特定の量だけ回転させる

次に例を示します(疑似コードを使用)。

  def F(x)
    x = x XOR 31415927       # XOR x with a secret key
    x = rotl(x, 5)           # rotate the bits left 5 times
    x = x XOR 31415927       # XOR x with a secret key again
    x = rotr(x, 5)           # rotate the bits right 5 times
    x = x XOR 31415927       # XOR x with a secret key again
    return x                 # return the value
  end

私はそれをテストしていませんが、これは可逆的であり、高速であり、方法を簡単に引き出すことはできないと思います。


定数mod2 ^ 32の追加もあります(ビットの回転がrot13を思い出させたため、誰もが簡単にリバーシブルな機能を使用できます)。
ccoakley 2011

それは本当にそうreturn x XOR rotr(31415927, 5)ですよね?最後のxorは最初のxorを元に戻し、回転は互いに元に戻します。もちろん、リバーシブル操作のチェーンもリバーシブルであるため、その条件を満たすことができます。
ハロルド

私はいくつかの簡単なテストを実行しましたが、結果は期待どおりであることに満足しています。ccoakleyが言及しているように、rot5の代わりにrot13を使用でき、任意の回転が機能し(警告:0> rot>整数サイズ)、別のキーと見なすことができます。彼が示唆するモジュラスのように、そしてハロルドが述べたようにそれらが可逆的である限り、あなたがここに投げ込むことができる他のものがあります。
IAmNaN 2011

1
申し訳ありませんが、@haroldはほとんどが正しいです-あなたの全体の機能は同等ですx = x XOR F(0)x = x XOR 3087989491、またはx = x XOR rotr(31415927, 5)。最初と最後のxorは互いに否定し合うため、ビットシフトされた入力をキーで排他的論理和するか、同等に、入力をビットシフトされたキーで排他的論理和します。これは、ステージごとに異なるキーを使用した場合でも当てはまることに注意してください。すべてのキーを1つのキーに合成して、プレーンテキストでxorすることができます。
ニックジョンソン

2
さらに悪いことに、一定のオフセットで回転するチェーンと、定数を持つxorを1つの回転と1つのxorに凝縮できることを証明するのは非常に簡単です。互いに結合した後の2つの回転(オフセットを追加)、互いに結合した後の2つのxor(xorと2つの定数のxor)、およびxor / rotペアをrot / xorに交換するには、同じ回転をxorの定数。
ハロルド2011


3

このスレッドのアイデアのいくつかを使用して、いくつかのJSコードを作成しました。

const BITS = 32n;
const MAX = 4294967295n;
const COPRIME = 65521n;
const INVERSE = 2166657316n;
const ROT = 6n;
const XOR1 = 10296065n; 
const XOR2 = 2426476569n;


function rotRight(n, bits, size) {
    const mask = (1n << bits) - 1n;
    // console.log('mask',mask.toString(2).padStart(Number(size),'0'));
    const left = n & mask;
    const right = n >> bits;
    return (left << (size - bits)) | right;
}

const pipe = fns => fns.reduce((f, g) => (...args) => g(f(...args)));

function build(...fns) {
    const enc = fns.map(f => Array.isArray(f) ? f[0] : f);
    const dec = fns.map(f => Array.isArray(f) ? f[1] : f).reverse();

    return [
        pipe(enc),
        pipe(dec),
    ]
}

[exports.encode, exports.decode] = build(
    [BigInt, Number],
    [i => (i * COPRIME) % MAX, i => (i * INVERSE) % MAX],
    x => x ^ XOR1,
    [x => rotRight(x, ROT, BITS), x => rotRight(x, BITS-ROT, BITS)],
    x => x ^ XOR2,
);

次のような素晴らしい結果が得られます。

1 1352888202n 1 'mdh37u'
2 480471946n 2 '7y26iy'
3 3634587530n 3 '1o3xtoq'
4 2225300362n 4 '10svwqy'
5 1084456843n 5 'hxno97'
6 212040587n 6 '3i8rkb'
7 3366156171n 7 '1jo4eq3'
8 3030610827n 8 '1e4cia3'
9 1889750920n 9 'v93x54'
10 1017334664n 10 'gtp0g8'
11 4171450248n 11 '1wzknm0'
12 2762163080n 12 '19oiqo8'
13 1621319561n 13 'qtai6h'
14 748903305n 14 'cdvlhl'
15 3903018889n 15 '1sjr8nd'
16 3567473545n 16 '1mzzc7d'
17 2426613641n 17 '144qr2h'
18 1554197390n 18 'ppbudq'
19 413345678n 19 '6u3fke'
20 3299025806n 20 '1ik5klq'
21 2158182286n 21 'zoxc3y'
22 1285766031n 22 'l9iff3'
23 144914319n 23 '2ea0lr'
24 4104336271n 24 '1vvm64v'
25 2963476367n 25 '1d0dkzz'
26 2091060108n 26 'ykyob0'
27 950208396n 27 'fpq9ho'
28 3835888524n 28 '1rfsej0'
29 2695045004n 29 '18kk618'
30 1822628749n 30 'u559cd'
31 681777037n 31 'b9wuj1'
32 346231693n 32 '5q4y31'

テスト:

  const {encode,decode} = require('./obfuscate')

  for(let i = 1; i <= 1000; ++i) {
        const j = encode(i);
        const k = decode(j);
        console.log(i, j, k, j.toString(36));
   }

XOR1XOR2は0からMAX。までの単なる乱数です。MAXです2**32-1; これは、最高のIDになると思うものに設定する必要があります。

COPRIME互いに素な数ですw / MAX。私が考える素数自体は(自身の倍数を除く)他のすべての番号と互いに素です。

INVERSE理解するのが難しいものです。これらのブログ投稿は正解ではありませんが、WolframAlphaはそれを理解することができます。基本的には、の方程式(COPRIME * x) % MAX = 1を解くだけですx

このbuild関数は、これらのエンコード/デコードパイプラインを簡単に作成できるようにするために作成したものです。[encode, decode]ペアで必要な数の操作をフィードできます。これらの関数は等しく、反対である必要があります。XORあなたがそこにペアを必要としないので、機能は独自の賛辞です。


ここに別の楽しい革命があります:

function mixHalves(n) {
    const mask = 2n**12n-1n;
    const right = n & mask;
    const left = n >> 12n;
    const mix = left ^ right;
    return (mix << 12n) | right;
}

(24ビット整数を想定しています-他のサイズの数値を変更するだけです)


1
かっこいい、共有してくれてありがとう!ところで、「32n」とは何ですか?これまで見たことがない。
ゲオルク

1
nBigIntsの番号の接尾辞です。これは、非常に大きな数を処理できる新しいJS機能です。中間値の1つが一時的に超過Number.MAX_SAFE_INTEGERして精度が低下する可能性がある非常に大きな数値を乗算しているため、これを使用する必要がありました。
MPEN

2

IDのビットを破壊しないようなことは何でもしてください。例えば:

  • 値を回転します
  • ルックアップを使用して、値の特定の部分を置き換えます
  • 何らかの値を持つxor
  • スワップビット
  • バイトを交換する
  • 値全体をミラーリングする
  • 値の一部をミラーリングする
  • ... 想像力を使って

復号化の場合は、すべてを逆の順序で実行します。

いくつかの興味深い値を「暗号化」するプログラムを作成し、それらを調べられるテーブルに配置します。同じプログラムで、システムに必要なすべての値のセットを使用して暗号化/復号化ルーチンをテストします。

あなたの番号があなたにきちんと壊れているように見えるまで、ルーチンに上記のリストに何かを追加してください。

何か他のもののために、コピーの入手ブックを


あなたが説明するのは、ブロック暗号の構成要素です。独自に発明するよりも、既存のものを使用する方が理にかなっています。
ニックジョンソン

@NickJohnson知っています、私の投稿の最後の行にあるリンクをクリックしましたか?
ダニエルモシュモンドール2011

rotl / xorの組み合わせをまとめることができず、十分に「ランダム」に見える結果が得られました(更新を参照)。ポインタはありますか?
georg 2011

@DanielMošmondorあなたが何にリンクしているのかは知っていますが、既存のものを使用する方がはるかに理にかなっているのに、最初に彼が自分で何かを作成することを提案しているという事実は変わりませんか?
ニックジョンソン

@NickJohnsonは明らかに、OPは既存の暗号を使用したくないので、新しいAPIを学習したい、または学習したくないのです。私は完全にそれに関係することができます。
ダニエルモシュモンドール2011

2

ブロック暗号を使用した安全な順列に関する記事を書きました。これは、前述の要件を満たす必要があります。

ただし、識別子を推測するのが難しい場合は、最初にそれらを使用する必要があることをお勧めします。UUIDを生成し、最初にそれらをレコードの主キーとして使用します。できる必要はありません。 '実際の' IDとの間で変換します。


2
@ thg435このアプローチに興味がある場合、有用な検索用語は「フォーマット保存暗号化」です。ウィキペディアのページでは、ニックの記事で言及されているBlack / Rogawayの論文と、最近の開発について説明しています。私はあなたがしていることに似た何かのためにFPEを首尾よく使用しました。私の場合、いくつかの軽い妥当性チェックに使用したIDの他にいくつかのビットを追加しましたが。
Paul Du Bois 2013年

1

必要な「ハード」、高速、または使用するメモリの量がわかりません。メモリの制約がない場合は、すべての整数のリストを作成し、それらをシャッフルして、そのリストをマッピングとして使用できます。ただし、4バイト整数の場合でも、大量のメモリが必要になります。

ただし、これを小さくすることもできるため、すべての整数をマッピングする代わりに、2(または最悪の場合1)バイトのみをマッピングし、これを整数の各グループに適用します。したがって、2バイトを使用すると、整数は(group1)(group2)になり、ランダムマップを介して各グループをマップします。ただし、group2のみを変更した場合、group1のマッピングは同じままになることを意味します。これは、各グループに異なるビットをマッピングすることで「修正」できます。

したがって、*(group2)は(ビット14,12,10,8,6,4,2,0)になる可能性があるため、1を追加するとgroup1group2の両方が変更されます。

それでも、これは隠すことによるセキュリティにすぎません。関数に数値を入力できる人は誰でも(関数を秘密にしていても)かなり簡単に理解できます。


システムの制約によっては、これはおそらく機能しません。F(x)をxに戻すことができる場合は、順列を使用できるようにする必要があり、そこからF(y)を簡単に計算できます。任意のy。
templatetypedef

@templatetypedef私が言ったように、これは隠すことによるセキュリティだけです。順列を知る必要がありますが、順列を「キー」として見ることができます。ここでの最大の問題は、OPが、暗号化されたメッセージが同じセットに収まり、セット内のすべてのメッセージに有効である1つのセット(小さなメッセージ)内のすべてのメッセージを暗号化できるようにしたいように見えることです。
ロジャーLindsjö

ありがとう。ルックアップテーブルを避けようとしています。
georg 2011

1

アプリケーションで使用する秘密対称鍵を生成し、それを使用して整数を暗号化します。これは、最も難しい#3を含む3つの要件すべてを満たします。スキームを破るには、キーを推測する必要があります。


thg435は整数から整数を要求しました(そして私が理解している限り、それはすべての整数で機能するはずです)。これらのプロパティを持つ秘密鍵アルゴリズムを提案できますか?
ロジャーLindsjö

1

ここで説明しているのは、一方向性関数の反対のようです。反転するのは簡単ですが、適用するのは非常に困難です。1つのオプションは、標準の既製の公開鍵暗号化アルゴリズムを使用することです。このアルゴリズムでは、秘密を保持する(秘密のランダムに選択された)公開鍵と、世界と共有する秘密鍵を修正します。そうすれば、関数F(x)は公開鍵を使用したxの暗号化になります。次に、秘密復号化キーを使用して、F(x)を簡単に復号化してxに戻すことができます。ここでは、公開鍵と秘密鍵の役割が逆になっていることに注意してください。すべての人に秘密鍵を渡して、関数を復号化できるようにしますが、サーバーでは公開鍵を秘密にしておきます。そのように:

  1. 関数は全単射なので、可逆です。
  2. F(x)が与えられると、xは効率的に計算可能です。
  3. xとF(x)が与えられた場合、yからF(y)を計算することは非常に困難です。これは、公開鍵がないと(暗号的に強力な暗号化スキームを使用していると仮定)、たとえ秘密であってもデータを暗号化する実行可能な方法がないためです。復号化キーは既知です。

これには多くの利点があります。まず、RSAのような確立されたアルゴリズムを使用すれば、偶発的な不安を心配する必要がないため、暗号システムは安全であると安心できます。第二に、これを行うためのライブラリがすでに存在するため、コードを作成する必要がなく、サイドチャネル攻撃の影響を受けません。最後に、誰もが実際にF(x)を計算できなくても、誰でもF(x)に移動して反転できるようにすることができます。

詳細-ここでは、標準のint型を使用するだけではいけません。64ビット整数の場合でも、組み合わせが非常に少ないため、攻撃者は、キーがなくても、一部のyの暗号化F(y)が見つかるまで、すべてをブルートフォースで反転しようとします。サイエンスフィクション攻撃でさえこれをブルートフォースすることはできないので、512ビット値のようなものを使用することをお勧めします。

お役に立てれば!


しかし、thg435は、小さなメッセージセット(4バイトメッセージ)を同じメッセージセットに暗号化できる暗号化を要求しているようであり、暗号化はすべてのメッセージに対して機能するはずです。
ロジャーLindsjö

ご回答ありがとうございます。本格的な暗号化フレームワークを使用するのがおそらくそれを行うための最良の方法ですが、私のニーズには少し「重すぎる」ものです。
georg 2011

1

場合は、xorすべてのための許容可能であるが、推測F(y)与えられたxF(x)、私はあなたがそれを行うことができると思い。まず、秘密の一方向性関数を選択します。たとえばS(s) = MD5(secret ^ s)。次にF(x) = (s, S(s) ^ x)、どこsがランダムに選択されます。私はそれをタプルとして書きましたが、2つの部分を整数に組み合わせることができますF(x) = 10000 * s + S(s) ^ x。復号化により、ソルトがs再度抽出され、が使用されF'(F(x)) = S(extract s) ^ (extract S(s)^x)ます。与えられてxF(x)あなたは見ることができますs(それは少し難読化されていますが)そしてあなたは推測することS(s)ができますyが、異なるランダムソルトを持つ他のユーザーにとってtは、知っているユーザーF(x)は見つけることができませんS(t)


ありがとう、しかしこれは私には十分にランダムに見えません(更新を参照してください)
georg 2011

ソルトはランダムに選択され、ハッシュS(s)もランダムに見えるためF(x)、進行はまったくありません。
ベンジャクソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.