Come generare una stringa casuale in Ruby


747

Attualmente sto generando una stringa maiuscola pseudo-casuale di 8 caratteri per "A" .. "Z":

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

ma non sembra pulito e non può essere passato come argomento poiché non è una singola istruzione. Per ottenere una stringa in maiuscolo "a" .. "z" più "A" .. "Z", l'ho cambiata in:

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

ma sembra spazzatura.

Qualcuno ha un metodo migliore?


Non capisco perché ti interessi che "dal momento che non è una singola affermazione non può essere passato come argomento". Perché non renderlo solo un metodo di utilità o di aiuto?
David J.

1
Supponiamo che esista un metodo per reimpostare la password di un utente e che abbia un argomento per la nuova password. Vorrei passare una stringa casuale, nel codice sopra ho bisogno di una variabile tmp, mentre negli esempi a dichiarazione singola qui sotto posso fare tutto come un solo liner. Certo un metodo di utilità potrebbe essere bello a lungo termine, specialmente se ho bisogno di qualcosa di simile qua e là, ma a volte lo vuoi solo sul posto, una volta, fatto.
Jeff,

No, non è necessario utilizzare una variabile temporanea. Prova questo: reset_user_password!(random_string)dovedef random_string; SecureRandom.urlsafe_base64(20) end
David J.

8 lettere è una password vergognosamente debole. Dato il md5sum un PC moderno potrebbe recuperare la password in 30 secondi . Che ne dici di qualcosa di piùsecurerandom.urlsafe_base64
colonnello Panic il

1
Perché questo ha così tante risposte? Non che non sia una domanda utile, ma sono curioso di come abbia attirato l'attenzione.
Lou,

Risposte:


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

Passo troppo tempo a giocare a golf.

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

E l'ultimo che è ancora più confuso, ma più flessibile e spreca meno cicli:

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

205
34 caratteri e velocissimo: ('a'..'z').to_a.shuffle[0,8].join. Nota che avrai bisogno di Ruby> = 1,9 a shuffle.
fny

20
Sfruttare le librerie esistenti è preferibile a meno che non si disponga di un driver per il roll-up delle proprie. Vedi SecureRandomcome un esempio, nelle altre risposte.
David J.

28
@faraz il tuo metodo non è funzionalmente uguale, non è casuale con la sostituzione.
michaeltwofish,

35
[*('a'..'z'),*('0'..'9')].shuffle[0,8].joinper generare una stringa casuale con lettere e numeri.
Robin

31
randè deterministico e prevedibile. Non usarlo per generare password! Utilizzare invece una delle SecureRandomsoluzioni.
matita

800

Perché non usare SecureRandom?

require 'securerandom'
random_string = SecureRandom.hex

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

SecureRandom ha anche metodi per:

  • Base64
  • random_bytes
  • numero casuale

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


11
base64 sarebbe, ma non esadecimale come nel suo esempio
Jeff Dickey

67
A proposito, fa parte dello stdlib nelle versioni 1.9 e 1.8 recenti, quindi si può solo require 'securerandom'ottenere questo SecureRandomaiutante pulito :)
J -_- L

21
BTW SecureRandom è stato rimosso da ActiveSupport nella versione 3.2. Dal log delle modifiche: "Rimosso ActiveSupport :: SecureRandom a favore di SecureRandom dalla libreria standard".
Marc

15
SecureRandom.random_number(36**12).to_s(36).rjust(12, "0")genererà una stringa con 0-9a-z (36 caratteri) che è SEMPRE lunga 12 caratteri. Cambia 12 con la lunghezza che desideri. Sfortunatamente non c'è modo di usare AZ Integer#to_s.
Gerry Shaw,

1
@ stringo0 che è sbagliato. Se vuoi passare +fGH1attraverso un URL, devi solo codificarlo come faresti QUALSIASI valore che passa attraverso un URL: %2BfGH1
nzifnab

247

Lo uso per generare stringhe casuali compatibili con URL con una lunghezza massima garantita:

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

Genera stringhe casuali di az minuscole e 0-9. Non è molto personalizzabile ma è corto e pulito.


