【问题标题】:Completely random identifier of a given length给定长度的完全随机标识符
【发布时间】:2011-07-08 05:35:46
【问题描述】:

我想生成一个包含数字、字母和特殊字符的给定长度(长度可能不同)的完全随机的“唯一”(我将确保使用我的模型)标识符

例如:

161551960578281|2.AQAIPhEcKsDLOVJZ.3600.1310065200.0-514191032|

有人可以建议在 Ruby on Rails 中最有效的方法吗?

编辑:重要提示: 如果可能,请评论您提出的解决方案的效率,因为每次用户进入网站时都会使用它!

谢谢

【问题讨论】:

  • 是否需要遵循特定的模式?或者,如果输入集中的字符可能出现在任何地方,是否可以?
  • 无模式。我不希望它以任何方式可预测。
  • id 会被用作某种会话 id 吗?那么除了随机之外,它还应该是加密安全的......
  • 它将用作 API 的访问令牌。和 Facebook 的做法一模一样!

标签: ruby-on-rails ruby ruby-on-rails-3


【解决方案1】:

将其用于访问令牌与 UUID 不同。您不仅需要伪随机性,而且还需要是加密安全的PRNG。如果您真的不在乎使用什么字符(它们不会增加任何安全性),您可以使用如下内容,生成 URL 安全的 Base64 编码访问令牌。如果您将令牌附加到 URL,URL 安全性变得很重要,类似于某些 Java Web 应用程序所做的:“http://www.bla.com/jsessionid=”。如果您为此目的使用原始 Base64 字符串,您将产生潜在的无效 URL。

require 'securerandom'

def produce_token(length=32)
  token = SecureRandom.urlsafe_base64(length)
end

得到重复的概率等于 2^(-length)。由于输出将是 Base64 编码的,因此实际输出将是 4/3 * 长度。如果安装,这是基于本机 OpenSSL PRNG 实现,因此在性能方面应该非常高效。如果未安装 OpenSSL 扩展,/dev/urandom 将被使用(如果可用),最后,如果您在 Windows 机器上,CryptGenRandom 将用作后备。这些选项中的每一个都应该具有足够的性能。例如,在我的笔记本电脑上运行 produce_tokena 百万次在 ~6 秒内完成。

【讨论】:

  • 你认为这是access_token的最佳解决方案吗?我在想我不会用它作为加密的密钥。但现在我相信使用加密安全的 PRNG 是一个很好的策略,因为我将来可能会用它来加密。
  • 使用 PRNG 的原因不是因为它加密了某些东西。您使用它是因为您不希望任何人能够通过观察模式来预测未来的令牌。随机数生成器可以产生完美的伪随机数,但仍然是可预测的,例如LCG。这就是为什么您需要一个安全的 PRNG。通过使用 Base64 编码而不是 hex 编码,您还可以节省一些字符:hex 是“length”的两倍,Base 64 只有 4/3。
  • 这也是一个好的解决方案吗? ruby-1.9.2-p180 :006 > @foo = SecureRandom.uuid => "cb0aece9-6702-4538-ba52-52df7f24921a" ruby-1.9.2-p180 :007 > Base64.encode64(@foo) => "Y2IwYWVjZTktNjcwMi00NTM4LWJhNTItNTJkZjdmMjQ5MjFh\n" ruby-1.9.2-p180 :008 > Base64.decode64("Y2IwYWVjZTktNjcwMi00NTM4LWJhNTItNTJkZjdmMjQ5MjFh\n") => "cb0aece9-6702-4538-ba52-52df7f24921a"
  • 无需先生成 UUID。在内部检索随机字节的机制保持不变,因此通过先生成 UUID 并在之后对其进行 Base64 编码,您将一无所获 - 您也可以直接生成 Base64。我忘记了 URL 安全性,这是另一个重要方面。我修改了我的答案以考虑到所有这些。
【解决方案2】:

最好的解决办法是:

require 'active_support/secure_random'
ActiveSupport::SecureRandom.hex(16) # => "00c62d9820d16b52740ca6e15d142854"

这将生成一个加密安全的随机字符串(即完全不可预测)

同样,您可以按照其他人的建议使用库来生成UUIDs。在这种情况下,请务必使用随机版本(版本 4)并确保实现使用加密安全随机生成器。

作为与安全相关的任何事情,滚动自己并不是最好的主意(即使我也屈服于它,请参阅第一个版本!:-)。如果你真的想要一个自制的随机字符串,这里重写tybro0103的方法:

