Cómo generar una cadena aleatoria en Ruby


747

Actualmente estoy generando una cadena mayúscula pseudoaleatoria de 8 caracteres para "A" .. "Z":

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

pero no se ve limpio, y no se puede pasar como argumento ya que no es una sola declaración. Para obtener una cadena de mayúsculas y minúsculas "a" .. "z" más "A" .. "Z", la cambié a:

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

Pero parece basura.

¿Alguien tiene un mejor método?


No entiendo por qué te importa que "dado que no es una sola declaración, no se puede pasar como argumento". ¿Por qué no simplemente convertirlo en una utilidad o método auxiliar?
David J.

1
Supongamos que hay un método para restablecer la contraseña de un usuario y tiene un argumento para la nueva contraseña. Me gustaría pasar una cadena aleatoria, en el código anterior necesito una variable tmp, mientras que en los ejemplos de declaraciones individuales a continuación puedo hacer todo como una sola línea. Claro que un método de utilidad podría ser bueno a largo plazo, especialmente si necesito algo similar aquí y allá, pero a veces solo lo quieres en su lugar, una vez, listo.
Jeff

No, no tiene que usar una variable temporal. Prueba esto: reset_user_password!(random_string)dondedef random_string; SecureRandom.urlsafe_base64(20) end
David J.

8 letras es una contraseña vergonzosamente débil. Dado el md5sum, una PC moderna podría recuperar la contraseña en 30 segundos . ¿Qué tal algo más?securerandom.urlsafe_base64
Coronel Panic

1
¿Por qué esto tiene tantas respuestas? No es que no sea una pregunta útil, pero tengo curiosidad por cómo ha llamado la atención.
Lou

Respuestas:


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

Paso demasiado tiempo jugando al golf.

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

Y una última que es aún más confusa, pero más flexible y desperdicia menos ciclos:

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

205
34 caracteres y rápida ardiente: ('a'..'z').to_a.shuffle[0,8].join. Tenga en cuenta que necesitará Ruby> = 1.9 para shuffle.
fny

20
Es preferible aprovechar las bibliotecas existentes a menos que tenga un controlador para rodar el suyo. Ver SecureRandomcomo un ejemplo, en las otras respuestas.
David J.

28
@faraz su método no es funcionalmente el mismo, no es aleatorio con reemplazo.
michaeltwofish

35
[*('a'..'z'),*('0'..'9')].shuffle[0,8].joinpara generar una cadena aleatoria con letras y números.
Robin

31
randEs determinista y predecible. ¡No use esto para generar contraseñas! Use una de las SecureRandomsoluciones en su lugar.
lápiz

800

¿Por qué no usar SecureRandom?

require 'securerandom'
random_string = SecureRandom.hex

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

SecureRandom también tiene métodos para:

  • base64
  • random_bytes
  • número aleatorio

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


11
base64 lo haría, pero no hexadecimal como en su ejemplo
Jeff Dickey el

67
Por cierto, es parte de la stdlib en 1.9 y 1.8 versiones recientes, por lo que uno puede simplemente require 'securerandom'para obtener esta ordenada SecureRandomde ayuda :)
J -_- L

21
BTW SecureRandom se eliminó de ActiveSupport en la versión 3.2. Del registro de cambios: "Eliminado ActiveSupport :: SecureRandom a favor de SecureRandom de la biblioteca estándar".
Marc

15
SecureRandom.random_number(36**12).to_s(36).rjust(12, "0")generará una cadena con 0-9a-z (36 caracteres) que SIEMPRE tiene 12 caracteres de longitud. Cambie 12 a la longitud que desee. Desafortunadamente, no hay forma de obtener AZ usando Integer#to_s.
Gerry Shaw

1
@ stringo0 eso está mal. Si desea pasar a +fGH1través de una URL, solo necesita codificarla como si fuera CUALQUIER valor que esté pasando por una URL: %2BfGH1
nzifnab

247

Lo uso para generar cadenas aleatorias de URL amigables con una longitud máxima garantizada:

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

Genera cadenas aleatorias de minúsculas az y 0-9. No es muy personalizable, pero es corto y limpio.


