如何在Ruby中生成随机字符串


746

我目前正在为“ 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}

但看起来像垃圾

有谁有更好的方法?


我不明白您为什么关心“由于它不是单个语句,所以不能作为参数传递”。为什么不仅仅使其成为实用程序或辅助方法?
David J.

1
假设有一种重置用户密码的方法,并且有一个用于新密码的参数。我想传入一个随机字符串,在上面的代码中,我需要一个tmp变量,而在下面的单个语句示例中,我可以将整个事情作为一个衬套来完成。当然,从长远来看,肯定可以使用一种实用程序方法,尤其是如果我在这里和那里都需要类似的方法时,但是有时候您只想一次完成它就可以了。
杰夫

不,您不必使用临时变量。试试这个:reset_user_password!(random_string)哪里def random_string; SecureRandom.urlsafe_base64(20) end
David J.

8个字母是一个可耻的弱密码。使用md5sum,现代PC可以在30秒内恢复密码。再怎么样东西securerandom.urlsafe_base64
上校恐慌

1
为什么会有这么多答案?并不是说这不是一个有用的问题,但我很好奇它是如何引起关注的。
Lou

Answers:


969
(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

204
34个字符,速度超快:('a'..'z').to_a.shuffle[0,8].join。请注意,您需要Ruby> = 1.9 to shuffle
fny 2012年

20
除非您有自己的驱动程序,否则最好利用现有的库。参见SecureRandom一个示例,在其他答案中。
David J. 2012年

28
@faraz您的方法在功能上并不相同,替换时也不是随机的。
michaeltwofish 2012年

35
[*('a'..'z'),*('0'..'9')].shuffle[0,8].join生成包含字母和数字的随机字符串。
罗宾(Robin)2013年

31
rand是确定性和可预测的。不要用它来生成密码!请使用其中一种SecureRandom解决方案。
铅笔

800

为什么不使用SecureRandom?

require 'securerandom'
random_string = SecureRandom.hex

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

SecureRandom还具有以下方法:

  • base64
  • random_bytes
  • random_number

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


11
BASE64会,但没有六角喜欢在他的榜样
杰夫·迪基

67
顺便说一下,它是1.9和最近的1.8版本中的stdlib的一部分,因此您只能require 'securerandom'得到这个简洁的SecureRandom助手:)
J -_- L

21
BTW SecureRandom在版本3.2中已从ActiveSupport中删除。从变更日志中:“从标准库中删除了ActiveSupport :: SecureRandom,改为使用SecureRandom”。
Marc

15
SecureRandom.random_number(36**12).to_s(36).rjust(12, "0")会产生一个0-9a-z(36个字符)的字符串,该字符串总是12个字符。将12更改为您想要的长度。不幸的是,没有办法只使用AZ Integer#to_s
Gerry Shaw

1
@ stringo0是错误的。如果您想+fGH1通过URL 传递,则只需对其进行URL编码,就像通过URL 传递的任何值一样: %2BfGH1
nzifnab

247

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

rand(36**length).to_s(36)

它生成小写字母az和0-9的随机字符串。它不是非常可定制的,但是又短又干净。


4
+1为最短版本(不调用外部二进制文件^^)。如果随机字符串不是公开的,我有时甚至使用rand.to_s; 难看,但是有效。
乔·利斯

12
这是一个很好的解决方案(也很快速),但是偶尔会产生长度不足 的字符串length,大约每40
Brian E

7
@Brian E,这样可以保证您想要的数字:(36**(length-1) + rand(36**length)).to_s(36)。转换为基数36的36 **(length-1)是10 **(length-1),这是具有所需数字长度的最小值。
艾瑞克·胡

11
这是总是产生所需长度令牌的版本:(36**(length-1) + rand(36**length - 36**(length-1))).to_s(36)
Adrien Jarthon