require 'digest/sha1'
ALPHABET = "|,.!-0123456789".split(//) + ('a'..'z').to_a + ('A'..'Z').to_a

def random_string
    not_quite_secure = Array.new(32){ ALPHABET.sample }.join
    secure = Digest::SHA1.hexdigest(not_quite_secure)
end

random_string # => "2555265b2ff3ecb0a13d65a3d177b326733bc143"

请注意,它会对随机字符串进行哈希处理,否则可能会受到攻击。 性能应该相似。

【讨论】:

  • 你的意思是这个版本是完全不可预测的还是使用上一个答案中建议的gem??
  • 这个版本依赖于Array#sample,它使用了Ruby(伟大的)内置随机生成器,所以它是不可预测的。更新了 UUID 的答案。
  • 我将使用您的版本并将其标记为答案。因为我无法在 heroku 上安装 gem。
  • 在第二个示例中使用 SHA-1 散列不会增加安全性。看到这个想到基于密码的加密(PKCS#5),这里的原理是一样的。如果底层数据是可预测的(就像密码一样),散列它不会增加熵 - 这就是密码在被消化之前被“加盐”的原因。
  • 我很好奇,所以我asked。似乎我们俩都说得有道理。它似乎增加了安全性,但仍然不如例如安全。 SecureRandom 提供。
【解决方案3】:

Universally Unique Identifieres - UUIDs 很难自己生成 ;-) 如果您想要真正可靠的东西,请使用 uuid4r gem 并使用 UUID4R::uuid(1) 调用它。这将根据时间和硬件 id(计算机的 mac 地址)输出一个 uuid。因此,如果同时生成,它甚至在多台机器上都是独一无二的。

uuid4r 的一个要求是ossp-uuid c library,您可以使用您选择的数据包管理器安装它(debian 上的apt-get install libossp-uuid libossp-uuid-dev 或带有homebrew 的mac 上的brew install ossp-uuid)或手动下载并编译它当然。

与手动(更简单?)实现相比,使用 uuid4r 的优势在于它 a) 真正独特,而不仅仅是“某种有时可靠的伪随机数生成器”,b) 它速度快(即使使用更高的 uuid版本)通过使用 c 库的本机扩展

require 'rubygems'
require 'uuid4r'
UUID4R::uuid(1) #=> "67074ea4-a8c3-11e0-8a8c-2b12e1ad57c3"
UUID4R::uuid(1) #=> "68ad5668-a8c3-11e0-b5b7-370d85fa740d"

更新: 关于速度,请参阅我的(完全不科学!)超过 50k 次迭代的小基准

      user     system      total        real
version 1  0.600000   1.370000   1.970000 (  1.980516)
version 4  0.500000   1.360000   1.860000 (  1.855086)

所以在我的机器上,生成一个 uuid 大约需要 0.4 毫秒(请记住,我在整个基准测试中使用了 50000 次迭代)。希望这对你来说足够快

(遵循“基准”)

require 'rubygems'
require 'uuid4r'
require 'benchmark'

n = 50000
Benchmark.bm do |bm|
  bm.report("version 1") { n.times { UUID4R::uuid(1) } }
  bm.report("version 4") { n.times { UUID4R::uuid(4) } }
end

heroku 更新:gem 是 available on heroku as well

【讨论】:

  • 效率高吗??因为每次用户进入我的网站我都会使用它!
  • 这可能非常有用,但 OP 评论说它不应该以任何方式预测,所以应该使用 UUID 版本 4...
  • 我不认为这是可以预测的,对@paukul?但我不确定这是否是最有效的方法!
  • @ntenisOT:版本 4 完全不可预测;其他的不是随机的,一旦你知道一个这样的 uuid,下一个的可能性空间就很小。有关不同版本 uuid 的说明,请参见 wikipedia:en.wikipedia.org/wiki/Uuid
  • @Marc-André Lafortune 使用版本 4 当然是正确的,感谢您指出这一点。我还在我的答案中添加了“基准”。
【解决方案4】:
def random_string(length=32)
    chars = (0..9).to_a.concat(('a'..'z').to_a).concat(('A'..'Z').to_a).concat(['|',',','.','!','-'])
    str = ""; length.times {str += chars.sample.to_s}
    str
end

结果:

>> random_string(42)
=> "a!,FEv,g3HptLCImw0oHnHNNj1drzMFM,1tptMS|rO"

【讨论】:

  • 也许在最终实现中,您会将 chars 设置为一个包含所有这些字符的长列表的数组,而不是每次都生成。
  • 是的,我认为这真的很糟糕。例如,如果您有 1000 个在线用户,并且您希望为每个用户提供一个唯一标识符.. 怎么办?我认为这没有效率。
  • 所以只需使用长数组 [0,1,2,3,4,...]。
  • 想法是对的,实现可以改进(见我的回答)。请注意,str += ...str << ... 慢,因为它每次都会创建一个中间字符串。
【解决方案5】:

由于字符行为的变化,在 Ruby 1.9 和 1.8 中生成随机字母有点棘手。在 1.9 中执行此操作的最简单方法是生成要使用的字符数组,然后从该数组中随机抓取字符。 见http://snippets.dzone.com/posts/show/491

【讨论】:

    【解决方案6】:

    你可以检查实现here我用this一个

    【讨论】:

      猜你喜欢
      • 2011-07-20
      • 2019-12-20
      • 1970-01-01
      • 1970-01-01
      • 2012-10-18
      • 2021-03-20
      • 2012-01-18
      • 2012-02-13
      • 1970-01-01
      相关资源
      最近更新 更多