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?

Jeff
fuente
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
Kent Fredric
fuente
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

estilos de christophers
fuente
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.

Christoffer Möller
fuente
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
ImNotQuiteJack
fuente
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.

Travis R
fuente
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.

Markus
fuente
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

Tom Carr
fuente
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
Travis R
fuente
77
Tal vez lo encontraste aquí? travisonrails.com/2007/06/07/generate-random-text-with-ruby
deadwards
¿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)
Lenzcom
fuente
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

Srikanta Mahapatro
fuente
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]
Coren
fuente
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
Awais
fuente
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"
Ragmaanir
fuente
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
lápiz
fuente
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
Thaha kp
fuente
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]
usuario163365
fuente
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

Teej
fuente
¿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
Nathan Long
fuente
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
pduersteler
fuente
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
Tilo
fuente
4

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

Josh
fuente
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"
asiniy
fuente
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
Tim James
fuente
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
webmat
fuente
¿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} }
eric
fuente
3

Mis 2 centavos:

  def token(length=16)
    chars = [*('A'..'Z'), *('a'..'z'), *(0..9)]
    (0..length).map {chars.sample}.join
  end
tybro0103
fuente
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"
peter
fuente
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.

erik
fuente