44
+1 para la versión más corta (que no llama binarios externos ^^). Si la cadena aleatoria no es pública, a veces incluso uso rand.to_s; feo, pero funciona.
Jo Liss

12
Esta es una gran solución (y también rápida), pero ocasionalmente producirá una cadena de longitud inferior length , aproximadamente una vez en ~ 40
Brian E

77
@ Brian E este garantizaría los dígitos que desea: (36**(length-1) + rand(36**length)).to_s(36). 36 ** (longitud-1) convertido a base 36 es 10 ** (longitud-1), que es el valor más pequeño que tiene la longitud de dígitos que desea.
Eric Hu

11
Aquí está la versión que siempre produce tokens de la longitud deseada:(36**(length-1) + rand(36**length - 36**(length-1))).to_s(36)
Adrien Jarthon

3
Esto me arroja un error en Rails 4 y Ruby 2.1.1: NameError: undefined local variable or method longitud 'para main: Object`
kakubei

175

Esta solución genera una cadena de caracteres fácilmente legibles para los códigos de activación; No quería que las personas confundieran 8 con B, 1 con I, 0 con O, L con 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

3
¿Es 'U' ambiguo o es un error tipográfico?
GMT

10
@gtd - Sí. U y V son ambigvovs.
colinm

1
Sin embargo, @colinm V está allí.
GTD

8
Para estar seguro, también querrá usar en SecureRandom.random_number(charset.size)lugar derand(charset.size)
gtd

1
Solo estaba tratando de mejorar esto, agregando minúsculas y / o algunos caracteres especiales (shift + num), y obtuve las siguientes listas: %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 ! @ # $ % ^ & * +}y %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 ! @ # $ % ^ & * }(la primera lista no incluye minúsculas, pero es más larga) Un poco interesante cómo eso funcionó.
dkniffin

130

Otros han mencionado algo similar, pero esto utiliza la función segura de URL.

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

El resultado puede contener AZ, az, 0-9, "-" y "_". "=" También se usa si el relleno es verdadero.


Esto es exactamente lo que estaba buscando. ¡Gracias!
Dan Williams

69

Desde Ruby 2.5, es realmente fácil con SecureRandom.alphanumeric:

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

Genera cadenas aleatorias que contienen AZ, az y 0-9 y, por lo tanto, deberían ser aplicables en la mayoría de los casos de uso. Y se generan aleatoriamente seguros, lo que también podría ser un beneficio.


Este es un punto de referencia para compararlo con la solución que tiene más votos a favor:

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)

Entonces, la randsolución solo toma alrededor de 3/4 del tiempo de SecureRandom. Eso podría importar si genera muchas cadenas, pero si solo crea alguna cadena aleatoria de vez en cuando, siempre iría con la implementación más segura, ya que también es más fácil de llamar y más explícita.


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

Genere una cadena aleatoria de 8 letras (por ejemplo, NVAYXHGR)

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

Genere una cadena aleatoria de 8 caracteres (por ejemplo, 3PH4SWF2), excluye 0/1 / I / O. Ruby 1.9


44
El único problema es que cada personaje en el resultado es único. Limita los posibles valores.
tybro0103

1
Si esta solicitud de función se realiza, Ruby 1.9.x puede terminar con #sample para muestreo sin reemplazo y #choice para muestreo con reemplazo.
David J.

Esto es un error, creo que necesitas ... [*("A".."Z")]'; ((no solo qoutes))
jayunit100

¿Me puede decir cómo puedo stubesto para rspecpasar.
Sinscary

31

No recuerdo dónde encontré esto, pero me parece el mejor y el menos intenso proceso:

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


¿No faltan 0 y 1 en esto?
sunnyrjuneja 16/03/2013

Parece que eso podría ser donde lo encontré.
Travis Reeder

Y sí, parece que faltan 0 y 1.
Travis Reeder

44
Los 0y 1y Oy Ifaltaron intencionalmente porque esos personajes son ambiguos. Si se usa este tipo de código para generar un conjunto de caracteres que un usuario necesita copiar, es mejor excluir los caracteres que pueden ser difíciles de distinguir fuera de contexto.
Andrew

28
require 'securerandom'
SecureRandom.urlsafe_base64(9)

