问题 用于id混淆的1-1映射


我使用顺序ID作为主键,并且在某些情况下我不希望这些ID对用户可见,例如我可能想要避免使用类似于?invoice_id = 1234的网址,以便用户猜出系统中有多少发票作为一个整体发行。

我可以添加一个带有GUID的数据库字段或者从散列函数,随机字符串和/或数字基本转换中产生的东西,但是这种方案有三个我觉得讨厌的问题:

  1. 必须分配额外的数据库字段。我知道我可以使用GUID作为我的主键,但我的自动增量整数PK是大多数用途的正确的东西,我不想改变它。

  2. 不得不考虑哈希/ GUID冲突的可能性。我完全赞同关于GUID碰撞的所有论点,就像自发燃烧或其他任何事情一样,但是忽视特殊情况,因为它们是特殊情况与我所教过的其他事情相悖,即使我知道它也会继续打扰我我应该对其他事情更加困扰。

  3. 我不知道如何安全地修剪基于散列的标识符,所以即使我的私有ID是16位或32位,我仍然坚持使用128位生成的标识符,这些标识符在url中是一种麻烦。

我对id范围,可伸缩或缩小的1-1映射感兴趣,例如16位id映射到16位id,32位id映射到32位id等,这会阻止某人尝试猜测一个时期内分配的ID总数或id分配率。

例如,如果我的用户ID是16位整数(0..65535),那么有点混淆id分配的转换示例是函数f(x)=(x mult 1001)mod 65536.内部id序列1,2,3的公共id序列成为1001,2002,3003的公共id序列。随着从基础转换的另一层混淆,例如到基础36,序列变为'rt','1jm','2bf'。当系统获得对url?userid = 2bf的请求时,它从base 36转换为get 3003并且它应用逆变换g(x)=(x mult 1113)mod 65536以返回到内部id = 3。

这种方案足以阻止临时用户随意观察,但很容易被一个有兴趣尝试解决问题的人解决。任何人都可以建议一些更强大的东西,但很容易实现PHP,没有特殊的库?这接近于自己的加密方案,所以也许有一个适当的加密算法可以广泛使用并且具有上面提到的可拉伸性能?

编辑:退一步,讨论一下 codinghorror 关于从三种键中选择 - 代理(基于guid),代理(基于整数),自然。在这些方面,我试图隐藏用户的整数代理键,但我正在寻找一些可缩小的东西,使得网址不会太长,我不知道如何处理标准的128位GUID 。有时,正如公主评论者在下面提出的那样,问题可以用自然键来回避。

编辑2 /摘要:

  • 考虑到我提出的问题的限制(可伸缩性,可逆性,易于实现),迄今为止最合适的解决方案似乎是Someone和Breton建议的基于异或的混淆。
  • 假设我可以通过默默无闻地实现混淆/安全,那将是不负责任的。知道它是一个整数序列可能是一个任何有能力的攻击者都能够利用的小儿床。
  • 我已经考虑了额外数据库字段的想法。额外领域的一个优点是,对于试图通过查看数据库来熟悉系统的未来程序员来说,它更加直接。否则,他们必须深入挖掘源代码(或文档,ahem),以确定如何将对给定URL的请求解析为数据库中的给定记录。
  • 如果我允许额外的数据库字段,那么问题中的一些其他假设就变得无关紧要了(例如,转换不需要是可逆的)。这成了一个不同的问题,所以我会留在那里。感谢大家分享您的见解。

2561
2018-01-11 03:14


起源



答案:


我发现简单的XOR加密最适合URL混淆。您可以继续使用您正在使用的任何序列号而无需更改。进一步的XOR加密不会增加源字符串的长度。如果您的文本是22个字节,加密的字符串也将是22个字节。要像腐烂13一样猜测它并不容易,但不像DSE / RSA那样重。

在网上搜索PHP XOR加密以找到一些实现。我找到的第一个是 这里


7
2018-01-11 04:46



如果用户得到多条消息,那么找出关键短语非常容易。 - erickson


答案:


我发现简单的XOR加密最适合URL混淆。您可以继续使用您正在使用的任何序列号而无需更改。进一步的XOR加密不会增加源字符串的长度。如果您的文本是22个字节,加密的字符串也将是22个字节。要像腐烂13一样猜测它并不容易,但不像DSE / RSA那样重。

在网上搜索PHP XOR加密以找到一些实现。我找到的第一个是 这里


7
2018-01-11 04:46



