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ę?


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

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


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.


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

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.


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.


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


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


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)

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


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]

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

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"

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

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

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]

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


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

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

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

4

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


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"

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

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

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} }

3

Moje 2 centy:

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


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"

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.