混淆ID


85

我正在寻找一种将整数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行不通的

为了清楚起见,我并不是在寻找强大的加密解决方案,而只是在混淆。想象一下,像URL的Web应用程序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)不应形成任何形式的进展。


是int F(int x)还是它是int [2] F(int x)?
Eugen Rieck

@Eugen Rieck,理想情况下,我希望x和F(x)在数字范围内
georg

@ toon81,是的,该功能将被保密
georg

既然您说过要不带令牌,那是否意味着要避免使用任何形式的查找表?
DanielMošmondor2011年

16
伙计,这个问题可以说得很清楚,而这正是我要寻找的。不错的工作。
Snekse 2013年

Answers:


39

使用2或3个简单方法的某种组合对其进行混淆:

  • 异或
  • 随机播放单个位
  • 转换为模块化表示形式(D.Knuth,第2卷,第4.3.2章)
  • 在每个子集中选择32个(或64个)重叠的位子集和XOR位(子集的奇偶校验位)
  • 用可变长度数字系统和随机数字表示
  • 选择一对奇数整数xy它们是彼此的乘法逆(模2 32),然后乘以x混淆并乘以y恢复,所有乘法都是模2 32(来源:Eric的“乘法逆的实际使用”)利珀特

可变长度数字系统方法本身并不能满足您的“进步”要求。它总是产生较短的算术级数。但是,当与其他方法结合使用时,它会产生良好的结果。

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

这是其中三种方法的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

谢谢您的回答。如果您可以提供一些伪代码示例,那就太好了。
乔治

3
@ thg435我用C ++代替了伪代码。不想给出未经测试的例子。
Evgeny Kluev 2011年

1
当我尝试使用x = 99上面的数字系统基本代码时,我得到z = 44。
哈维2013年

@Harvey:要获得可逆混淆器,所有碱基的乘积应大于混淆的数量。在此示例中,3 * 4 * 5 = 60,因此,任何较大的数字(如99)都不必恢复为相同的值。
Evgeny Kluev 2013年

1
@Harvey:也有可能使所有基数的乘积较小,但非常接近2 ^ 32,然后使用一张小表混淆剩余值。在这种情况下,所有内容都保留为32位数字。
Evgeny Kluev 2013年

8

您希望转换是可逆的,而不是显而易见的。这听起来像是一种加密,它采用给定范围内的数字并在相同范围内产生不同的数字。如果您的范围是64位数字,则使用DES。如果您的范围是128位数字,则使用AES。如果您想要一个不同的范围,那么最好的选择可能是Hasty Pudding cipher,它设计用于处理不同的块大小和不完全适合一个块的数字范围,例如100,000到999,999。


有趣的东西,但是
要让

谢谢!我正在尝试使其尽可能简单。
乔治

如果找不到Hasty Pudding的实现(您只需要允许的大小之一),则可以轻松地以均匀的块大小实现简单的4轮Feistel密码(en.wikipedia.org/wiki/Feistel_cipher)。就像Hasty Pudding一样,只需继续加密直到输出在正确的范围内即可。不安全,但足以混淆。
rossum 2011年

NSA现在已经发布了Speck密码,其中包括包含32位和48位块大小的版本。这对于混淆那些大小的数字可能也很有用。特别是32位版本可能会有用。
rossum

5

就安全性而言,混淆还不够。

但是,如果您想阻止随意的旁观者,我建议将两种方法结合使用:

  • 您将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

我还没有测试过,但是我认为这是可逆的,应该快速,并且不太容易弄清楚该方法。


还增加了一个常量mod 2 ^ 32(因为您的位旋转使我想起了rot13,这是每个人最喜欢的平凡可逆函数)。
ccoakley 2011年

那真的只是return x XOR rotr(31415927, 5)对吧?最后一个异或会撤消第一个异或,而旋转会互斥对方。..当然,任何可逆操作链也是可逆的,因此它确实满足该条件。
哈罗德

我已经进行了一些简短的测试,并对结果符合预期感到满意。正如ccoakley所提到的,可以用rot13代替rot5,任何旋转都可以(caveat:0> rot>整数大小),可以认为是另一个关键。您还可以在这里添加其他内容,例如他建议的模数,以及哈罗德提到的可逆性。
IAmNaN 2011年

1
抱歉,但是@harold最正确-您的整个函数等效于x = x XOR F(0),或x = x XOR 3087989491,或x = x XOR rotr(31415927, 5)。您的第一个和最后一个异或运算彼此取反,因此您要做的就是用键对移位后的输入进行异或-或等效地,将键移位后的输入异或。请注意,即使您在每个阶段使用了不同的键,这都是正确的-所有键都可以组合成一个可以与明文异或的键。
尼克·约翰逊

