Jak wygenerować losowy ciąg w Ruby

746

Obecnie generuję 8-znakowy pseudolosowy ciąg wielkich liter dla „A” .. „Z”:

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

ale nie wygląda na czysty i nie można go przekazać jako argumentu, ponieważ nie jest to pojedyncze stwierdzenie. Aby uzyskać ciąg „a” .. „z” plus „A” .. „Z”, zmieniłem go na:

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

ale wygląda jak śmieci.

Czy ktoś ma lepszą metodę?

Jeff
źródło
Nie rozumiem, dlaczego cię to obchodzi „ponieważ nie jest to pojedyncze stwierdzenie, nie można go przekazać jako argumentu”. Dlaczego nie uczynić go użytecznym lub pomocniczym?
David J.
1
Załóżmy, że istnieje metoda resetowania hasła użytkownika i argument za nowym hasłem. Chciałbym przekazać losowy ciąg, w powyższym kodzie potrzebuję zmiennej tmp, podczas gdy w przykładach pojedynczej instrukcji poniżej mogę zrobić wszystko jako jeden linijka. Pewnie, że metoda użyteczności może być dobra na dłuższą metę, szczególnie jeśli potrzebuję podobnych tu i tam, ale czasami po prostu chcesz to zrobić, raz.
Jeff
Nie, nie musisz używać zmiennej tymczasowej. Spróbuj tego: reset_user_password!(random_string)gdziedef random_string; SecureRandom.urlsafe_base64(20) end
David J.
8 liter to haniebnie słabe hasło. Biorąc pod uwagę md5sum, nowoczesny komputer może odzyskać hasło w 30 sekund . Co powiesz na coś więcejsecurerandom.urlsafe_base64
pułkownik Panic
1
Dlaczego ma tak wiele odpowiedzi? Nie chodzi o to, że nie jest to przydatne pytanie, ale jestem ciekawy, w jaki sposób przyciągnął uwagę.
Lou

Odpowiedzi:

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

Zbyt dużo czasu spędzam na grze w golfa.

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

I ostatni, jeszcze bardziej zagmatwany, ale bardziej elastyczny i marnujący mniej cykli:

o = [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
string = (0...50).map { o[rand(o.length)] }.join
Kent Fredric
źródło
204
34 znaków i błyskawicznie: ('a'..'z').to_a.shuffle[0,8].join. Uwaga: potrzebujesz Ruby> = 1,9 do shuffle.
fny
20
Korzystanie z istniejących bibliotek jest lepsze, chyba że masz sterownik do tworzenia własnych bibliotek. Zobacz SecureRandomjako jeden przykład w innych odpowiedziach.
David J.
28
@faraz twoja metoda nie jest funkcjonalnie taka sama, nie jest losowa z zamianą.
michaeltwofish,
35
[*('a'..'z'),*('0'..'9')].shuffle[0,8].joinaby wygenerować losowy ciąg zawierający zarówno litery, jak i cyfry.
Robin,
31
randjest deterministyczny i przewidywalny. Nie używaj tego do generowania haseł! SecureRandomZamiast tego użyj jednego z rozwiązań.
ołówek
800

Dlaczego nie skorzystać z SecureRandom?

require 'securerandom'
random_string = SecureRandom.hex

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

SecureRandom ma również metody:

  • base64
  • random_bytes
  • Liczba losowa

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

christopherstyles
źródło
11
base64 zrobiłby, ale nie hex jak w jego przykładzie
Jeff Dickey
67
Nawiasem mówiąc, jest częścią stdlib w wersji 1.9 i najnowszych wersjach 1.8, więc można po prostu require 'securerandom'zdobyć tego schludnego SecureRandompomocnika :)
J -_- L
21
BTW SecureRandom został usunięty z ActiveSupport w wersji 3.2. Z dziennika zmian: „Usunięto ActiveSupport :: SecureRandom na rzecz SecureRandom ze standardowej biblioteki”.
Marc
15
SecureRandom.random_number(36**12).to_s(36).rjust(12, "0")wygeneruje ciąg z 0-9a-z (36 znaków), który ZAWSZE ma 12 znaków. Zmień 12 na dowolną długość. Niestety nie ma sposobu, aby po prostu użyć AZ Integer#to_s.
Gerry Shaw
1
@ stringo0, który jest zły. Jeśli chcesz przekazać +fGH1adres URL, musisz go zakodować, tak jak %2BfGH1
DOWOLNA
247