SecureRandom es una excelente lib. No sabía que estaba allí. Gracias.
Dogweather

vieja alternativa de skool (y más fea):Random.new.bytes(9)
tardate

44
por cierto, urlsafe_base64 devuelve una cadena de aproximadamente 4/3 de la longitud indicada. Para obtener una cadena exactamente n caracteres largos, intenten=9 ; SecureRandom.urlsafe_base64(n)[0..n-1]
tarda

25

Si desea una cadena de longitud especificada, use:

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

Generará una cadena aleatoria de longitud que 2ncontiene 0-9ya-f


No genera una cadena de longitud n, en realidad genera una cadena de 4/3 de n.
Omar Ali

@OmarAli te equivocas. Según la documentación de Ruby en caso de .hexque sea 2n. Documentación: SecureRandom.hex genera una cadena hexadecimal aleatoria. El argumento n especifica la longitud, en bytes, del número aleatorio que se generará. La longitud de la cadena hexadecimal resultante es el doble de n .
Rihards


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

Interesante, pero un poco más costoso desde el punto de vista computacional
Jeff

Tampoco hay capacidad para el alcance limitado de caracteres.
Kent Fredric

3
Tenga en cuenta que un resumen hexadecimal devuelve solo 0-9 y caracteres af.
webmat

11

Aquí hay un código simple de una línea para una cadena aleatoria con longitud 8:

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

También puede usarlo para una contraseña aleatoria que tenga una longitud 8:

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

2
No debe usar esto para generar una contraseña, ya que este método nunca repetirá un carácter. Por lo tanto, solo está utilizando P(36, 8) / 36^8 = 0.4el espacio de caracteres posible para 8 caracteres (~ 2x más fácil para la fuerza bruta) o P(36, 25) / 36^25 = 0.00001del espacio de caracteres posible para 25 caracteres (~ 100,000x más fácil para la fuerza bruta).
Glaciales

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
¡La mapsolución es realmente buena!
Misha Moroshko

Puede preguntar #samplecuántos elementos desea. Por ejemplo ALPHABET.sample(10).join... ruby-doc.org/core-2.4.0/Array.html#method-i-sample
odlp

Eso está realmente mal. sample(10)te da 10 muestras únicas. Pero quieres permitir que se repitan. Pero lo usaría Array.new(10).mappara el rendimiento
Saša Zejnilović

Quería alfanumérico con minúsculas y mayúsculas. También he cambiado a usar Array.newy su sintaxis de bloque. Array.new(20) { [*'0'..'9', *'a'..'z', *'A'..'Z'].sample }.join
taylorthurlow

8

Tenga en cuenta: randes predecible para un atacante y, por lo tanto, probablemente inseguro. Definitivamente debe usar SecureRandom si esto es para generar contraseñas. Yo uso algo como esto:

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

Esta es probablemente la solución "más" segura. SecureRandom intenta utilizar las API de seguridad subyacentes proporcionadas por el sistema operativo. Si tiene OpenSSL lo usará, si está en Windows, será la mejor opción allí. Me gusta especialmente esta solución porque le permite especificar un conjunto de caracteres para su uso. Aunque no funcionará si su conjunto de caracteres es más largo que el valor máximo de un byte: 255. Recomiendo ver el código fuente de SecureRandom en el documento: ruby-doc.org/stdlib-1.9.3/libdoc/securerandom/ rdoc / ...
Breedly

8

Aquí hay un código simple para contraseña aleatoria con longitud 8:

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

7

Otro método que me gusta usar:

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

Agregue ljustsi está realmente paranoico sobre la longitud correcta de la cadena:

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

Aún mejor para obtener la parte menos significativa del número aleatorio usando el lado derecho de la cadena: rand (2 ** 64) .to_s (36) [- 10,10]
Jeff

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

Algo de idear


¿Por qué reemplaza el uso de caracteres de cadena .tr('+/=lIO0', 'pqrsxyz')?
miguelcobain

Los caracteres especiales porque no son seguros para URL. Y l / I u O / 0 porque son muy fáciles de confundir si usa la técnica para generar contraseñas de usuario legibles.
ToniTornado