2
更糟糕的是,很容易证明以恒定的偏移量旋转任何链,并且具有常数的异或可以被浓缩为仅一个旋转和一个异或。可以合并彼此后的两个旋转(加上它们的偏移量),可以合并彼此后的两个异或(与两个常量的异或),并且可以通过将相同的旋转应用于异或腐对来替换为腐/异或。异或中的常数。
哈罗德


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));
   }

XOR1并且XOR2只是0到之间的随机数MAXMAX2**32-1; 您应该将此设置为您认为最高ID的任何值。

COPRIME是一个互质数w /的数字MAX。我认为质数本身与其他所有数均互质(它们的倍数除外)。

INVERSE是需要解决的棘手问题。这些博客文章并没有给出直接的答案,但是WolframAlpha可以为您找到答案。基本上,只要解方程(COPRIME * x) % MAX = 1x

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功能,可让您处理很大的数字。我需要使用它,因为我要乘以非常大的数字,这可能会导致中间值之一暂时超过Number.MAX_SAFE_INTEGER并失去精度。
mpen

2

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

  • 旋转值
  • 使用查找替换值的某些部分
  • 具有一定价值的异或
  • 交换位
  • 交换字节
  • 反映整体价值
  • 反映一部分价值
  • ... 动用你的想象力

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

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

将上面列表中的内容添加到例程中,直到您的电话号码看起来正确为止。

除此之外,还可以获取The Book的副本。


您所描述的是分组密码的基本组成部分。使用现有的比发明自己的更有意义。
尼克·约翰逊

@NickJohnson我知道,您是否单击了我帖子最后一行中的链接?
丹尼尔·莫斯蒙多(DanielMošmondor)2011年

我未能将rotl / xor组合在一起给出的结果看起来“足够随机”(请参阅​​更新)。有指针吗?
乔治

@DanielMošmondor我知道您要链接的内容-但这并不能改变您最初建议他自己构建某些东西的事实,而仅使用现有的东西更有意义吗?
尼克·约翰逊

@NickJohnson显然,OP不想使用现有的加密货币,因为他想学习还是不学习新的API。我完全可以与之相关。
DanielMošmondor2011年

2

我写了一篇有关使用分组密码的安全置换的文章,该文章应该能够满足您所说的要求。

不过,我建议,如果您想猜测标识符,则应该首先使用它们:生成UUID,并首先将它们用作记录的主键-无需能够在“真实” ID之间进行转换。


2
@ thg435如果您对此方法感兴趣,一个有用的搜索词是“格式保留加密”。维基百科页面涵盖了Nick文章中提到的Black / Rogaway论文,以及最近的发展。我已经成功地将FPE用于与您正在做的事情类似的事情;尽管在我的情况下,我在用于光有效性检查的ID之外添加了一些内容。
Paul Du Bois

1

不知道您需要多“硬”,多快或使用多少内存。如果没有内存限制,则可以列出所有整数,将它们洗牌并将其用作映射。但是,即使对于4字节整数,您也将需要大量内存。

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

因此,*(group2)可能是(bit 14,12,10,8,6,4,2,0)因此,加1会同时更改group1group2

尽管如此,这仅仅是出于安全性的考虑,任何能够将数字输入到您的函数中的人(即使您对函数保密)也可以很容易地找出来。


取决于系统的约束,这可能不起作用,因为如果您可以将F(x)转换回x,则必须具有可用的排列,然后可以根据给定任何值轻松地计算F(y)任意
templatetypedef

@templatetypedef正如我所说的,这只是出于默默无闻的安全。排列必须是已知的,但是您可以将排列视为“键”。这里最大的问题是,OP似乎希望能够对一个集合(一小部分)中的所有消息进行加密,其中加密的消息应适合于同一集合,并且这对集合中的所有消息均应有效。
RogerLindsjö2011年

谢谢。我试图避免任何查找表。
乔治


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字节消息)加密为同一组消息,并且加密应该适用于所有消息。
罗杰·林德斯(RogerLindsjö)2011年

谢谢您的回答。使用成熟的加密框架也许是实现此目的的最佳方法,但是对于我的需求来说有点“繁重”。
乔治

1

如果xor除了推断F(y)给定的一切都可以接受的话xF(x)那么我认为你可以用盐做些。首先选择一个秘密的单向功能。例如S(s) = MD5(secret ^ s)。然后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)。给定后xF(x)您可以看到s(尽管有些模糊),并且可以进行推断,S(s)但是对于其他一些y具有不同随机盐t的用户,该用户F(x)不会找到S(t)


谢谢,但这对我来说似乎不够随机(请参阅更新)
georg

盐是随机选择的,并且散列S(s)也将看起来是随机的,因此F(x)完全没有任何进展。
本杰克逊
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.