4
+1 per la versione più breve (che non chiama binari esterni ^^). Se la stringa casuale non è rivolta al pubblico, a volte uso anche solo rand.to_s; brutto, ma funziona.
Jo Liss,

12
Questa è una grande soluzione (e veloce, anche), ma a volte produrre una stringa di sotto length di lunghezza, circa una volta in ~ 40
Brian E

7
@ Brian E questo garantirebbe alle cifre da inserire: (36**(length-1) + rand(36**length)).to_s(36). 36 ** (lunghezza-1) convertito in base 36 è 10 ** (lunghezza-1), che è il valore più piccolo che ha la lunghezza della cifra desiderata.
Eric Hu,

11
Ecco la versione che produce sempre token della lunghezza desiderata:(36**(length-1) + rand(36**length - 36**(length-1))).to_s(36)
Adrien Jarthon

3
Questo sputa un errore per me in Rails 4 e Ruby 2.1.1: NameError: undefined local variable or method lunghezza "per main: Object"
kakubei

175

Questa soluzione genera una stringa di caratteri facilmente leggibili per i codici di attivazione; Non volevo che le persone confondessero 8 con B, 1 con I, 0 con O, L con 1, ecc.

# 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" è ambiguo o è un errore di battitura?
gtd

10
@gtd - Sì. U e V sono ambigvov.
colinm,

1
@colinm V è lì dentro però.
gtd

8
Per essere sicuri che si potrebbe anche voler utilizzare SecureRandom.random_number(charset.size)al posto dirand(charset.size)
GTD

1
Stavo solo cercando di migliorare questo, aggiungendo caratteri minuscoli e / o alcuni caratteri speciali (shift + num), e ho ottenuto i seguenti elenchi: %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 ! @ # $ % ^ & * +}e %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 ! @ # $ % ^ & * }(il primo elenco non include lettere minuscole, ma è più lungo) allenato.
dkniffin,

130

Altri hanno menzionato qualcosa di simile, ma questo utilizza la funzione sicura URL.

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

Il risultato può contenere AZ, az, 0-9, “-” e “_”. “=” Viene utilizzato anche se il riempimento è vero.


Questo e 'esattamente quello che stavo cercando. Grazie!
Dan Williams,

69

Da Ruby 2.5, è davvero facile con SecureRandom.alphanumeric:

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

Genera stringhe casuali contenenti AZ, az e 0-9 e pertanto dovrebbe essere applicabile nella maggior parte dei casi d'uso. E sono generati in modo casuale sicuro, il che potrebbe anche essere un vantaggio.


Questo è un punto di riferimento per confrontarlo con la soluzione con il maggior numero di voti:

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)

Quindi la randsoluzione richiede solo circa 3/4 del tempo di SecureRandom. Ciò potrebbe importare se generi molte stringhe, ma se di tanto in tanto creo qualche stringa casuale, andrei sempre con l'implementazione più sicura poiché è anche più facile da chiamare ed più esplicito.


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

Genera una stringa casuale di 8 lettere (ad esempio NVAYXHGR)

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

Genera una stringa casuale di 8 caratteri (ad es. 3PH4SWF2), esclude 0/1 / I / O. Ruby 1.9


4
L'unico problema è che ogni personaggio nel risultato è unico. Limita i possibili valori.
tybro0103,

1
Se questa richiesta di funzionalità viene soddisfatta, Ruby 1.9.x potrebbe finire con #sample per il campionamento senza sostituzione e #choice per il campionamento con sostituzione.
David J.

Questo è un errore, penso che tu abbia bisogno di ... [*("A".."Z")]'; ((non singoli Qoutes))
jayunit100,

puoi dirmi come posso farlo stubper rspecpassare.
Sinscary,

31

Non riesco a ricordare dove l'ho trovato, ma mi sembra il processo migliore e meno intenso:

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


Non manca uno 0 e 1 da questo?
Sunnyrjuneja,

Sembra che potrebbe essere dove l'ho trovato.
Travis Reeder

E sì, sembra che manchino 0 e 1.
Travis Reeder

4
L' 0e 1ed Oe Isono stati intenzionalmente manca perché quei personaggi sono ambigue. Se questo tipo di codice viene utilizzato per generare un set di caratteri che un utente deve copiare, è meglio escludere caratteri che potrebbero essere difficili da distinguere fuori dal contesto.
Andrew,

