【问题标题】:How to generate a random string in Ruby如何在 Ruby 中生成随机字符串
【发布时间】:2010-09-10 10:33:14
【问题描述】:

我目前正在为 "A" .. "Z" 生成一个 8 字符的伪随机大写字符串:

value = ""; 8.times{value  << (65 + rand(25)).chr}

但它看起来并不干净,并且不能作为参数传递,因为它不是单个语句。为了得到混合大小写的字符串 "a" .. "z" 加上 "A" .. "Z",我将其更改为:

value = ""; 8.times{value << ((rand(2)==1?65:97) + rand(25)).chr}

但它看起来像垃圾。

谁有更好的方法?

【问题讨论】:

  • 我不明白你为什么关心“因为它不是一个单一的语句,它不能作为参数传递”。为什么不把它变成一个实用程序或辅助方法呢?
  • 假设有一个方法可以重置用户的密码并且它有一个新密码的参数。我想传入一个随机字符串,在上面的代码中我需要一个 tmp 变量,而在下面的单个语句示例中,我可以将整个事情作为一个衬垫来完成。当然,从长远来看,实用方法可能会很好,尤其是如果我需要类似的地方和那里,但有时你只是希望它到位,一次完成。
  • 不,您不必使用临时变量。试试这个:reset_user_password!(random_string)def random_string; SecureRandom.urlsafe_base64(20) end
  • 8 个字母是可耻的弱密码。鉴于 md5sum,现代 PC 可以恢复 30 seconds 中的密码。再长点儿怎么样securerandom.urlsafe_base64
  • 为什么会有这么多答案?并不是说这不是一个有用的问题,但我很好奇它是如何引起注意的。

标签: ruby random passwords


【解决方案1】:
(0...8).map { (65 + rand(26)).chr }.join

我花太多时间打高尔夫球。

(0...50).map { ('a'..'z').to_a[rand(26)] }.join

最后一个更令人困惑,但更灵活,浪费的周期更少:

o = [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
string = (0...50).map { o[rand(o.length)] }.join

如果您想生成一些随机文本,请使用以下内容:

50.times.map { (0...(rand(10))).map { ('a'..'z').to_a[rand(26)] }.join }.join(" ")

此代码生成 50 个单词长度小于 10 个字符的随机单词字符串,然后用空格连接

【讨论】:

  • 34 个字符,速度极快:('a'..'z').to_a.shuffle[0,8].join。请注意,您需要 Ruby >=1.9 到 shuffle
  • 最好利用现有库,除非您有自己的驱动程序。在其他答案中,请参阅 SecureRandom 作为一个示例。
  • @faraz 你的方法在功能上不一样,替换不是随机的。
  • [*('a'..'z'),*('0'..'9')].shuffle[0,8].join 生成包含字母和数字的随机字符串。
  • rand 具有确定性和可预测性。不要用它来生成密码!请改用SecureRandom 解决方案之一。
【解决方案2】:

为什么不使用 SecureRandom?

require 'securerandom'
random_string = SecureRandom.hex

# outputs: 5b5cd0da3121fc53b4bc84d0c8af2e81 (i.e. 32 chars of 0..9, a..f)

SecureRandom 还具有以下方法:

  • base64
  • 随机字节数
  • 随机数

见:http://ruby-doc.org/stdlib-1.9.2/libdoc/securerandom/rdoc/SecureRandom.html

【讨论】:

  • base64 会,但不像他的例子那样是十六进制的
  • 顺便说一句,它是 1.9 和最近的 1.8 版本中 stdlib 的一部分,所以你可以通过 require 'securerandom' 来获得这个整洁的 SecureRandom 助手:)
  • BTW SecureRandom 在版本 3.2 中已从 ActiveSupport 中删除。从更改日志中:“从标准库中删除 ActiveSupport::SecureRandom 以支持 SecureRandom”。
  • SecureRandom.random_number(36**12).to_s(36).rjust(12, "0") 将生成一个长度为 0-9a-z(36 个字符)的字符串,长度始终为 12 个字符。将 12 更改为您想要的任何长度。不幸的是,没有办法只使用 Integer#to_s 获得 A-Z。
  • @stringo0 这是错误的。如果您想通过 URL 传递 +fGH1,您只需像对通过 URL 的任何值一样进行 URL 编码:%2BfGH1
