831

I'm currently generating an 8-character pseudo-random uppercase string for "A" .. "Z":

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

but it doesn't look clean, and it can't be passed as an argument since it isn't a single statement. To get a mixed-case string "a" .. "z" plus "A" .. "Z", I changed it to:

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

but it looks like trash.

Does anyone have a better method?

5
  • I don't understand why you care that "since it isn't a single statement it can't be passed as an argument". Why not just make it a utility or helper method? Commented Jun 14, 2012 at 18:53
  • 1
    Suppose there is a method to reset a user's password and it has an argument for the new password. I would like to pass in a random string, in the above code I need a tmp variable, whereas in the single statement examples bellow I can do the whole thing as a one liner. Sure a utility method could be nice in the long run, esp if I'm needing similar here and there, but sometimes you just want it in place, one time, done. Commented Jun 26, 2012 at 16:07
  • No, you don't have to use a temporary variable. Try this: reset_user_password!(random_string) where def random_string; SecureRandom.urlsafe_base64(20) end Commented Jun 26, 2012 at 16:13
  • 8 letters is a shamefully weak password. Given the md5sum a modern PC could recover the password in 30 seconds. How about something longer securerandom.urlsafe_base64 Commented Oct 13, 2012 at 15:46
  • well, put a webdesigner infront of VIM and ask him to save and exit; Jokes apart. require 'securerandom'; SecureRandom.hex(15) should work fine Commented Apr 22, 2016 at 7:45

46 Answers 46

1043
(0...8).map { (65 + rand(26)).chr }.join

I spend too much time golfing.

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

And a last one that's even more confusing, but more flexible and wastes fewer cycles:

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

If you want to generate some random text then use the following:

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

this code generates 50 random word string with words length less than 10 characters and then join with space

Sign up to request clarification or add additional context in comments.

13 Comments

34 characters and blazing fast: ('a'..'z').to_a.shuffle[0,8].join. Note you'll need Ruby >=1.9 to shuffle.
Leveraging existing libraries is preferable unless you have a driver to roll your own. See SecureRandom as one example, in the other answers.
@faraz your method isn't functionally the same, it's not random with replacement.
[*('a'..'z'),*('0'..'9')].shuffle[0,8].join to generate a random string with both letters and numbers.
rand is deterministic and predictable. Don't use this for generating passwords! Use one of the SecureRandom solutions instead.
|
870

Why not use SecureRandom?

require 'securerandom'
random_string = SecureRandom.hex

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

SecureRandom also has methods for:

  • base64
  • random_bytes
  • random_number

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

14 Comments

base64 would, but not hex like in his example
By the way, it's part of the stdlib in 1.9 and recent 1.8 versions, so one can just require 'securerandom' to get this neat SecureRandom helper :)
BTW SecureRandom was removed from ActiveSupport in version 3.2. From the changelog: "Removed ActiveSupport::SecureRandom in favor of SecureRandom from the standard library".
SecureRandom.random_number(36**12).to_s(36).rjust(12, "0") will generate a string with 0-9a-z (36 characters) that is ALWAYS 12 characters long. Change 12 to whatever length you want. Unfortunately no way to just get A-Z using Integer#to_s.
@stringo0 that is wrong. If you wanted to pass +fGH1 through a URL, you just need to URL-encode it like you would ANY value that's going through a URL: %2BfGH1
|
273

I use this for generating random URL friendly strings with a length between 1 and string_length characters:

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

It generates random strings of lowercase a-z and 0-9. It's not very customizable but it's short and clean.

12 Comments

+1 for the shortest version (that doesn't call external binaries ^^). If the random string isn't public facing, I sometimes even just use rand.to_s; ugly, but works.
This is a great solution (and fast, too), but it will occasionally produce a string under length length, roughly once in ~40
@Brian E this would guarantee the digits you want: (36**(length-1) + rand(36**length)).to_s(36). 36**(length-1) converted to base 36 is 10**(length-1), which is the smallest value that has the digit length you want.
Here is the version always producing tokens of the desired length: (36**(length-1) + rand(36**length - 36**(length-1))).to_s(36)
This spits out an error for me in Rails 4 and Ruby 2.1.1: NameError: undefined local variable or method length' for main:Object`
|
177

This solution generates a string of easily readable characters for activation codes; I didn't want people confusing 8 with B, 1 with I, 0 with O, L with 1, etc.

# 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

7 Comments

Is 'U' ambiguous or is that a typo?
@gtd - Yep. U and V are ambigvovs.
@colinm V's in there though.
To be secure you would also want to use SecureRandom.random_number(charset.size) instead of rand(charset.size)
I was just trying to improve on this, by adding lower case and/or some special characters (shift+num), and got the following lists: %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 ! @ # $ % ^ & * +} and %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 ! @ # $ % ^ & * } (first list doesn't include lower case, but it's longer) Kinda interesting how that worked out.
|
149

Since Ruby 2.5, it's really easy with SecureRandom.alphanumeric:

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

It generates random strings containing A-Z, a-z and 0-9 and therefore should be applicable in most use-cases. And they are generated randomly secure, which might be a benefit, too.

In Ruby 3.3, the :chars keyword argument was added to alphanumeric. If you just want A-Z, you can use:

len = 8    
SecureRandom.alphanumeric(len, chars: [*'A'..'Z'])
=> "KNJVPINU"

This is a benchmark to compare it with the solution having the most upvotes:

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)