28
require 'securerandom'
SecureRandom.urlsafe_base64(9)

SecureRandom è un'eccellente libreria. Non sapevo che fosse lì. Grazie.
Dogweather,

vecchia alternativa skool (e più brutta):Random.new.bytes(9)
tardate

4
btw, urlsafe_base64 restituisce una stringa di circa 4/3 della lunghezza indicata. Per ottenere una stringa esattamente lunga n caratteri, provan=9 ; SecureRandom.urlsafe_base64(n)[0..n-1]
tardare il

25

Se si desidera una stringa di lunghezza specificata, utilizzare:

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

Genererà una stringa casuale di lunghezza 2ncontenente 0-9ea-f


Non genera una stringa di lunghezza n, in realtà genera una stringa di 4/3 di n.
Omar Ali,

@OmarAli ti sbagli. Secondo la documentazione di Ruby in caso di .hex2n. Documentazione: SecureRandom.hex genera una stringa esadecimale casuale. L'argomento n specifica la lunghezza, in byte, del numero casuale da generare. La lunghezza della stringa esadecimale risultante è due volte di n .
Rihards,


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

Interessante, ma un po 'più costoso dal punto di vista computazionale
Jeff

Inoltre nessuna capacità per un limitato ambito di caratteri.
Kent Fredric,

3
Tieni presente che un digest esadecimale restituisce solo 0-9 e caratteri af.
webmat,

11

Ecco un codice semplice di una riga per stringa casuale con lunghezza 8:

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

Puoi anche usarlo per password casuali di lunghezza 8:

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

2
Non dovresti usarlo per generare una password, poiché questo metodo non ripeterà mai un carattere. Quindi stai usando solo P(36, 8) / 36^8 = 0.4il possibile spazio del personaggio per 8 caratteri (~ 2x più facile da forzare) o P(36, 25) / 36^25 = 0.00001del possibile spazio del personaggio per 25 caratteri (~ 100.000x più facile da forzare).
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
La mapsoluzione è davvero bella!
Misha Moroshko,

Puoi chiedere #samplequanti elementi vuoi. Ad esempio ALPHABET.sample(10).join... ruby-doc.org/core-2.4.0/Array.html#method-i-sample
odlp

Questo è in realtà sbagliato. sample(10)ti dà 10 campioni unici. Ma vuoi permettere loro di ripetere. Ma Array.new(10).map
userei

Volevo alfanumerico con lettere minuscole e maiuscole. Sono anche passato all'utilizzo Array.newe alla sua sintassi a blocchi. Array.new(20) { [*'0'..'9', *'a'..'z', *'A'..'Z'].sample }.join
taylorthurlow,

8

Attenzione: randè prevedibile per un aggressore e quindi probabilmente insicuro. Dovresti assolutamente usare SecureRandom se questo è per generare password. Uso qualcosa del genere:

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

Questa è probabilmente la soluzione "più" sicura. SecureRandom tenta di utilizzare le API di sicurezza sottostanti fornite dal sistema operativo. Se hai OpenSSL lo userà, se sei su Windows andrà l'opzione migliore lì. Mi piace soprattutto questa soluzione perché ti consente di specificare un set di caratteri da utilizzare. Sebbene non funzionerà se il set di caratteri è più lungo del valore massimo di un byte: 255. Consiglio di visualizzare il codice sorgente per SecureRandom nel documento: ruby-doc.org/stdlib-1.9.3/libdoc/securerandom/ rdoc / ...
Breedly,

8

Ecco un semplice codice per password casuale con lunghezza 8:

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

7

Un altro metodo che mi piace usare:

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

Aggiungi ljustse sei davvero paranoico sulla lunghezza della stringa corretta:

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

Ancora meglio per afferrare la parte meno significativa del numero casuale usando il lato destro della stringa: rand (2 ** 64) .to_s (36) [- 10,10]
Jeff

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

Qualcosa da Devise


Perché sta sostituendo i caratteri stringa usando .tr('+/=lIO0', 'pqrsxyz')?
miguelcobain,

