Generating random number of length 6 with SecureRandom in Ruby
Asked Answered
D

5

8

I tried SecureRandom.random_number(9**6) but it sometimes returns 5 and sometimes 6 numbers. I'd want it to be a length of 6 consistently. I would also prefer it in the format like SecureRandom.random_number(9**6) without using syntax like 6.times.map so that it's easier to be stubbed in my controller test.

Dunigan answered 17/5, 2017 at 17:20 Comment(1)
Hint: 9**6 is 531441.Highroad
H
12

You can do it with math:

(SecureRandom.random_number(9e5) + 1e5).to_i

Then verify:

100000.times.map do
  (SecureRandom.random_number(9e5) + 1e5).to_i
end.map { |v| v.to_s.length }.uniq
# => [6]

This produces values in the range 100000..999999:

10000000.times.map do
  (SecureRandom.random_number(9e5) + 1e5).to_i
end.minmax
# => [100000, 999999]

If you need this in a more concise format, just roll it into a method:

def six_digit_rand
  (SecureRandom.random_number(9e5) + 1e5).to_i
end
Highroad answered 17/5, 2017 at 19:29 Comment(6)
Thanks, seems like (SecureRandom.random_number(9e5) + 1e5).to_i is what I am looking for.Dunigan
How to use this for generating random number of length n?Wiring
Use 9 * 10**(n-1) and 10**(n-1) instead.Highroad
Getting an error when trying to pass 9e5 as a parameter to random_number, although 10**6 works.Margaretmargareta
@Margaretmargareta You can always do it long_form: 1_000_000.Highroad
This appropriately answers the question as asked, but in case anyone else is looking to generate a six digit code (such as for SMS based MFA) see Eliot Sykes answer below for a more secure and comprehensive answer.Hornstone
H
13

To generate a random, 6-digit string:

# This generates a 6-digit string, where the
# minimum possible value is "000000", and the
# maximum possible value is "999999"
SecureRandom.random_number(10**6).to_s.rjust(6, '0')

Here's more detail of what's happening, shown by breaking the single line into multiple lines with explaining variables:

  # Calculate the upper bound for the random number generator
  # upper_bound = 1,000,000
  upper_bound = 10**6

  # n will be an integer with a minimum possible value of 0,
  # and a maximum possible value of 999,999
  n = SecureRandom.random_number(upper_bound)

  # Convert the integer n to a string
  # unpadded_str will be "0" if n == 0
  # unpadded_str will be "999999" if n == 999999
  unpadded_str = n.to_s

  # Pad the string with leading zeroes if it is less than
  # 6 digits long.
  # "0" would be padded to "000000"
  # "123" would be padded to "000123"
  # "999999" would not be padded, and remains unchanged as "999999"
  padded_str = unpadded_str.rjust(6, '0')
Horsepowerhour answered 12/6, 2019 at 14:6 Comment(0)
H
12

You can do it with math:

(SecureRandom.random_number(9e5) + 1e5).to_i

Then verify:

100000.times.map do
  (SecureRandom.random_number(9e5) + 1e5).to_i
end.map { |v| v.to_s.length }.uniq
# => [6]

This produces values in the range 100000..999999:

10000000.times.map do
  (SecureRandom.random_number(9e5) + 1e5).to_i
end.minmax
# => [100000, 999999]

If you need this in a more concise format, just roll it into a method:

def six_digit_rand
  (SecureRandom.random_number(9e5) + 1e5).to_i
end
Highroad answered 17/5, 2017 at 19:29 Comment(6)
Thanks, seems like (SecureRandom.random_number(9e5) + 1e5).to_i is what I am looking for.Dunigan
How to use this for generating random number of length n?Wiring
Use 9 * 10**(n-1) and 10**(n-1) instead.Highroad
Getting an error when trying to pass 9e5 as a parameter to random_number, although 10**6 works.Margaretmargareta
@Margaretmargareta You can always do it long_form: 1_000_000.Highroad
This appropriately answers the question as asked, but in case anyone else is looking to generate a six digit code (such as for SMS based MFA) see Eliot Sykes answer below for a more secure and comprehensive answer.Hornstone
A
1

One can simply pass a Range as an argument:

SecureRandom.random_number(100_000..999_999).to_s

It will always generate a string of random number, with a length of six characters.

Achene answered 22/8 at 12:28 Comment(0)
E
0

Docs to Ruby SecureRand, lot of cool tricks here.

Specific to this question I would say: (SecureRandom.random_number * 1000000).to_i

Docs: random_number(n=0)

If 0 is given or an argument is not given, ::random_number returns a float: 0.0 <= ::random_number < 1.0.

Then multiply by 6 decimal places (* 1000000) and truncate the decimals (.to_i)

If letters are okay, I prefer .hex:

SecureRandom.hex(3) #=> "e15b05"

Docs: hex(n=nil)

::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 n.

If n is not specified or is nil, 16 is assumed. It may be larger in future.

The result may contain 0-9 and a-f.

Other options:

  • SecureRandom.uuid #=> "3f780c86-6897-457e-9d0b-ef3963fbc0a8"
  • SecureRandom.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"

For Rails apps creating a barcode or uid with an object you can do something like this in the object model file:

before_create :generate_barcode

  def generate_barcode
    begin
      return if self.barcode.present?
      self.barcode = SecureRandom.hex.upcase
    end while self.class.exists?(barcode: barcode)
  end
Edgaredgard answered 16/11, 2022 at 19:36 Comment(0)
S
-2

SecureRandom.random_number(n) gives a random value between 0 to n. You can achieve it using rand function.

2.3.1 :025 > rand(10**5..10**6-1)
=> 742840

rand(a..b) gives a random number between a and b. Here, you always get a 6 digit random number between 10^5 and 10^6-1.

Silk answered 17/5, 2017 at 17:32 Comment(2)
Why are you recommending the non-secure rand instead? That's really a step backwards.Highroad
Yea agreed, I wouldn't use rand, I would stick to SecureRandomDunigan

© 2022 - 2024 — McMap. All rights reserved.