So the rand solution only takes about 3/4 of the time of SecureRandom. That might matter if you generate a lot of strings, but if you just create some random string from time to time I'd always go with the more secure implementation since it is also easier to call and more explicit.

1 Comment

Now we need a similar solution for 0-z (without the uppercase characters). SecureRandom doesn't seem to provide that out of the box.
136

Others have mentioned something similar, but this uses the URL safe function.

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

The result may contain A-Z, a-z, 0-9, “-” and “_”. “=” is also used if padding is true.

Comments

55
[*('A'..'Z')].sample(8).join

Generate a random 8 letter string (e.g. NVAYXHGR)

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

Generate a random 8 character string (e.g. 3PH4SWF2), excludes 0/1/I/O. Ruby 1.9

4 Comments

Only problem is each character in the result is unique. Limits the possible values.
If this feature request goes through, Ruby 1.9.x may end up with #sample for sampling without replacment and #choice for sampling with replacement.
This is an error, i think you need ... [*("A".."Z")]' ; ((not single qoutes))
can you tell me how i can stub this for rspec to pass.
31

I can't remember where I found this, but it seems like the best and the least process intensive to me:

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

5 Comments

Isn't there a 0 and 1 missing from this?
Looks like that could be where I found it.
And ya, it does look like 0 and 1 are missing.
The 0 and 1 and O and I were intentionally missing because those characters are ambiguous. If this sort of code is being used to generate a set of characters that a user needs to copy, it's best to exclude characters that may be difficult to distinguish out of context.
29
require 'securerandom'
SecureRandom.urlsafe_base64(9)

2 Comments

old skool (and uglier) alternative: Random.new.bytes(9)
btw, urlsafe_base64 returns a string about 4/3 the length indicated. To get a string exactly n chars long, try n=9 ; SecureRandom.urlsafe_base64(n)[0..n-1]
26

If you want a string of specified length, use:

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

It will generate a random string of length 2n containing 0-9 and a-f

2 Comments

It doesn't generate a string of length n, it actually generates a string that's 4/3 of n.
@OmarAli you're wrong. As per Ruby documentation in case of .hex it's 2n. Documentation: SecureRandom.hex generates a random hexadecimal string. The argument n specifies the length, in bytes, of the random number to be generated. The length of the resulting hexadecimal string is twice of n.
16

Array.new(n){[*"0".."9"].sample}.join, where n=8 in your case.

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

From: "Generate pseudo random string A-Z, 0-9".

Comments

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

3 Comments

Interesting, but quite a bit more computationally expensive
Also no capacity for limited scope of characters.
Keep in mind that a hex digest returns only 0-9 and a-f characters.
11

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"

4 Comments

The map solution is really nice!
You can ask #sample for how many elements you want. E.g. ALPHABET.sample(10).join... ruby-doc.org/core-2.4.0/Array.html#method-i-sample
That is actually wrong. sample(10) gives you 10 unique samples. But you want to allow them to repeat. But I would use Array.new(10).map for performance
I wanted alphanumeric with both lower and upper cases. I've also switched to use Array.new and its block syntax. Array.new(20) { [*'0'..'9', *'a'..'z', *'A'..'Z'].sample }.join
11

Here is one line simple code for random string with length 8:

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

You can also use it for random password having length 8:

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

1 Comment

You should not use this to generate yourself a password, as this method will never repeat a character. Therefore you're only using P(36, 8) / 36^8 = 0.4 of the possible character space for 8 characters (~2x easier to brute force) or P(36, 25) / 36^25 = 0.00001 of the possible character space for 25 characters (~100,000x easier to brute force).
8

Be aware: rand is predictable for an attacker and therefore probably insecure. You should definitely use SecureRandom if this is for generating passwords. I use something like this:

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

1 Comment