【解决方案3】:

我使用它来生成具有保证最大长度的随机 URL 友好字符串:

string_length = 8
rand(36**string_length).to_s(36)

它生成小写 a-z 和 0-9 的随机字符串。它不是很可定制,但它又短又干净。

【讨论】:

  • +1 表示最短版本(不调用外部二进制文件^^)。如果随机字符串不是面向公众的,有时我什至只使用rand.to_s;丑陋,但有效。
  • 这是一个很好的解决方案(也很快),但它偶尔会产生一个字符串 under length 长度,大约在 ~40 次
  • @Brian E 这将保证您想要的数字:(36**(length-1) + rand(36**length)).to_s(36)。 36**(length-1) 转换为 base 36 是 10**(length-1),这是具有您想要的数字长度的最小值。
  • 这是始终生成所需长度令牌的版本:(36**(length-1) + rand(36**length - 36**(length-1))).to_s(36)
  • 这在 Rails 4 和 Ruby 2.1.1 中为我吐出一个错误:NameError: undefined local variable or method length' for main:Object`
【解决方案4】:

此解决方案为激活码生成一串易于阅读的字符;我不希望人们将 8 与 B、1 与 I、0 与 O、L 与 1 等混淆。

# Generates a random string from a set of easily readable characters
def generate_activation_code(size = 6)
  charset = %w{ 2 3 4 6 7 9 A C D E F G H J K M N P Q R T V W X Y Z}
  (0...size).map{ charset.to_a[rand(charset.size)] }.join
end

