混淆ID

时间:2011-12-18 19:43:37

标签: algorithm encryption

我正在寻找一种方法来将整数ID加密/混淆为另一个整数。更确切地说,我需要一个函数int F(int x),所以

  • x< - > F(x)是一一对应的(如果x!= y,F(x)!= F(y))
  • 给定F(x),很容易找到x - 所以F不是哈希函数
  • 给出x和F(x)很难/不可能找到F(y),x ^ 0x1234之类的东西不起作用

为清楚起见,我不是在寻找强大的加密解决方案,它只是模糊处理。设想一个网页应用程序,其中包含example.com/profile/1example.com/profile/2等网址。这些配置文件本身并不是秘密,但我想阻止随意的偷窥者一个接一个地查看/获取所有配置文件,所以我想而是隐藏在example.com/profile/23423example.com/profile/80980234之类的东西之后。虽然数据库存储的令牌很容易完成,但我很好奇是否有一些简单的数学可用。

我不清楚的一个重要要求是结果看起来应该是“随机的”,也就是说,给定序列x,x+1,...,x+nF(x),F(x+1)...F(x+n)不应形成任何类型的进展。

11 个答案:

答案 0 :(得分:37)

使用2或3种简单方法的组合对其进行模糊处理:

  • XOR
  • 洗牌个人位
  • 转换为模块化表示(D.Knuth,第2卷,第4.3.2章)
  • 在每个子集中选择32位(或64位)重叠的位子集和XOR位(子集的奇偶校验位)
  • 用可变长度的数字系统和随机数字表示它
  • 选择一对奇数整数xy,它们是彼此的乘法逆(模2 32 ),然后乘以x进行混淆并乘以y进行恢复,所有乘法都是模2 32 (来源:"A practical use of multiplicative inverses" by Eric Lippert

可变长度数字系统方法本身不符合您的“进展”要求。它总是产生短的算术进展。但是当与其他方法结合使用时,它会产生很好的效果。

模块化表示方法也是如此。

以下是其中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

答案 1 :(得分:9)

您希望转换是可逆的,而不是显而易见的。这听起来像是一个加密,它在给定范围内取一个数字,并在同一范围内产生不同的数字。如果您的范围是64位数,则使用DES。如果您的范围是128位数,则使用AES。如果你想要一个不同的范围,那么你最好的选择可能是Hasty Pudding cipher,这是为了应对不同的块大小和数字范围不适合整齐到一个块,如100,000到999,999。

答案 2 :(得分:6)

在安全性方面,混淆不够。

然而,如果你试图阻止随意的旁观者,我建议采用两种方法的组合:

  • 通过xor'ing将它们与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

我没有对它进行过测试,但我认为这是可逆的,应该是快速的,并且不容易梳理出这种方法。

答案 3 :(得分:5)

我发现这段特殊的Python / PHP代码非常有用:

https://github.com/marekweb/opaque-id

答案 4 :(得分:2)

使用不会破坏它们的ID位做任何事情。例如:

  • 旋转值
  • 使用查找替换值的某些部分
  • xor带有一些值
  • 交换位
  • swap bytes
  • 镜像整个价值
  • 镜像值的一部分
  • ...用你的想象力

对于解密,请按相反顺序执行所有操作。

创建一个程序,为您“加密”一些有趣的值,并将它们放在您可以检查的表中。使用相同的程序使用您希望在系统中拥有的所有值集来测试加密/解密例程。

将以上列表中的内容添加到例程中,直到您的数字看起来被错误地显示给您。

除此之外,请获取The Book的副本。

答案 5 :(得分:2)

我写了一篇关于secure permutations with block ciphers的文章,它应该满足你所说的要求。

我建议,如果您想要难以猜测标识符,您应该首先使用它们:生成UUID,并首先将它们用作记录的主键 - 没有必要能够转换为“真实”ID。

答案 6 :(得分:2)

我使用该线程中的一些想法编写了一些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之间的随机数。 MAX2**32-1;您应该将其设置为您认为最高ID的任何值。

COPRIME是与MAX互质的数字。我认为质数本身与其他所有数互质(除了它们的倍数)。

INVERSE是需要解决的棘手问题。这些博客文章并未给出直接答案,而是WolframAlpha can figure it out for you。基本上,只需为(COPRIME * x) % MAX = 1求解方程x

我创建了build函数,以使创建这些编码/解码管道更加容易。您可以根据需要输入[encode, decode]对任意数量的操作。这些功能必须相等且相反。 XOR函数是它们自己的补充,因此您在那里不需要一对。


这是另一个有趣的involution

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位整数-只需将数字更改为任何其他大小即可)

