随机化DB记录ID

时间:2012-05-28 13:55:07

标签: ruby-on-rails postgresql encryption random uuid

在我们的网络应用程序中,我们想要随机化记录ID。原因是我们想要隐藏数据库中已有多少条目,我们还有未列出的内容。如果ID是简单的增量数字,则很容易猜出未列出的内容的ID。

我认为有三种方法可以做到这一点:

简单随机数

算法:

  1. 在插入时创建一个随机数。
  2. 检查ID是否已被使用。如果是,转到1。
  3. 使用此ID。
    • 容易
    • 适用于任何大小或类型的ID(32位,64位,变长,字符串)

    魂斗罗

    • 需要针对可能的竞争条件进行交易(算法不是原子的)

    的UUID

    • 碰撞的可能性非常低,你可以忽略它

    魂斗罗

    • 我们想要将页面标题作为网址注释("#{id}--#{page_title})获得不错的短网址,UUID会将此评论一直转移到右侧
    • 我认为UUID作为主键会对联接产生较低的性能吗?

    加密ID

    算法:

    1. 使用nextval(原子!)
    2. 从序列中读取数字
    3. 使用密钥和加密算法加密ID,加密算法使用ID
    4. 的大小

      • 没有竞争条件(无需交易)

      魂斗罗

      • ID列的大小永远不能更改
      • 如果有人可以破解/猜测关键一切都是无用的

      时间戳

      建议@emboss

      • 容易
      • 不会用完ID

      魂斗罗

      • 可能会产生碰撞(虽然需要测试它是否确实发生过)
      • 可能有点可猜测

      随机公共ID /基于名称的公共ID

      @viktor tron建议

      URL中出现的所有内容的第二个ID,仅用于查找记录。使用内部正常ID(用于连接等)。

      • 内部一切都保持清醒
      • 一个好的随机算法/命名方案应该使URL猜测不可能(足够)

      魂斗罗

      • 更改了许多在公共接口中使用ID的内容
      • 用户可能希望他们可以删除包含此类标题的网址,但在这种情况下,网址将不再有效

      我想我会使用第三种选择。还是有更多反对它的论据?有更好的解决方案吗?我们使用Ruby on Rails 3.x和PostgreSQL 9.x。

      编辑:不公开并不代表私密!它就像YouTube上的不公开视频一样。它们是普通视频,未在搜索或上传者的个人资料中列出。所以你无法真正找到它们(没有尝试所有可能的ID),但知道URL的每个人都可以访问它们。当然,那些制作不公开内容并将链接发送给其他人的用户必须知道它可能不会保持未知(URL可能会传递并通过链接最终可能会出现在搜索引擎中)。

      我们还有另一个选项可以让事情变得私密。这是两件不同的事情。 (我认为假设每个人都知道“不公开”意味着什么是错误。)

6 个答案:

答案 0 :(得分:12)

注意:这回答了问题的初始版本,从中不明显这不是授权逻辑的替代。


这是解决错误问题的错误方法。

您认为问题是:用户可以猜出“未列出”的内容并使用它们。

实际问题是:用户可以擅自访问。

将授权逻辑放在适当的位置,允许用户只访问他可以合法访问的项目,并禁止其他所有内容。

同时

  

隐藏数据库中有多少条目

如果这是原因,我认为小的并不羞耻。无论如何,你可以从100000开始你的序列或者用N递增它或使用另一个类似的技巧:)

答案 1 :(得分:3)

我建议采用完全不同的方式:只是不向用户显示记录ID。你不需要。 使用其他形式的身份证明。

既然你说你想要漂亮的网址,你可以简单地使用一个重击手/永久链接宝石,比如https://github.com/norman/friendly_id

friendly_id的默认slug生成器提供了检查slug字符串的唯一性的功能,如有必要,还会附加一个序列来保证它。

说真的,单独留下ID:)

答案 2 :(得分:2)

使用具有一些静态盐的抗冲突哈希函数以及“内部ID”引用。例如,SHA-256将X中的元素唯一地映射到H中的元素,具有较低的碰撞概率;但是,从H中的元素计算X中的元素是非常困难的(数学上的)。

在Ruby中,执行以下操作:

@hashed_id = Digest::SHA2.new << SHA_SALT << @foo.id

顺便说一句,这不是一种加密形式,因为任何人都可以在不知道私钥的情况下,在给定相同输入的情况下生成相同的哈希。它也只是一个单向函数,因此也没有“解密”算法。

答案 3 :(得分:1)

我认为塞尔吉奥已经为你的问题提供了完美的答案。

您要实现的目标是通过默默无闻的安全性的一个很好的例子:您试图将这些项目隐藏起来,而不是正确地限制对某些不公开项目的访问。但这仍然有可能猜测那些隐藏的项目,而访问限制使得不可能查看一个不应该的页面。这就是为什么访问限制是明显的赢家:我们有0次查看我们不应该拥有的东西而不是成功的可能性很小。即使它可以忽略不计,0总是会胜过大于零的某个值。

我只是想补充一些想法,为什么你提出的解决方案不起作用:

随机数

在这里不使用SecureRandom就已经失败了。使用正常rand可以使随机数可预测,因此任何决定“找到”隐藏页面的人都有很大的成功机会。但即使使用安全的随机数,您只是“扩散”您的页面均匀分布在某些数字范围内。记录/页面最终落入您的应用程序中,攻击者随机猜测最终会成功的概率越高。

的UUID

一旦攻击者发现它们是如何构建的,它们很容易被猜到。由于它们是按照确定性方案构建的,因此它们的随机性没有任何安全性。

加密/散列

这里使用加密是错误的。这是错误的,因为它是可逆的,并且没有必要,因为这是你实际上想要防止的。除非您使用经过身份验证的加密,否则生成的密文将具有可扩展性,因此即使不知道所使用的密钥,攻击者也很有可能在禁止的页面上进行操作。更不用说他们可能试图恢复密钥的众多攻击。 因此,更好的解决方案确实是使用正确随机化的安全散列函数。使用静态盐是不够好的,原因与密码不够相同:每个ID盐可以更好地最小化预先计算字典的能力。预计算非常简单,使用各种盐计算ID为1-100的表实际上是一种很有前途的策略,因为攻击者知道它是在此处进行散列的顺序数据库ID。

但无论你怎么努力,总有机会通过简单的猜测获得访问权限。因此,总结一下塞尔吉奥已经说过的话,你真正需要的是身份验证和访问限制的实现。

如果要隐藏数据大小,为什么不尝试使用时间戳呢?在任何情况下,我都会保持数据库ID不变,并为您要在URL中显示的内容添加一个特殊的“显示ID”列,但保留原始ID作为主键。

答案 4 :(得分:1)

无耻插头: https://github.com/dividedmind/pg_random_id

只需放入gem,按照自述文件添加迁移即可。这是基于加扰序列,因此保证不会发生冲突。您可以使用随机整数或字符串ID。

答案 5 :(得分:0)

虽然这是一个值得称赞的目标,但将ID列从自动递增的整数更改可能是一个错误。当您接下来时,ID列应该仅供数据库使用。它允许数据库公开关系并确保记录可以彼此分开查找。您正在尝试使用ID列来展示我认为的业务逻辑:您希望模型具有基本随机的参考编号。当业务逻辑发生变化时,您将需要更改ID列,这将导致外键丢失,并且可能会非常令人头痛。

要实现这一目标,您应该创建一个名为“number”的新列,并在其上实现其中一个策略。然后,如果您需要迁移到新策略,那么这样做会更容易:而不是Model.find(id),您只需执行Model.find_by_number(number)