Używam tego do generowania ciągów przyjaznych losowym adresom URL o gwarantowanej maksymalnej długości:

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

Generuje losowe ciągi małych liter az i 0-9. Nie jest bardzo konfigurowalny, ale jest krótki i czysty.

Christoffer Möller
źródło
4
+1 za najkrótszą wersję (która nie wywołuje zewnętrznych plików binarnych ^^). Jeśli losowy ciąg nie jest publicznie dostępny, czasami nawet po prostu używam rand.to_s; brzydkie, ale działa.
Jo Liss,
12
To świetne rozwiązanie (i również szybkie), ale od czasu do czasu produkuje sznur poniżej length długości, mniej więcej raz na ~ 40
Brian E
7
@Brian E to zagwarantuje cyfry chcesz: (36**(length-1) + rand(36**length)).to_s(36). 36 ** (długość-1) przeliczona na podstawę 36 wynosi 10 ** (długość-1), co jest najmniejszą wartością o żądanej długości cyfr.
Eric Hu,
11
Oto wersja zawsze produkująca żetony o pożądanej długości:(36**(length-1) + rand(36**length - 36**(length-1))).to_s(36)
Adrien Jarthon
3
To wyrzuca mi błąd w Railsach 4 i Ruby 2.1.1: NameError: undefined local variable or method length 'for main: Object`
kakubei
175

To rozwiązanie generuje ciąg łatwo czytelnych znaków dla kodów aktywacyjnych; Nie chciałem, żeby ludzie mylili 8 z B, 1 z I, 0 z O, L z 1 itd.

# 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
źródło
3
Czy „U” jest niejednoznaczne, czy to literówka?
gtd
10
@gtd - Tak. U i V są ambigvovs.
colinm
1
@ colinm V jest tam.
gtd
8
Dla bezpieczeństwa powinieneś także użyć SecureRandom.random_number(charset.size)zamiastrand(charset.size)
gtd 10.10.13
1
Właśnie próbowałem to poprawić, dodając małe litery i / lub niektóre znaki specjalne (shift + num), i otrzymałem następujące listy: %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 ! @ # $ % ^ & * +}i %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 ! @ # $ % ^ & * }(pierwsza lista nie zawiera małych liter, ale jest dłuższa) Trochę interesujące, jak to wypracowane.
dkniffin
130

Inni wspominali o czymś podobnym, ale używa to bezpiecznej funkcji adresu URL.

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

Wynik może zawierać AZ, az, 0-9, „-” i „_”. „=” Jest także używane, jeśli wypełnienie jest prawdziwe.

Travis R.
źródło
Właśnie tego szukałem. Dzięki!
Dan Williams
69

Od Ruby 2.5 jest to naprawdę łatwe dzięki SecureRandom.alphanumeric:

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

Generuje losowe ciągi zawierające AZ, az i 0-9 i dlatego powinno mieć zastosowanie w większości przypadków użycia. I są generowane losowo bezpieczne, co również może być zaletą.


Jest to punkt odniesienia w porównaniu z rozwiązaniem mającym najwięcej pozytywnych opinii:

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)

Tak więc randrozwiązanie zajmuje tylko około 3/4 czasu SecureRandom. Może to mieć znaczenie, jeśli wygenerujesz wiele ciągów, ale jeśli od czasu do czasu utworzysz jakiś losowy ciąg, zawsze będę korzystał z bezpieczniejszej implementacji, ponieważ łatwiej jest także wywoływać i bardziej jawnie.

Markus
źródło
48
[*('A'..'Z')].sample(8).join

Wygeneruj losowy ciąg 8 liter (np. NVAYXHGR)

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

Wygeneruj losowy ciąg 8 znaków (np. 3PH4SWF2), z wyłączeniem 0/1 / I / O. Ruby 1.9