答案 7 :(得分:1)

不确定你需要多么“难”,有多快,或者使用的内存有多少。如果您没有内存约束,则可以创建所有整数的列表,将它们混合并将该列表用作映射。但是,即使是4字节整数,也需要大量内存。

但是,这可以做得更小,因此不是映射所有整数,而是仅映射2(或最差情况1)字节,并将其应用于整数中的每个组。因此,使用2个字节的整数将是(group1)(group2),您将通过随机映射映射每个组。但这意味着如果您只更改group2,那么group1的映射将保持不变。这可以通过将不同的位映射到每个组来“修复”。

所以,*(group2)可能是(位14,12,10,8,4,2,0)所以,添加1会改变 group1 group2 < / em>的

尽管如此,这只是默默无闻的安全,任何能够将数字输入你的功能的人(即使你保守功能秘密)都可以很容易地解决它。

答案 8 :(得分:1)

你在这里所描述的似乎是单向函数的反面:它很容易反转,但很难应用。一种选择是使用标准的,现成的公钥加密算法,您可以修复一个(秘密的,随机选择的)公钥,保存一个秘密,以及您与世界共享的私钥。这样,你的函数F(x)将是使用公钥加密x。然后,您可以使用私有解密密钥轻松地将F(x)解密回x。请注意,公钥和私钥的角色在这里是相反的 - 您向所有人提供私钥,以便他们可以解密该功能,但在您的服务器上保密公钥。那样:

  1. 这个功能是一个双射,所以它是可逆的。
  2. 给定F(x),x是有效可计算的。
  3. 给定x和F(x),从y计算F(y)非常困难,因为没有公钥(假设您使用加密强加密方案),没有可行的方法来加密数据,甚至如果私有解密密钥已知。
  4. 这有很多优点。首先,您可以放心,加密系统是安全的,因为如果您使用像RSA这样成熟的算法,那么您不必担心意外的不安全感。其次,已有图书馆可以做到这一点,因此您不需要编写太多代码,也可以免受旁道攻击。最后,你可以让任何人都可以去反转F(x)而不需要任何人实际计算F(x)。

    一个细节 - 你绝对不应该只是在这里使用标准的int类型。即使使用64位整数,也可能只有很少的组合,攻击者可以蛮力尝试反转所有内容,直到他们找到某些y的加密F(y),即使他们没有密钥。我建议使用类似512位值的东西,因为即使是科幻攻击也无法对此进行暴力破解。

    希望这有帮助!

答案 9 :(得分:1)

生成一个私有对称密钥以在您的应用程序中使用,并使用它加密您的整数。这将满足所有三个要求,包括最难的#3:一个人需要猜测你的密钥才能打破你的计划。

答案 10 :(得分:1)

如果除了xor F(y)x之外,F(x)可以接受S(s) = MD5(secret ^ s),我认为你可以使用 salt 来做到这一点。首先选择一个秘密的单向函数。例如F(x) = (s, S(s) ^ x)。然后s随机选择F(x) = 10000 * s + S(s) ^ x。我把它写成一个元组,但你可以将这两个部分组合成一个整数,例如: s。解密再次提取盐F'(F(x)) = S(extract s) ^ (extract S(s)^x)并使用x。鉴于F(x)s,您可以看到S(s)(虽然它稍微模糊不清),您可以推断y,但对于其他一些具有不同随机的用户t知道F(x)的用户无法找到S(t)。{/ 1>}。{{1}}