【讨论】:

  • “U”是模棱两可的还是错字?
  • @gtd - 是的。 U 和 V 是歧义词。
  • @colinm V 在里面。
  • 为了安全起见,您还需要使用SecureRandom.random_number(charset.size) 而不是rand(charset.size)
  • 我只是想通过添加小写和/或一些特殊字符(shift+num)来改进这一点,并得到以下列表:%w{ A C D E F G H J K L M N P Q R T W X Y Z 2 3 4 6 7 9 ! @ # $ % ^ &amp; * +}%w{ A D E F G H J L N Q R T Y a d e f h n r y 2 3 4 7 ! @ # $ % ^ &amp; * }(第一个列表没有'不包括小写,但它更长)有点有趣。
【解决方案5】:

其他人也提到过类似的东西,但这使用了 URL 安全功能。

require 'securerandom'
p SecureRandom.urlsafe_base64(5) #=> "UtM7aa8"
p SecureRandom.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"
p SecureRandom.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ=="

结果可能包含 A-Z、a-z、0-9、“-”和“_”。如果填充为真,也使用“=”。

【讨论】:

  • 这正是我想要的。谢谢!
【解决方案6】:

从 Ruby 2.5 开始,使用SecureRandom.alphanumeric 真的很容易:

len = 8
SecureRandom.alphanumeric(len)
=> "larHSsgL"

它生成包含 A-Z、a-z 和 0-9 的随机字符串,因此应该适用于大多数用例。而且它们是随机安全生成的,这也可能是一个好处。


这是一个将其与获得最多支持的解决方案进行比较的基准:

require 'benchmark'
require 'securerandom'

len = 10
n = 100_000

Benchmark.bm(12) do |x|
  x.report('SecureRandom') { n.times { SecureRandom.alphanumeric(len) } }
  x.report('rand') do
    o = [('a'..'z'), ('A'..'Z'), (0..9)].map(&:to_a).flatten
    n.times { (0...len).map { o[rand(o.length)] }.join }
  end
end

                   user     system      total        real
SecureRandom   0.429442   0.002746   0.432188 (  0.432705)
rand           0.306650   0.000716   0.307366 (  0.307745)

所以rand 解决方案只需要SecureRandom 大约3/4 的时间。如果您生成大量字符串,这可能很重要,但如果您只是不时创建一些随机字符串,我总是会选择更安全的实现,因为它也更容易调用和更明确。

【讨论】:

  • 现在我们需要一个类似的 0-z 解决方案(没有大写字符)。 SecureRandom 似乎没有提供开箱即用的功能。
【解决方案7】:
[*('A'..'Z')].sample(8).join

生成一个随机的 8 个字母字符串(例如 NVAYXHGR)

([*('A'..'Z'),*('0'..'9')]-%w(0 1 I O)).sample(8).join

生成一个随机的 8 个字符的字符串(例如 3PH4SWF2),不包括 0/1/I/O。红宝石 1.9

【讨论】:

  • 唯一的问题是结果中的每个字符都是唯一的。限制可能的值。
  • 如果这个feature request 通过,Ruby 1.9.x 可能会以#sample 不替换采样和#choice 替换采样结束。
  • 这是一个错误,我认为你需要... [*("A".."Z")]' ; ((不是单引号))
  • 你能告诉我如何stub 这个rspec 可以通过吗?
【解决方案8】:

我不记得我在哪里找到的,但对我来说,这似乎是最好的和最少的流程:

def random_string(length=10)
  chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ0123456789'
  password = ''
  length.times { password << chars[rand(chars.size)] }
  password
end

【讨论】:

  • 这不是少了一个0和1吗?
  • 看起来可能就是我找到它的地方。
  • 而且,看起来确实缺少 0 和 1。
  • 故意遗漏了01OI,因为这些字符不明确。如果使用此类代码生成一组用户需要复制的字符,最好排除在上下文之外可能难以区分的字符。
【解决方案9】:
require 'securerandom'
SecureRandom.urlsafe_base64(9)

【讨论】:

  • SecureRandom 是一个优秀的库。我不知道它在那里。谢谢。
  • old skool (and uglier) 替代品:Random.new.bytes(9)
  • 顺便说一句,urlsafe_base64 返回一个大约 4/3 长度的字符串。要获得正好 n 个字符长的字符串,请尝试 n=9 ; SecureRandom.urlsafe_base64(n)[0..n-1]
【解决方案10】:

如果你想要一个指定长度的字符串,使用:

require 'securerandom'
randomstring = SecureRandom.hex(n)

它将生成一个长度为2n的随机字符串,其中包含0-9a-f

【讨论】:

  • 它不会生成长度为n的字符串,它实际上会生成一个长度为n的4/3的字符串。
  • @OmarAli 你错了。根据 .hex 的 Ruby 文档,它是 2n。文档:SecureRandom.hex 生成一个随机的十六进制字符串。参数 n 指定要生成的随机数的长度(以字节为单位)。生成的十六进制字符串的长度是 n. 的两倍
【解决方案11】:

Array.new(n){[*"0".."9"].sample}.join, n=8 在你的情况下。

广义:Array.new(n){[*"A".."Z", *"0".."9"].sample}.join

发件人:“Generate pseudo random string A-Z, 0-9”。

【讨论】:

    【解决方案12】:

    这是长度为8的随机字符串的一行简单代码:

     random_string = ('0'..'z').to_a.shuffle.first(8).join
    

    您也可以将它用于长度为 8 的随机密码:

    random_password = ('0'..'z').to_a.shuffle.first(8).join
    

    【讨论】:

    • 您不应该使用它来为自己生成密码,因为这种方法永远不会重复一个字符。因此,您仅使用 P(36, 8) / 36^8 = 0.4 的可能字符空间用于 8 个字符(约 2 倍更容易暴力破解)或P(36, 25) / 36^25 = 0.00001 可能的字符空间用于 25 个字符(约 100,000 倍更容易暴力破解)。
    【解决方案13】:
    require 'sha1'
    srand
    seed = "--#{rand(10000)}--#{Time.now}--"
    Digest::SHA1.hexdigest(seed)[0,8]
    

    【讨论】:

    • 有趣,但计算量要大一些
    • 也无法容纳有限的字符范围。
    • 请记住,十六进制摘要仅返回 0-9 和 a-f 字符。
    【解决方案14】:

    Ruby 1.9+:

    ALPHABET = ('a'..'z').to_a
    #=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
    
    10.times.map { ALPHABET.sample }.join
    #=> "stkbssowre"
    
    # or
    
    10.times.inject('') { |s| s + ALPHABET.sample }
    #=> "fdgvacnxhc"
    

    【讨论】:

    • map 解决方案非常好!
    • 你可以向#sample询问你想要多少元素。例如。 ALPHABET.sample(10).join...ruby-doc.org/core-2.4.0/Array.html#method-i-sample
    • 这实际上是错误的。 sample(10) 为您提供 10 个独特的样本。但是你想让他们重复。但我会使用Array.new(10).map 来提高性能
    • 我想要大小写字母数字。我也改用Array.new 及其块语法。 Array.new(20) { [*'0'..'9', *'a'..'z', *'A'..'Z'].sample }.join
    【解决方案15】:

    请注意:rand 对于攻击者来说是可预测的,因此可能不安全。如果用于生成密码,您绝对应该使用 SecureRandom。我使用这样的东西:

    length = 10
    characters = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a
    
    password = SecureRandom.random_bytes(length).each_char.map do |char|
      characters[(char.ord % characters.length)]
    end.join
    

    【讨论】:

    • 这可能是“最”安全​​的解决方案。 SecureRandom 尝试使用操作系统提供的底层安全 API。如果您有 OpenSSL,它将使用它,如果您在 Windows 上,它将是那里的最佳选择。我特别喜欢这个解决方案,因为它允许您指定一组要使用的字符。虽然如果您的字符集长于一个字节的最大值:255,它将不起作用。我建议在文档中查看 SecureRandom 的源代码:ruby-doc.org/stdlib-1.9.3/libdoc/securerandom/rdoc/…
    【解决方案16】:

    这里是一个长度为 8 的随机密码的简单代码:

    rand_password=('0'..'z').to_a.shuffle.first(8).join
    

    【讨论】:

      【解决方案17】:
      SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')
      

      来自设计的东西

      【讨论】:

      • 为什么要用.tr('+/=lIO0', 'pqrsxyz')替换字符串?
      • 特殊字符,因为它们不是 URL 安全的。还有 l/I 或 O/0,因为如果您使用该技术生成可读的用户密码,它们很容易混淆。
      • 该函数对某些字符有一些偏见。同样对于其他长度(例如 16),最后一个字符不会是随机的。这是避免这种情况的一种方法。 SecureRandom.base64(64).tr('+/=lIO01', '')[0,16]
      【解决方案18】:

      我喜欢使用的另一种方法:

       rand(2**256).to_s(36)[0..7]
      

      如果您真的对正确的字符串长度有疑虑,请添加ljust

       rand(2**256).to_s(36).ljust(8,'a')[0..7]
      

      【讨论】:

      • 最好通过使用字符串的右侧来获取随机数的最低有效部分:rand(2**64).to_s(36)[-10,10]跨度>
      【解决方案19】:

      我认为这是简洁、清晰和易于修改的良好平衡。

      characters = ('a'..'z').to_a + ('A'..'Z').to_a
      # Prior to 1.9, use .choice, not .sample
      (0..8).map{characters.sample}.join
      

      轻松修改

      例如,包括数字:

      characters = ('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a
      

      大写十六进制:

      characters = ('A'..'F').to_a + (0..9).to_a
      

      对于真正令人印象深刻的字符数组:

      characters = (32..126).to_a.pack('U*').chars.to_a
      

      【讨论】:

      • 我建议只使用大写字母+数字,同时删除“令人困惑”的字符集 = (1..9).to_a.concat(('A'..'Z') .to_a).reject{ |a| [0, 1, 'O', 'I'].include?(a) } (0...size).map{ charset[rand(charset.size)] }.join
      【解决方案20】:

      只需在此处添加我的美分...

      def random_string(length = 8)
        rand(32**length).to_s(32)
      end
      

      【讨论】:

      • 注意:这并不总是准确地返回一个字符串 +length+ long - 它可能更短。这取决于rand返回的数字
      【解决方案21】:

      此解决方案需要外部依赖,但似乎比其他解决方案更漂亮。

      1. 安装 gem faker
      2. Faker::Lorem.characters(10) # =&gt; "ang9cbhoa8"

      【讨论】:

      • 不推荐使用第一个字符参数传递数字。请改用 characters(number: ...) 之类的关键字参数。 Faker::Lorem.characters(number: 10) # =&gt; "ang9cbhoa8"
      【解决方案22】:

      您可以使用来自 Ruby Gem 的 Facets 的 String#randomfacets

      基本上是这样的:

      class String
        def self.random(len=32, character_set = ["A".."Z", "a".."z", "0".."9"])
          characters = character_set.map { |i| i.to_a }.flatten
          characters_len = characters.length
          (0...len).map{ characters[rand(characters_len)] }.join
        end
      end
      

      【讨论】:

        【解决方案23】:

        我最喜欢的是(:A..:Z).to_a.shuffle[0,8].join。请注意,shuffle 需要 Ruby > 1.9。

        【讨论】:

          【解决方案24】:

          给定:

          chars = [*('a'..'z'),*('0'..'9')].flatten
          

          单个表达式,可以作为参数传递,允许重复字符:

          Array.new(len) { chars.sample }.join
          

          【讨论】:

            【解决方案25】:

            我最近正在做这样的事情,从 62 个字符中生成一个 8 字节的随机字符串。字符是 0-9、a-z、A-Z。我有一个数组,循环 8 次并从数组中选择一个随机值。这是在 Rails 应用程序中。

            str = ''
            8.times {|i| str << ARRAY_OF_POSSIBLE_VALUES[rand(SIZE_OF_ARRAY_OF_POSSIBLE_VALUES)] }
            

            奇怪的是我得到了很多重复。现在随机这几乎不会发生。 62^8 是巨大的,但在数据库中的 1200 个左右的代码中,我有很多重复项。我注意到它们发生在彼此的小时边界上。换句话说,我可能会在 12:12:23 和 2:12:22 或类似的时间看到一个重复...不确定是否是时间问题。

            此代码在 ActiveRecord 对象的创建之前。在创建记录之前,此代码将运行并生成“唯一”代码。数据库中的条目总是可靠地生成,但是代码(上一行中的str)被重复的太频繁了。

            我创建了一个脚本来运行上述行的 100000 次迭代,延迟很小,因此希望每小时看到某种重复模式需要 3-4 小时,但什么也没看到。我不知道为什么我的 Rails 应用会发生这种情况。

            【讨论】:

              【解决方案26】:

              我认为到目前为止,我最喜欢 Radar 的回答。我会稍微调整一下:

              CHARS = ('a'..'z').to_a + ('A'..'Z').to_a
              def rand_string(length=8)
                s=''
                length.times{ s << CHARS[rand(CHARS.length)] }
                s
              end
              

              【讨论】:

              • 为什么不使用(CHARS*length).sample(length).join
              • @niels 这个建议会生成一个 weighted 字符串,有利于非重复字符。例如,如果CHARS=['a','b'],那么您的方法将仅在 33% 的时间生成 "aa""bb",但在 67% 的时间生成 "ab""ba"。也许这不是问题,但值得牢记!
              • 好点子,@TomLord,我想我在发布该建议时并没有意识到这一点(尽管我必须承认我根本不记得发布过:D)
              【解决方案27】:
              ''.tap {|v| 4.times { v << ('a'..'z').to_a.sample} }
              

              【讨论】:

                【解决方案28】:

                我的 2 美分:

                  def token(length=16)
                    chars = [*('A'..'Z'), *('a'..'z'), *(0..9)]
                    (0..length).map {chars.sample}.join
                  end
                

                【讨论】:

                  【解决方案29】:

                  我只是写了一个小 gem random_token 来为大多数用例生成随机令牌,享受吧~

                  https://github.com/sibevin/random_token

                  【讨论】:

                  • 伟大的宝石。谢谢你:)
                  【解决方案30】:

                  由 3 个范围组成的随机字符串的 2 种解决方案:

                  (('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a).sample(8).join
                  
                  ([*(48..57),*(65..90),*(97..122)]).sample(8).collect(&:chr)*""
                  

                  每个范围中的一个字符。

                  如果您需要每个范围中至少一个字符,例如创建一个包含一个大写、一个小写字母和一个数字的随机密码,您可以执行以下操作:

                  ( ('a'..'z').to_a.sample(8) + ('A'..'Z').to_a.sample(8) + (0..9).to_a.sample(8) ).shuffle.join 
                  #=> "Kc5zOGtM0H796QgPp8u2Sxo1"
                  

                  【讨论】:

                  • 如果你想强制每个范围的一定数量,你可以这样做:( ('a'..'z').to_a.sample(8) + ('A'..'Z').to_a.sample(8) + (0..9).to_a.sample(8) + [ "%", "!", "*" ].sample(8) ).shuffle.join #=&gt; "Kc5zOGtM0*H796QgPp%!8u2Sxo1"
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-02-13
                  • 2011-01-09
                  • 2023-03-19
                  • 1970-01-01
                  • 2014-02-09
                  • 2017-08-01
                  相关资源
                  最近更新 更多