Tom Carr
źródło
4
Jedynym problemem jest to, że każda postać w wyniku jest unikalna. Ogranicza możliwe wartości.
tybro0103
1
Jeśli żądanie funkcji zostanie zrealizowane, Ruby 1.9.x może skończyć na #sample do próbkowania bez zamiany i #choice do próbkowania z zamianą.
David J.
To jest błąd, myślę, że potrzebujesz ... [*("A".."Z")]'; ((nie pojedyncze kwity))
jayunit100
czy możesz mi powiedzieć, jak mogę stubto rspecprzekazać.
Sinscary,
31

Nie pamiętam, gdzie to znalazłem, ale wydaje mi się, że to najlepszy i najmniej wymagający proces:

def random_string(length=10)
  chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ0123456789'
  password = ''
  length.times { password << chars[rand(chars.size)] }
  password
end
Travis R.
źródło
7
Być może znalazłeś to tutaj? travisonrails.com/2007/06/07/generate-random-text-with-ruby
deadwards
Czy nie brakuje w tym 0 i 1?
sunnyrjuneja
Wygląda na to, że tam właśnie go znalazłem.
Travis Reeder,
I wygląda na to, że brakuje 0 i 1.
Travis Reeder,
4
0I 1i Oi Izostały celowo brakuje, ponieważ te znaki są niejednoznaczne. Jeśli ten rodzaj kodu jest używany do generowania zestawu znaków, które użytkownik musi skopiować, najlepiej jest wykluczyć znaki, które mogą być trudne do odróżnienia poza kontekstem.
Andrew
28
require 'securerandom'
SecureRandom.urlsafe_base64(9)
LENZCOM
źródło
SecureRandom to doskonała biblioteka lib. Nie wiedziałem, że tam jest. Dzięki.
Dogweather
stara skool (i brzydsza) alternatywa:Random.new.bytes(9)
opóźnienie
4
btw, urlsafe_base64 zwraca ciąg o 4/3 podanej długości. Aby uzyskać ciąg o długości dokładnie n znaków, spróbujn=9 ; SecureRandom.urlsafe_base64(n)[0..n-1]
tardate
25

Jeśli chcesz ciąg o określonej długości, użyj:

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

Wygeneruje losowy ciąg długości 2nzawierający 0-9ia-f

Srikanta Mahapatro
źródło
Nie generuje ciągu o długości n, w rzeczywistości generuje ciąg, który ma 4/3 długości n.
Omar Ali
@OmarAli się mylisz. Zgodnie z dokumentacją Ruby w przypadku .hexjego 2n. Dokumentacja: SecureRandom.hex generuje losowy ciąg szesnastkowy. Argument n określa długość (w bajtach) losowej liczby do wygenerowania. Długość wynikowego ciągu szesnastkowego jest dwa razy większa od n .
Rihards
11
require 'sha1'
srand
seed = "--#{rand(10000)}--#{Time.now}--"
Digest::SHA1.hexdigest(seed)[0,8]
Coren
źródło
Ciekawe, ale nieco droższe obliczeniowo
Jeff
Brak możliwości ograniczonego zakresu znaków.
Kent Fredric
3
Pamiętaj, że skrót heksadecymalny zwraca tylko 0-9 znaków af.
webmat
11

Oto prosty, jednoliniowy kod losowego ciągu o długości 8:

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

Możesz go również użyć do losowego hasła o długości 8:

random_password = ('0'..'z').to_a.shuffle.first(8).join
Awais
źródło
2
Nie należy tego używać do generowania hasła, ponieważ ta metoda nigdy nie powtórzy znaku. Dlatego używasz tylko P(36, 8) / 36^8 = 0.4możliwej przestrzeni znaków dla 8 znaków (~ 2x łatwiejsza brutalna siła) lub P(36, 25) / 36^25 = 0.00001możliwej przestrzeni postaci dla 25 znaków (~ 100 000x łatwiejszej brutalnej siły).
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"
Ragmaanir
źródło
1
mapRozwiązanie jest naprawdę ładny!
Misha Moroshko,
Możesz zapytać, #sampleile elementów chcesz. Np. ALPHABET.sample(10).join... ruby-doc.org/core-2.4.0/Array.html#method-i-sample
odlp
To jest właściwie złe. sample(10)daje 10 unikalnych próbek. Ale chcesz pozwolić im się powtórzyć. Ale chciałbym użyć Array.new(10).mapdo wykonania
Saša Zejnilović
Chciałem alfanumeryczny z małymi i dużymi literami. Przełączyłem się także na use Array.newi jego składnię bloków. Array.new(20) { [*'0'..'9', *'a'..'z', *'A'..'Z'].sample }.join
taylorthurlow,
8

Bądź świadomy: randjest przewidywalny dla atakującego i dlatego prawdopodobnie niepewny. Zdecydowanie powinieneś użyć SecureRandom, jeśli jest to do generowania haseł. Używam czegoś takiego:

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
ołówek
źródło
Jest to prawdopodobnie „najbardziej” bezpieczne rozwiązanie. SecureRandom próbuje użyć podstawowych API bezpieczeństwa dostarczonych przez system operacyjny. Jeśli masz OpenSSL, skorzysta z niego, jeśli korzystasz z systemu Windows, wybierze tam najlepszą opcję. Szczególnie podoba mi się to rozwiązanie, ponieważ umożliwia określenie zestawu znaków do użycia. Chociaż to nie zadziała, jeśli Twój zestaw znaków jest dłuższy niż maksymalna wartość bajtu: 255. Polecam przejrzenie kodu źródłowego SecureRandom w dokumencie: ruby-doc.org/stdlib-1.9.3/libdoc/securerandom/ rdoc /…
Breedly
8

Oto jeden prosty kod losowego hasła o długości 8:

rand_password=('0'..'z').to_a.shuffle.first(8).join
Thaha kp
źródło
7

Kolejna metoda, której lubię używać:

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

Dodaj, ljustjeśli naprawdę jesteś paranoikiem w kwestii prawidłowej długości łańcucha:

 rand(2**256).to_s(36).ljust(8,'a')[0..7]
163365
źródło
Jeszcze lepiej, aby chwycić najmniej znaczącą część liczby losowej, używając prawej strony ciągu: rand (2 ** 64) .to_s (36) [- 10,10]
Jeff
6
SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')

Coś z Devise

Teej
źródło
Dlaczego zastępuje ciąg znaków .tr('+/=lIO0', 'pqrsxyz')?
miguelcobain
Znaki specjalne, ponieważ nie są bezpieczne pod adresem URL. I l / I lub O / 0, ponieważ są bardzo łatwe do pomylenia, jeśli używasz techniki do generowania czytelnych haseł użytkowników.
ToniTornado,
Ta funkcja jest nieco stronnicza w stosunku do niektórych postaci. Również dla innych długości (np. 16) ostatni znak nie będzie losowy. Oto sposób, aby tego uniknąć. SecureRandom.base64 (64) .tr ('+ / = lIO01', '') [0,16]
Shai Coleman
5

Myślę, że to niezła równowaga zwięzłości, jasności i łatwości modyfikacji.

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

Łatwo modyfikowany

Na przykład z cyframi:

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

Wielkie litery szesnastkowe:

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

Aby uzyskać naprawdę imponujący zestaw znaków:

characters = (32..126).to_a.pack('U*').chars.to_a
Nathan Long
źródło
1
zaleciłbym to, aby po prostu użyć wielkich liter + cyfr, a także usunąć „mylący” zestaw znaków = (1..9) .to_a.concat ((„A '..” Z ”). to_a) .reject {| a | [0, 1, „O”, „I”]. Obejmują? (A)} (rozmiar 0 ...) .map {charset [rand (charset.size)]} .join
luster
5

Właśnie dodałem tutaj swoje centy ...

def random_string(length = 8)
  rand(32**length).to_s(32)
end
pduersteler
źródło
3
Uwaga: nie zawsze zwraca dokładnie łańcuch + długość + długość - może być krótszy. To zależy od liczby zwracane przezrand
tardate
5

Możesz użyć String#randomz Facets of Ruby Gem facets.

Zasadniczo robi to:

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
źródło
4

Moim ulubionym jest (:A..:Z).to_a.shuffle[0,8].join. Zauważ, że losowanie wymaga Ruby> 1.9.

Josh
źródło
4

To rozwiązanie wymaga zewnętrznej zależności, ale wydaje się ładniejsze niż inne.

  1. Zainstaluj gem faker
  2. Faker::Lorem.characters(10) # => "ang9cbhoa8"
asiniy
źródło
4

Dany:

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

Pojedyncze wyrażenie, które można przekazać jako argument, umożliwia duplikowanie znaków:

Array.new(len) { chars.sample }.join
Tim James
źródło
3

Myślę, że najbardziej podoba mi się odpowiedź Radar. Poprawiłbym trochę tak:

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
źródło
Dlaczego nie użyć (CHARS*length).sample(length).join?
niels,
@niels Ta sugestia wygenerowałaby łańcuch ważony na korzyść niepowtarzalnych znaków. Na przykład, jeśli CHARS=['a','b']twoja metoda wygeneruje "aa"lub "bb"tylko 33% czasu, ale "ab"lub "ba"67% czasu. Może to nie jest problem, ale warto o tym pamiętać!
Tom Lord
dobra uwaga, @TomLord, chyba nie zdawałem sobie sprawy z tego, że kiedy opublikowałem tę sugestię (chociaż muszę przyznać, że w ogóle nie pamiętam, że ją opublikowałem: D)
niels
3
''.tap {|v| 4.times { v << ('a'..'z').to_a.sample} }
eric
źródło
3

Moje 2 centy:

  def token(length=16)
    chars = [*('A'..'Z'), *('a'..'z'), *(0..9)]
    (0..length).map {chars.sample}.join
  end
tybro0103
źródło
3

2 rozwiązania dla ciągu losowego składającego się z 3 zakresów:

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

Jedna postać z każdego zakresu.

A jeśli potrzebujesz co najmniej jednego znaku z każdego zakresu, na przykład utworzenia losowego hasła, które ma jedną wielką literę, jedną małą literę i jedną cyfrę, możesz zrobić coś takiego:

( ('a'..'z').to_a.sample(8) + ('A'..'Z').to_a.sample(8) + (0..9).to_a.sample(8) ).shuffle.join 
#=> "Kc5zOGtM0H796QgPp8u2Sxo1"
Peter
źródło
A jeśli chcesz wymusić określoną liczbę każdego zakresu, możesz zrobić coś takiego:( ('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

Ostatnio robiłem coś takiego, aby wygenerować 8-bajtowy ciąg losowy z 62 znaków. Znaki to 0-9, az, AZ. Miałem ich tablicę, ponieważ zapętlałem 8 razy i wybrałem losową wartość z tablicy. To było w aplikacji Rails.

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

Dziwne jest to, że mam dużą liczbę duplikatów. Teraz losowo to raczej nie powinno się zdarzyć. 62 ^ 8 jest ogromny, ale z około 1200 kodów w db miałem dużą liczbę duplikatów. Zauważyłem, że zdarzają się one na godzinnych granicach. Innymi słowy, mogę zobaczyć duplikat o 12:12:23 i 2:12:22 lub coś w tym rodzaju ... nie jestem pewien, czy czas jest problemem, czy nie.

Ten kod znajdował się przed utworzeniem obiektu ActiveRecord. Przed utworzeniem rekordu ten kod działałby i generował „unikalny” kod. Wpisy w DB zawsze były tworzone niezawodnie, ale kod ( strw powyższym wierszu) był zbyt często kopiowany.

Stworzyłem skrypt, który uruchamiałby 100 000 iteracji powyższej linii z niewielkim opóźnieniem, więc zajęłoby to 3-4 godziny w nadziei, że zobaczę jakiś wzór powtarzalny co godzinę, ale nic nie zobaczyłem. Nie mam pojęcia, dlaczego tak się dzieje w mojej aplikacji Rails.

erik
źródło