如果用户得到多条消息,那么找出关键短语非常容易。 - erickson


我自己以业余的方式玩弄了这种东西,并得到了一种怪异的数字加扰算法,涉及混合基数。基本上我有一个函数,它将0-N之间的数字映射到0-N范围内的另一个数字。对于URLS,我然后将该数字映射到几个英语单词。 (单词更容易记住)。

我所做的简化版本,没有混合基数:你有一个32位的数字,所以提前有一个32位长的密码,并且输入密码与你的输入数字进行异或。然后在确定的重新排序中将位置洗牌。 (可能基于你的密钥)。

关于这个的好处是

  1. 没有碰撞,只要你每次都以同样的方式洗牌和xor
  2. 无需将混淆密钥存储在数据库中
  3. 仍然在内部使用您订购的IDS,因为您可以撤消混淆
  4. 您可以多次重复该操作以获得更多混淆的结果。

如果你想要混合基数版本,它基本上是相同的,除了我添加了将输入转换为混合raddix数字的步骤,使用最大范围的素因子作为数字的基数。然后我将数字随机移动,用数字保持基数,然后将其转回标准整数。


3
2018-01-11 06:07





您可能会发现重新使用GUID的想法很有用,因为您可以以不受冲突的方式构造GUID。

看看 关于GUID的维基百科页面  - “类型1”算法使用PC的MAC地址和当前日期/时间作为输入。这保证了碰撞根本不可能。

或者,如果在数据库中创建GUID列作为备用键(继续使用自动增量主键),请将其定义为唯一。然后,如果您的GUID生成方法确实给出了重复,那么您将在可以处理的插入上获得适当的错误。


2
2018-01-11 03:51



好点。我将阅读该文章,看看是否有适合我目的的GUID类型,并尝试修改问题,以便术语'hash'和'GUID'的使用不太可互换。


我昨天看到了这个问题: reddit如何生成一个alpum id 

我认为这是一个相当不错的方法(特别聪明)

它使用Python

def to_base(q, alphabet):
    if q < 0: raise ValueError, "must supply a positive integer"
    l = len(alphabet)
    converted = []
    while q != 0:
        q, r = divmod(q, l)
        converted.insert(0, alphabet[r])
    return "".join(converted) or '0'

def to36(q):
    return to_base(q, '0123456789abcdefghijklmnopqrstuvwxyz')

2
2018-01-11 06:55





在您的订单表中添加char(10)字段...将其命名为'order_number'。

创建新订单后,随机生成1 ... 9999999999之间的整数。检查数据库中是否存在'order_number'。如果没有,请使用此值更新您的最新行。如果确实存在,请随机选择另一个号码。

将'order_number'用于可公开查看的网址,也可以使用零填充。

当两个线程试图同时添加相同的数字时,存在竞争条件问题...如果你真的担心的话,你可以做一个表锁,但这是一个很大的问题。更新后添加第二个检查,重新选择以确保它是唯一的。递归调用,直到获得唯一条目。在调用之间保持随机数毫秒,并使用当前时间作为随机数生成器的种子。

从中刷过 这里

更新 与使用Bevan描述的GUID aproach一样,如果列被限制为唯一,那么您不必冒汗。我想这与使用GUID没有什么不同,除了客户和客户服务将更容易参考订单。


1
2018-01-11 05:47





我找到了一种更简单的方法。假设您要将N位数伪随机地映射到N位数。你找到N的下一个最高素数,你就可以实现你的功能了

prandmap(x) return x * nextPrime(N) % N

这将产生一个每N重复(或有一个句号)的函数,在x = N + 1之前不会产生两次数。它始终从0开始,但此后是伪随机的。


1
2018-06-01 06:19





老实说,加密/解密查询字符串数据是解决这个问题的一个不好的方法。最简单的解决方案是使用POST而不是GET发送数据。如果用户单击带有查询字符串数据的链接,则必须使用某些链接 javascript hacks 通过POST发送数据(关闭Javascript的用户请记住可访问性)。这并不妨碍用户查看源代码,但至少它会使搜索引擎无法对敏感信息进行索引,假设您尝试隐藏的数据首先是真正敏感的。

另一种方法是使用自然的唯一键。例如,如果您每月向客户开具发票,则“yyyyMM [customerID]”将唯一标识特定用户的特定发票。


0
2018-01-11 03:49



关于自然键的好处。使用POST而不是GET - 我想到的那种攻击者/ noseyparker能够解释HTML源并嗅探HTTP请求。