我使用顺序ID作为主键,并且有些情况下我不希望这些ID对用户可见,例如我可能想要避免使用像?invoice_id = 1234这样的网址,以便用户猜出有多少发票系统作为一个整体发行。
我可以添加一个带有GUID的数据库字段或者从散列函数,随机字符串和/或数字基本转换中产生的东西,但是这种方案有三个我觉得讨厌的问题:
必须分配额外的数据库字段。我知道我可以使用GUID作为我的主键,但是我的自动增量整数PK对于大多数用途来说是正确的,我不想改变它。
必须考虑哈希/ GUID冲突的可能性。我完全同意关于GUID碰撞的所有论点,就像自发燃烧或其他任何事情一样,但是忽视特殊情况,因为它们与我所教过的其他所有内容相悖,即使我知道它也会继续打扰我我应该对其他事情感到困扰。
我不知道如何安全地修剪基于散列的标识符,所以即使我的私有ID是16位或32位,我仍然坚持使用128位生成的标识符,这些标识符在网址中很麻烦。
我对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成为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 /摘要:
答案 0 :(得分:7)
我发现简单的XOR加密最适合URL混淆。您可以继续使用您正在使用的任何序列号而无需更改。进一步的XOR加密不会增加源字符串的长度。如果您的文本是22个字节,加密的字符串也将是22个字节。要想像腐烂13一样但不像DSE / RSA那样重量不够容易。
在网上搜索PHP XOR加密以找到一些实现。我找到的第一个是here。
答案 1 :(得分:3)
我自己以业余的方式玩弄了这种东西,并得到了一种怪异的数字加扰算法,涉及混合基数。基本上我有一个函数可以将0到N之间的数字映射到0-N范围内的另一个数字。对于URLS,我然后将该数字映射到几个英语单词。 (单词更容易记住)。
我所做的简化版本,没有混合基数:你有一个32位的数字,所以提前有一个32位长的密码,并且用你的输入数字对密码进行异或。然后在确定的重新排序中将这些位洗牌。 (可能基于你的密钥)。
关于这个的好处是
如果您使用的是混合基数版本,它基本上是相同的,除了我添加了将输入转换为混合的raddix数字的步骤,使用最大范围的素因子作为数字的基数。然后我将数字随机移动,用数字保持基数,然后将其转回标准整数。
答案 2 :(得分:2)
您可能会发现重新审视使用GUID的想法很有用,因为您可以以不会发生冲突的方式构建GUID。
查看Wikipedia page on GUIDs - “类型1”算法使用PC的MAC地址和当前日期/时间作为输入。这保证了碰撞根本不可能。
或者,如果在数据库中创建GUID列作为备用键(继续使用自动增量主键),请将其定义为唯一。然后,如果您的GUID生成方法确实给出了重复,那么您将在插入时得到适当的错误,您可以处理。
答案 3 :(得分:2)
昨天我看到了这个问题:how reddit generates an alphanum 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')
答案 4 :(得分:1)
在您的订单表中添加char(10)字段...将其命名为'order_number'。
创建新订单后,随机生成1 ... 9999999999之间的整数。检查数据库中是否存在'order_number'。如果没有,请使用此值更新您的最新行。如果确实存在,请随机选择另一个号码。
将'order_number'用于可公开查看的网址,也可以使用零填充。
当两个线程同时尝试添加相同的数字时,存在竞争条件问题...如果您真的担心,可以执行表锁定,但这是一个很大的问题。更新后添加第二个检查,重新选择以确保它是唯一的。递归调用,直到获得唯一条目。在调用之间保持一个随机的毫秒数,并使用当前时间作为随机数生成器的种子。
从here翻译。
更新与使用Bevan描述的GUID aproach一样,如果列被限制为唯一,那么您不必冒汗。我想这与使用GUID没有什么不同,除了客户和客户服务将更容易参考订单。
答案 5 :(得分:1)
我发现了一种更简单的方法。假设您要将N位数伪随机地映射到N位数。你从N中找到了下一个最高素数,你就可以实现你的功能
prandmap(x) return x * nextPrime(N) % N
这将产生一个每N重复(或有一个句号)的函数,直到x = N + 1才产生两次数。它始终从0开始,但此后是伪随机的。
答案 6 :(得分:0)
老实说,加密/解密查询字符串数据是解决此问题的一种不好的方法。最简单的解决方案是使用POST而不是GET发送数据。如果用户点击带有查询字符串数据的链接,则必须使用一些javascript hacks来通过POST发送数据(对于关闭了Javascript的用户,请记住可访问性)。这并不妨碍用户查看源代码,但至少它会保持敏感,不会被搜索引擎编入索引,假设您尝试隐藏的数据首先是真正敏感的。
另一种方法是使用自然的唯一键。例如,如果您每月向客户开具发票,则“yyyyMM [customerID]”会唯一标识特定用户的特定发票。
答案 7 :(得分:0)
从你的描述来看,我个人首先要处理可用的标准加密库(我是一名Java程序员,但我认为,一个基本的AES加密库必须可用于PHP):
此外,如果您想隐藏变量是发票ID,您可以考虑将其称为“invoice_id”以外的其他内容。