This is probably the "most" secure solution. SecureRandom attempts to use underlying security APIs provided by the operating system. If you have OpenSSL it will use that, if you're on Windows it will go the best option there. I especially like this solution because it allows you to specify a set of characters for use. Though it won't work if your character set is longer than the maximum value of a byte: 255. I recommend viewing the source code for SecureRandom in the doc: ruby-doc.org/stdlib-1.9.3/libdoc/securerandom/rdoc/…
8

Another method I like to use:

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

Add ljust if you are really paranoid about the correct string length:

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

1 Comment

Even better to grab the least significant part of the random number by using the right hand side of the string: rand(2**64).to_s(36)[-10,10]
7

I think this is a nice balance of conciseness, clarity and ease of modification.

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

Easily modified

For example, including digits:

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

Uppercase hexadecimal:

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

For a truly impressive array of characters:

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

1 Comment

i would recommend this to just use capital letters + numbers, also remove the "confusing" ones charset = (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
7
SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')

Something from Devise

3 Comments

Why is it replacing string characters using .tr('+/=lIO0', 'pqrsxyz')?
The special characters because they are not URL safe. And l/I or O/0 because they are very easy confused if you use the technique to generate readable user passwords.
That function has a bit of bias towards certain characters. Also for other lengths (e.g. 16), the last character will not be random. Here's a way to avoid that. SecureRandom.base64(64).tr('+/=lIO01', '')[0,16]
7

Here is one simple code for random password with length 8:

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

Comments

5

Just adding my cents here...

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

1 Comment

NB: this doesn't always return a string exactly +length+ long - it may be shorter. It depends on the the number returned by rand
5

You can use String#random from the Facets of Ruby Gem facets.

It basically does this:

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

Comments

4

This solution needs external dependency, but seems prettier than another.

  1. Install gem faker
  2. Faker::Lorem.characters(10) # => "ang9cbhoa8"

1 Comment

Passing number with the 1st argument of characters is deprecated. Use keyword argument like characters(number: ...) instead. Faker::Lorem.characters(number: 10) # => "ang9cbhoa8"
4

Given:

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

Single expression, can be passed as an argument, allows duplicate characters:

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

Comments

4

I was doing something like this recently to generate an 8 byte random string from 62 characters. The characters were 0-9,a-z,A-Z. I had an array of them as was looping 8 times and picking a random value out of the array. This was inside a Rails app.

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

The weird thing is that I got good number of duplicates. Now randomly this should pretty much never happen. 62^8 is huge, but out of 1200 or so codes in the db i had a good number of duplicates. I noticed them happening on hour boundaries of each other. In other words I might see a duple at 12:12:23 and 2:12:22 or something like that...not sure if time is the issue or not.

This code was in the before create of an ActiveRecord object. Before the record was created this code would run and generate the 'unique' code. Entries in the DB were always produced reliably, but the code (str in the above line) was being duplicated much too often.

I created a script to run through 100000 iterations of this above line with small delay so it would take 3-4 hours hoping to see some kind of repeat pattern on an hourly basis, but saw nothing. I have no idea why this was happening in my Rails app.

Comments

3

With this method you can pass in an abitrary length. It's set as a default as 6.

def generate_random_string(length=6)
  string = ""
  chars = ("A".."Z").to_a
  length.times do
    string << chars[rand(chars.length-1)]
  end
  string
end

Comments

3

I like Radar's answer best, so far, I think. I'd tweak a bit like this:

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

3 Comments

Why not use (CHARS*length).sample(length).join?
@niels This suggestion would generate a weighted string, in favour of non-repeated characters. For example, if CHARS=['a','b'] then your method would generate "aa" or "bb" only 33% of the time, but "ab" or "ba" 67% of the time. Maybe that's not a problem, but it's worth bearing in mind!
good point, @TomLord, I think I didn't actually realise that when I posted that suggestion (although I must admit I don't remember posting that at all :D)
3

My 2 cents:

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

Comments

3

My favorite is (:A..:Z).to_a.shuffle[0,8].join. Note that shuffle requires Ruby > 1.9.

Comments

3

Another trick that works with Ruby 1.8+ and is fast is:

>> require "openssl"
>> OpenSSL::Random.random_bytes(20).unpack('H*').join
=> "2f3ff53dd712ba2303a573d9f9a8c1dbc1942d28"

It get's you random hex string. Similar way you should be able to generate base64 string ('M*').

Comments

3

2 solutions for a random string consisting of 3 ranges:

(('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)*""

One Character from each Range.

And if you need at least one character from each range, such as creating a random password that has one uppercase, one lowercase letter and one digit, you can do something like this:

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

1 Comment

And if you want to enforce a certain number of each range, you could do something like this: ( ('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"

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.