假设我有一个带有自动递增 id 的表Orders
,例如 1、2、3、4...,它们当前查询为 http://www.example.com/order?id={1,2,3..}
现在,我想将主键 [1, 2, 3, ..] 散列到另一个称为订单号的数字中,以便我们的客户可以在他们的请求中引用它们,例如
1 -> 100192938303
2 -> 293029200002
我想要以下内容:
- 无法通过查看自动增量 ID 来猜测我每天创建了多少订单
- 不需要数据库额外的查找,纯粹由PHP进行哈希(和预定义的盐)
- 无碰撞
可能吗?
我认为您可能会选择更简单的方法 - 不要使用自动递增 id,使用随机整数作为 id。例:
while (true) {
$id = get_random_integer();
$stmt = $db->prepare("INSERT INTO Orders (id, foo, bar) VALUES (:id, 'foo', 'bar')");
try {
$stmt->execute(array(':id' => $id));
//OK
break;
} catch (Exception $ex) {
if (is_duplicate_id_exception) {
//generate new id and try again
continue;
}
//Some other problem
throw $ex;
}
}
这样你就可以:
- 避免碰撞
- 不需要哈希函数和 {哈希 -> id} 映射
- ID 不包含有关订单数量的信息
你提议使用加盐哈希。 由于哈希是单向函数,并且您需要从哈希转换为原始值,因此您需要以下方法之一将哈希转换为原始订单值:
- 循环遍历合理的订单值,获取每个值的加盐哈希值,直到您确定匹配的哈希值或耗尽允许的订单 ID 池。
- 缓存合理的订单值一次(例如,在应用程序启动时),并存储在哈希表中。 创建缓存后,此方法要快得多,但需要额外的查找。
您还注意到原始订单标识符是机密的,因为可以获得多个订单 ID 的攻击者可以推断订单量。 订单识别码的机密性与订单本身的机密性是分开的,该问题不涉及,可以通过单独的访问控制机制进行处理。
我认为您的示例中的首选方法是使用加密而不是哈希。 加密订单 ID 将满足机密性和往返要求,而不会产生哈希缓存或数据库查找的开销。 该方法可能如下所示:
- 使用您的密钥加密订单 ID。
- Base64 对订单 ID 进行编码,并作为令牌返回给客户端。
- 从客户端收到加密令牌后,解码 Base64 字符串
- 使用密钥解密解码的字符串以生成原始订单号。
例如,对于订单 42 和 DES 密钥E0EC4E44EF2C6CEE
和零 IV,您将dmTt0kbIlcA=
作为订单 ID 发送到客户端(如果将 42 编码为小端序 32 位整数)。 (此处适合使用零 IV,因为在您的方案中不考虑唯一的密文。
这里有两个想法:
- 使用可逆哈希。 这是否有效取决于您认为的安全性,因为它本质上只是混淆。 但是,如果您自定义它(可能会更改算法中某些步骤的顺序),并且防止源代码泄露,它将阻止除最坚定的攻击者之外的所有攻击者。 (根据您的安全目标,您可能希望与其他一些技术相结合以降低泄漏风险,例如,员工离开公司。 考虑将算法的一部分保密,就好像它是加密密钥一样,并对输入进行额外的可变预转换。
在我的头顶上,一个简单的可逆哈希可能只是位的"横向添加"。 对于更复杂的东西,在我的头顶上,流行的"MurmurHash"算法家族据称是可逆的。
我不知道有任何加密强度高的可逆哈希。 但是,关于对称加密主题的其他答案与此想法相似。
- 使用流密码,又名加密 RNG。 如果订单总数相当小,这是合适的。 您需要的是一个独特的数字序列,它与计数数字序列一对一映射。 因此,使用您选择的 RC4 或 HMAC 生成一个唯一的随机数序列,随时消除重复项。 (也许让这一切快速进行的一种创造性方法是布隆滤镜。
对于从内部 ID 到外部 ID 的映射,您只需生成序列。 相反,您继续前进,直到找到 ID 或达到最大订单 ID。 这个算法是O(n),这显然不理想,但如果你愿意妥协一点,增加更多的复杂性,或者聪明一点,你可能会找到一种方法来缓解这种情况。 例如,您可以在RAM中保留ID的缓存。
编辑:
由于线性复杂性,我自己对#2持怀疑态度,所以我运行了一些数字。 使用 Core2 处理器的 Crypto++ 基准数字,如果您预算 10 毫秒进行数字转换,并且使用 40 位 ID(假设达到一千万亿个订单),则订单 ID 最大值约为 2,500,000。 我认为你可以通过使用较小的键将其加倍。
所以这种方法可以采用任何一种方式。 对于小规模的东西,这很好。 (上述假设是保守的。 但对于大规模的东西,这可能是一个烦恼。 这足以让您完成产品发布;在你开始讨论如何将你的软件构建为分布式系统的时候,你会想重新审视它,这也将有助于解决这个问题。 但在这一点上,你最好质疑最初的假设,只是把这个东西存储在某个地方的数据库中。
订单 ID 提交到 GET 表单之前使用 base64_encode() 对其进行编码,然后在捕获表单发送的变量时使用 base64_decode() 进行编码
您甚至可以添加盐,例如base64_encode($id)。盐")