3
这为我在Rails 4和Ruby 2.1.1中吐出了一个错误:NameError: undefined local variable or method main:Object`的length
kakubei 2014年

175

该解决方案为激活码生成一串易于阅读的字符;我不希望人们将B与8混淆,将I与1混淆,将O与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

3
“ U”是歧义词还是错字?
gtd 2012年

10
@gtd-是的。U和V是ambigvovs。
colinm

1
@colinm V在那里。
gtd

8
是安全的,你也可能需要使用SecureRandom.random_number(charset.size)的,而不是rand(charset.size)
GTD

1
我只是想通过添加小写字母和/或一些特殊字符(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 ! @ # $ % ^ & * +}%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 ! @ # $ % ^ & * }(第一个列表不包含小写字母,但更长)Kinda对此很感兴趣解决了。
dkniffin 2014年

130

其他人提到了类似的内容,但这使用了URL安全功能。

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

结果可能包含AZ,az,0-9,“-”和“ _”。如果填充为true,则也使用“ =”。


这正是我想要的。谢谢!
丹·威廉姆斯

69

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

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

它会生成包含AZ,az和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解决方案仅需要3/4的时间SecureRandom。如果您生成许多字符串,那可能很重要,但是如果您不时地创建一些随机字符串,我总是会选择更安全的实现,因为它也更容易调用和更明确。


48
[*('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。Ruby 1.9


4
唯一的问题是结果中的每个字符都是唯一的。限制可能的值。
tybro0103 2012年

1
如果此功能请求通过,则Ruby 1.9.x可能会以#sample进行抽样而不进行替换,而以#choice进行抽样以进行替换。
David J.

这是一个错误,我认为您需要... [*("A".."Z")]';((不是单个qoutes))
jayunit100 '16

你能告诉我怎么可以stub这样对rspec通过。
Sinscary '16

31

我不记得我在哪里找到的,但对我来说似乎是最好,最省力的过程:

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


难道不缺少0和1吗?
Sunnyrjuneja

看起来可能是我找到它的地方。
Travis Reeder

而且,看起来确实缺少0和1。
Travis Reeder

4
01OI被故意丢失,因为这些字符是不明确的。如果使用这种代码生成用户需要复制的一组字符,则最好排除可能难以区分出上下文的字符。
2014年

28
require 'securerandom'
SecureRandom.urlsafe_base64(9)

SecureRandom是一个出色的库。我不知道它在那里。谢谢。
Dogweather

旧的skool(和较丑陋的)替代方法:Random.new.bytes(9)
tardate 2012年

4
顺便说一句,urlsafe_base64返回一个字符串,长度约为指示的长度的4/3。要获取正好为n个字符长的字符串,请尝试n=9 ; SecureRandom.urlsafe_base64(n)[0..n-1]
tardate 2012年

25

如果要指定长度的字符串,请使用:

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

这将产生长度的随机串2n含有0-9a-f


它不会生成长度的字符串n,而是实际上会生成4/3的字符串n
奥马尔·阿里

@OmarAli你错了。根据Ruby文档,如果是.hex2n。文档:SecureRandom.hex生成一个随机的十六进制字符串。参数n指定要生成的随机数的长度(以字节为单位)。所得的十六进制字符串的长度是n的两倍。
Rihards


11
require 'sha1'
srand
seed = "--#{rand(10000)}--#{Time.now}--"
Digest::SHA1.hexdigest(seed)[0,8]

有趣,但计算量却高得多
Jeff

也没有能力限制字符范围。
肯特·弗雷德里克

3
请记住,十六进制摘要仅返回0-9和af字符。
webmat

11

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

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

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

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

2
您不应该使用它来生成密码,因为这种方法永远不会重复一个字符。因此,您仅使用P(36, 8) / 36^8 = 0.48个字符P(36, 25) / 36^25 = 0.00001的可能字符空间(〜2倍更容易暴力破解)或25个字符的可能字符空间(〜100,000x较容易暴力破解)。
Glacials

10

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"

1
map解决方案是非常好的!
米莎·莫罗什科

您可以要求#sample多少个元素。例如ALPHABET.sample(10).join... ruby-doc.org/core-2.4.0/Array.html#method-i-sample
odlp

这实际上是错误的。sample(10)给您10个独特的样本。但是您想允许它们重复。但我会用Array.new(10).map性能
SASAZejnilović

我想要小写和大写字母数字。我也已切换为使用Array.new及其块语法。 Array.new(20) { [*'0'..'9', *'a'..'z', *'A'..'Z'].sample }.join
taylorthurlow

8

请注意: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 /…

8

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

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

7

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

 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]
Jeff

6
SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')

来自Devise的东西


为什么使用替换字符串字符.tr('+/=lIO0', 'pqrsxyz')
miguelcobain 2013年

特殊字符,因为它们不是URL安全的。l / I或O / 0,因为如果使用该技术生成可读的用户密码,它们很容易混淆。
ToniTornado

该功能对某些字符有些偏见。同样对于其他长度(例如16),最后一个字符将不是随机的。这是避免这种情况的一种方法。SecureRandom.base64(64).tr('+ / = lIO01','')[0,16]
Shai Coleman

5

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

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
我建议仅使用大写字母+数字,同时删除“令人困惑”的字符集=(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
光泽

5

只是在这里加我的美分...

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

3
注意:这并不总是返回正好+ length + long的字符串-它可能更短。这取决于rand
tardate 2012年

5

您可以String#random从Ruby Gem的Facets中使用facets

它基本上是这样做的:

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

4

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


4

此解决方案需要外部依赖性,但看起来比其他解决方案还漂亮。

  1. 安装宝石造假者
  2. Faker::Lorem.characters(10) # => "ang9cbhoa8"

4

鉴于:

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

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

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

3

我认为到目前为止,我最喜欢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 2013年

@niels此建议将生成一个加权字符串,而不要使用非重复字符。例如,如果CHARS=['a','b']那么你的方法会产生"aa""bb"只有33%的时间,但"ab"还是"ba"的67%的时间。也许这不是问题,但值得牢记!
汤姆·罗德

好点,@ TomLord,我想我实际上并没有意识到,当我发布该建议时(尽管我必须承认我根本不记得发布该建议:D)
niels

3
''.tap {|v| 4.times { v << ('a'..'z').to_a.sample} }

3

我的2美分:

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


3

包含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 #=> "Kc5zOGtM0*H796QgPp%!8u2Sxo1"
Joshua Pinter

3

我最近在做这样的事情,从62个字符中生成了一个8字节的随机字符串。字符是0-9,az,AZ。我有一个数组,循环了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应用程序中发生。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.