Esa función tiene un poco de sesgo hacia ciertos personajes. También para otras longitudes (por ejemplo, 16), el último carácter no será aleatorio. Aquí hay una manera de evitar eso. SecureRandom.base64 (64) .tr ('+ / = lIO01', '') [0,16]
Shai Coleman el

5

Creo que este es un buen equilibrio de concisión, claridad y facilidad de modificación.

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

Modificado fácilmente

Por ejemplo, incluidos los dígitos:

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

Hexadecimal mayúscula:

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

Para una variedad realmente impresionante de personajes:

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

1
recomendaría esto para usar solo letras mayúsculas + números, también eliminar los "confusos" charset = (1..9) .to_a.concat (('A' .. 'Z'). to_a) .reject {| a El | [0, 1, 'O', 'I']. Include? (A)} (0 ... size) .map {charset [rand (charset.size)]} .join
lustre

5

Solo agrego mis centavos aquí ...

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

3
NB: esto no siempre devuelve una cadena exactamente + longitud + longitud, puede ser más corta. Depende del número devuelto porrand
tardate

5

Puedes usar String#randomdesde Facets of Ruby Gemfacets .

Básicamente hace esto:

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

Mi favorito es (:A..:Z).to_a.shuffle[0,8].join. Tenga en cuenta que barajar requiere Ruby> 1.9.


4

Esta solución necesita dependencia externa, pero parece más bonita que otra.

  1. Instalar falsificador de gemas
  2. Faker::Lorem.characters(10) # => "ang9cbhoa8"

4

Dado:

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

La expresión única, se puede pasar como argumento, permite caracteres duplicados:

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

3

Me gusta más la respuesta de Radar, hasta ahora, creo. Ajustaría un poco así:

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

¿Por qué no usar (CHARS*length).sample(length).join?
niels

@niels Esta sugerencia generaría una cadena ponderada a favor de caracteres no repetidos. Por ejemplo, si CHARS=['a','b']su método generaría "aa"o "bb"solo el 33% del tiempo, "ab"o el "ba"67% del tiempo. Tal vez eso no sea un problema, ¡pero vale la pena tenerlo en cuenta!
Tom Lord

Buen punto, @TomLord, creo que en realidad no me di cuenta de eso cuando publiqué esa sugerencia (aunque debo admitir que no recuerdo haber publicado eso en absoluto: D)
niels

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

3

Mis 2 centavos:

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


3

2 soluciones para una cadena aleatoria que consta de 3 rangos:

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

Un personaje de cada rango.

Y si necesita al menos un carácter de cada rango, como crear una contraseña aleatoria que tenga una letra mayúscula, una letra minúscula y un dígito, puede hacer algo como esto:

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

Y si desea aplicar un cierto número de cada rango, puede hacer algo como esto:( ('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

Estaba haciendo algo como esto recientemente para generar una cadena aleatoria de 8 bytes a partir de 62 caracteres. Los caracteres fueron 0-9, az, AZ. Tuve una matriz de ellos, ya que estaba haciendo un bucle 8 veces y seleccionando un valor aleatorio de la matriz. Esto estaba dentro de una aplicación Rails.

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

Lo extraño es que obtuve una buena cantidad de duplicados. Ahora al azar esto casi nunca debería suceder. 62 ^ 8 es enorme, pero de 1200 más o menos códigos en la base de datos tuve una buena cantidad de duplicados. Me di cuenta de que ocurrían en límites de horas entre sí. En otras palabras, podría ver un duplicado a las 12:12:23 y 2:12:22 o algo así ... no estoy seguro si el tiempo es el problema o no.

Este código estaba en la creación anterior de un objeto ActiveRecord. Antes de que se creara el registro, este código se ejecutaría y generaría el código 'único'. Las entradas en el DB siempre se produjeron de manera confiable, pero el código (str en la línea anterior) se duplicaba con demasiada frecuencia.

Creé un script para ejecutar 100000 iteraciones de esta línea anterior con un pequeño retraso, por lo que tomaría de 3 a 4 horas con la esperanza de ver algún tipo de patrón de repetición por hora, pero no vi nada. No tengo idea de por qué sucedía esto en mi aplicación Rails.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.