I caratteri speciali perché non sono sicuri per gli URL. E l / I o O / 0 perché sono molto facili da confondere se si utilizza la tecnica per generare password utente leggibili.
ToniTornado,

Questa funzione ha un po 'di pregiudizio verso alcuni personaggi. Anche per altre lunghezze (ad es. 16), l'ultimo carattere non sarà casuale. Ecco un modo per evitarlo. SecureRandom.base64 (64) .tr ('+ / = lIO01', '') [0,16]
Shai Coleman

5

Penso che questo sia un buon equilibrio tra concisione, chiarezza e facilità di modifica.

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

Facilmente modificato

Ad esempio, comprese le cifre:

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

Esadecimale maiuscolo:

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

Per una gamma davvero impressionante di personaggi:

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

1
lo consiglierei per usare solo lettere maiuscole + numeri, anche per rimuovere quelli "confusi" 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
lustre

5

Sto solo aggiungendo i miei centesimi qui ...

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

3
NB: questo non restituisce sempre una stringa esattamente + lunghezza + lunghezza - potrebbe essere più corta. Dipende dal numero restituito darand
tardate

5

È possibile utilizzare String#randomdalle Sfaccettature di Ruby Gem facets.

Fondamentalmente lo fa:

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

Il mio preferito è (:A..:Z).to_a.shuffle[0,8].join. Nota che shuffle richiede Ruby> 1.9.


4

Questa soluzione richiede dipendenza esterna, ma sembra più bella di un'altra.

  1. Installa gemmafaker
  2. Faker::Lorem.characters(10) # => "ang9cbhoa8"

4

Dato:

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

L'espressione singola, può essere passata come argomento, consente caratteri duplicati:

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

3

Mi piace la risposta di Radar meglio, finora, penso. Modificherei un po 'così:

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

Perché non usare (CHARS*length).sample(length).join?
niels

@niels Questo suggerimento genererebbe una stringa ponderata , a favore di caratteri non ripetuti. Ad esempio, se CHARS=['a','b']quindi il tuo metodo generasse "aa"o "bb"solo il 33% delle volte, ma "ab"o il "ba"67% delle volte. Forse non è un problema, ma vale la pena ricordare!
Tom Lord,

buon punto, @TomLord, penso di non essermi reso conto che quando ho pubblicato quel suggerimento (anche se devo ammettere che non ricordo di averlo pubblicato affatto: D)
Niels

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

3

I miei 2 centesimi:

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


3

2 soluzioni per una stringa casuale composta da 3 intervalli:

(('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 personaggio per ogni intervallo.

E se hai bisogno di almeno un carattere per ogni intervallo, come la creazione di una password casuale con una lettera maiuscola, una lettera minuscola e una cifra, puoi fare qualcosa del genere:

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

E se vuoi imporre un certo numero di ogni intervallo, potresti fare qualcosa del genere:( ('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

Recentemente stavo facendo qualcosa del genere per generare una stringa casuale di 8 byte da 62 caratteri. I personaggi erano 0-9, az, AZ. Ne ho avuti un array come 8 volte in loop e selezionando un valore casuale dall'array. Questo era all'interno di un'app Rails.

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

La cosa strana è che ho ottenuto un buon numero di duplicati. Ora a caso questo non dovrebbe praticamente mai accadere. 62 ^ 8 è enorme, ma su circa 1200 codici nel db ho avuto un buon numero di duplicati. Ho notato che stavano accadendo a ore di distanza l'una dall'altra. In altre parole, potrei vedere un duplicato alle 12:12:23 e 2:12:22 o qualcosa del genere ... non sono sicuro che il problema sia il tempo.

Questo codice era nella creazione precedente di un oggetto ActiveRecord. Prima della creazione del record, questo codice veniva eseguito e generava il codice "univoco". Le voci nel DB venivano sempre prodotte in modo affidabile, ma il codice ( strnella riga sopra) veniva duplicato troppo spesso.

Ho creato uno script per eseguire 100000 iterazioni di questa riga sopra con un piccolo ritardo, quindi ci vorrebbero 3-4 ore sperando di vedere un qualche tipo di ripetizione su base oraria, ma non ho visto nulla. Non ho idea del perché ciò stesse accadendo nella mia app Rails.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.