Comment générer une chaîne aléatoire dans Ruby

747

Je génère actuellement une chaîne majuscule pseudo-aléatoire à 8 caractères pour "A" .. "Z":

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

mais il n'a pas l'air propre, et il ne peut pas être passé en argument car il ne s'agit pas d'une seule instruction. Pour obtenir une chaîne à casse mixte "a" .. "z" plus "A" .. "Z", je l'ai modifiée en:

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

mais ça ressemble à une poubelle.

Quelqu'un at-il une meilleure méthode?

Jeff
la source
Je ne comprends pas pourquoi vous vous souciez du fait que "puisque ce n'est pas une seule déclaration, elle ne peut pas être passée comme argument". Pourquoi ne pas simplement en faire une méthode utilitaire ou auxiliaire?
David J.
1
Supposons qu'il existe une méthode pour réinitialiser le mot de passe d'un utilisateur et qu'il dispose d'un argument pour le nouveau mot de passe. Je voudrais passer dans une chaîne aléatoire, dans le code ci-dessus, j'ai besoin d'une variable tmp, tandis que dans les exemples d'instructions simples ci-dessous, je peux faire le tout comme une ligne. Bien sûr, une méthode utilitaire pourrait être agréable à long terme, surtout si j'ai besoin de ça ici et là, mais parfois vous voulez juste qu'elle soit en place, une fois, c'est fait.
Jeff
Non, vous n'êtes pas obligé d'utiliser une variable temporaire. Essayez ceci: reset_user_password!(random_string)def random_string; SecureRandom.urlsafe_base64(20) end
David J.
8 lettres est un mot de passe honteusement faible. Étant donné la somme md5, un PC moderne pourrait récupérer le mot de passe en 30 secondes . Que diriez-vous de quelque chose de plussecurerandom.urlsafe_base64
Colonel Panic
1
Pourquoi cela a-t-il tant de réponses? Non pas que ce ne soit pas une question utile, mais je suis curieux de voir comment cela a attiré l'attention.
Lou

Réponses:

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

Je passe trop de temps à jouer au golf.

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

Et un dernier encore plus déroutant, mais plus flexible et qui gaspille moins de cycles:

o = [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
string = (0...50).map { o[rand(o.length)] }.join
Kent Fredric
la source
205
34 caractères et ultra - rapide: ('a'..'z').to_a.shuffle[0,8].join. Notez que vous aurez besoin de Ruby> = 1,9 pour shuffle.
fny
20
Il est préférable d'utiliser les bibliothèques existantes à moins que vous n'ayez un pilote pour rouler le vôtre. Voir à SecureRandomtitre d'exemple, dans les autres réponses.
David J.26
28
@faraz votre méthode n'est pas fonctionnellement la même, elle n'est pas aléatoire avec remplacement.
michaeltwofish
35
[*('a'..'z'),*('0'..'9')].shuffle[0,8].joinpour générer une chaîne aléatoire avec des lettres et des chiffres.
Robin
31
randest déterministe et prévisible. Ne l'utilisez pas pour générer des mots de passe! Utilisez SecureRandomplutôt l' une des solutions.
crayon
800

Pourquoi ne pas utiliser SecureRandom?

require 'securerandom'
random_string = SecureRandom.hex

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

SecureRandom propose également des méthodes pour:

  • base64
  • random_bytes
  • random_number

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

christopherstyles
la source
11
base64 le ferait, mais pas hex comme dans son exemple
Jeff Dickey
67
Soit dit en passant, il fait partie du stdlib dans les versions 1.9 et 1.8 récentes, donc on peut juste require 'securerandom'obtenir cet SecureRandomassistant soigné :)
J -_- L
21
BTW SecureRandom a été supprimé d'ActiveSupport dans la version 3.2. Depuis le journal des modifications: "Suppression d'ActiveSupport :: SecureRandom au profit de SecureRandom de la bibliothèque standard".
Marc
15
SecureRandom.random_number(36**12).to_s(36).rjust(12, "0")générera une chaîne de 0 à 9a-z (36 caractères) de TOUJOURS 12 caractères. Remplacez 12 par la longueur souhaitée. Malheureusement, aucun moyen d'obtenir simplement AZ en utilisant Integer#to_s.
Gerry Shaw
1
@ stringo0 qui est faux. Si vous vouliez passer +fGH1par une URL, il vous suffit de la coder par URL comme vous le feriez pour TOUTE valeur passant par une URL: %2BfGH1
nzifnab
247

J'utilise ceci pour générer des chaînes aléatoires adaptées aux URL avec une longueur maximale garantie:

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

Il génère des chaînes aléatoires d'az minuscule et de 0 à 9. Ce n'est pas très personnalisable mais c'est court et propre.

Christoffer Möller
la source
4
+1 pour la version la plus courte (qui n'appelle pas les binaires externes ^^). Si la chaîne aléatoire n'est pas accessible au public, je l'utilise même parfois rand.to_s; moche, mais fonctionne.
Jo Liss
12
C'est une excellente solution (et rapide aussi), mais elle produira parfois une chaîne sous la length longueur, environ une fois en ~ 40
Brian E
7
E cela @ Brian garantir les chiffres que vous voulez: (36**(length-1) + rand(36**length)).to_s(36). 36 ** (longueur-1) converti en base 36 est 10 ** (longueur-1), qui est la plus petite valeur ayant la longueur de chiffres souhaitée.
Eric Hu
11
Voici la version produisant toujours des jetons de la longueur souhaitée:(36**(length-1) + rand(36**length - 36**(length-1))).to_s(36)
Adrien Jarthon
3
Cela crache une erreur pour moi dans Rails 4 et Ruby 2.1.1: NameError: undefined local variable or method longueur 'pour main: Object`
kakubei
175

Cette solution génère une chaîne de caractères facilement lisibles pour les codes d'activation; Je ne voulais pas que les gens confondent 8 avec B, 1 avec I, 0 avec O, L avec 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
la source
3
Le «U» est-il ambigu ou s'agit-il d'une faute de frappe?
gtd
10
@gtd - Ouais. U et V sont des ambigvovs.
colinm
1
@colinm V est là-dedans cependant.
gtd
8
Pour être sûr, vous voudrez également utiliser à la SecureRandom.random_number(charset.size)place derand(charset.size)
gtd
1
J'essayais juste d'améliorer cela, en ajoutant des minuscules et / ou des caractères spéciaux (shift + num), et j'ai obtenu les listes suivantes: %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 ! @ # $ % ^ & * +}et %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 première liste n'inclut pas les minuscules, mais elle est plus longue) élaboré.
dkniffin
130

D'autres ont mentionné quelque chose de similaire, mais cela utilise la fonction de sécurité des URL.

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

Le résultat peut contenir AZ, az, 0-9, «-» et «_». "=" Est également utilisé si le remplissage est vrai.

Travis R
la source
Ceci est exactement ce que je cherchais. Merci!
Dan Williams
70

Depuis Ruby 2.5, c'est très simple avec SecureRandom.alphanumeric:

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

Il génère des chaînes aléatoires contenant AZ, az et 0-9 et devrait donc être applicable dans la plupart des cas d'utilisation. Et ils sont générés de manière aléatoire et sécurisée, ce qui pourrait également être un avantage.


Il s'agit d'une référence pour la comparer avec la solution ayant le plus de votes positifs:

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)

La randsolution ne prend donc qu'environ 3/4 du temps de SecureRandom. Cela peut avoir de l'importance si vous générez beaucoup de chaînes, mais si vous créez de temps en temps une chaîne aléatoire, j'opterais toujours pour une implémentation plus sécurisée car elle est également plus facile à appeler et plus explicite.

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

Générez une chaîne aléatoire de 8 lettres (par exemple NVAYXHGR)

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

Génère une chaîne aléatoire de 8 caractères (par exemple 3PH4SWF2), exclut 0/1 / I / O. Ruby 1.9

Tom Carr
la source
4
Seul problème, chaque caractère du résultat est unique. Limite les valeurs possibles.
tybro0103
1
Si cette fonctionnalité demande est acceptée, Ruby 1.9.x peut se retrouver avec #sample pour l'échantillonnage sans remplacement et #choice pour l'échantillonnage avec remplacement.
David J.26
C'est une erreur, je pense que vous avez besoin ... [*("A".."Z")]'; ((pas des qoutes simples))
jayunit100
pouvez-vous me dire comment je peux passer stubça rspec.
Sinscary
31

Je ne me souviens pas où j'ai trouvé cela, mais cela me semble être le meilleur et le moins intensif pour moi:

def random_string(length=10)
  chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ0123456789'
  password = ''
  length.times { password << chars[rand(chars.size)] }
  password
end
Travis R
la source
7
Vous l'avez peut-être trouvé ici? travisonrails.com/2007/06/07/generate-random-text-with-ruby
deadwards
N'y a-t-il pas un 0 et un manquant à cela?
sunnyrjuneja
On dirait que c'est peut-être là que je l'ai trouvé.
Travis Reeder
Et oui, il semble que 0 et 1 manquent.
Travis Reeder
4
Le 0et 1et Oet Iétaient intentionnellement manquants car ces caractères sont ambigus. Si ce type de code est utilisé pour générer un ensemble de caractères qu'un utilisateur doit copier, il est préférable d'exclure les caractères qui peuvent être difficiles à distinguer hors contexte.
Andrew
28
require 'securerandom'
SecureRandom.urlsafe_base64(9)
LENZCOM
la source
SecureRandom est une excellente bibliothèque. Je ne savais pas que c'était là. Merci.
Dogweather
ancienne alternative skool (et plus laide):Random.new.bytes(9)
tard le
4
btw, urlsafe_base64 renvoie une chaîne d'environ 4/3 de la longueur indiquée. Pour obtenir une chaîne exactement n caractères, essayezn=9 ; SecureRandom.urlsafe_base64(n)[0..n-1]
tardate
25

Si vous voulez une chaîne de longueur spécifiée, utilisez:

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

Il générera une chaîne aléatoire de longueur 2ncontenant 0-9eta-f

Srikanta Mahapatro
la source
Il ne génère pas de chaîne de longueur n, il génère en fait une chaîne de 4/3 n.
Omar Ali
@OmarAli vous vous trompez. Selon la documentation Ruby dans le cas de .hex2n. Documentation: SecureRandom.hex génère une chaîne hexadécimale aléatoire. L'argument n spécifie la longueur, en octets, du nombre aléatoire à générer. La longueur de la chaîne hexadécimale résultante est le double de n .
Rihards
11
require 'sha1'
srand
seed = "--#{rand(10000)}--#{Time.now}--"
Digest::SHA1.hexdigest(seed)[0,8]
Coren
la source
Intéressant, mais un peu plus cher en termes de calcul
Jeff
De plus, aucune capacité pour une portée limitée de caractères.
Kent Fredric
3
Gardez à l'esprit qu'un résumé hexadécimal ne renvoie que des caractères 0-9 et af.
webmat
11

Voici un code simple d'une ligne pour une chaîne aléatoire de longueur 8:

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

Vous pouvez également l'utiliser pour un mot de passe aléatoire de longueur 8:

random_password = ('0'..'z').to_a.shuffle.first(8).join
Awais
la source
2
Vous ne devez pas l'utiliser pour générer vous-même un mot de passe, car cette méthode ne répétera jamais un caractère. Par conséquent, vous n'utilisez que P(36, 8) / 36^8 = 0.4l'espace de caractères possible pour 8 caractères (~ 2x plus facile à la force brute) ou P(36, 25) / 36^25 = 0.00001l'espace de caractères possible pour 25 caractères (~ 100 000x plus facile à la force brute).
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
la source
1
La mapsolution est vraiment sympa!
Misha Moroshko
Vous pouvez demander #samplecombien d'éléments vous souhaitez. Par exemple ALPHABET.sample(10).join... ruby-doc.org/core-2.4.0/Array.html#method-i-sample
odlp
C'est en fait faux. sample(10)vous donne 10 échantillons uniques. Mais vous voulez leur permettre de répéter. Mais j'utiliserais Array.new(10).mappour la performance
Saša Zejnilović
Je voulais alphanumérique avec des minuscules et des majuscules. J'ai également changé pour utiliser Array.newet sa syntaxe de bloc. Array.new(20) { [*'0'..'9', *'a'..'z', *'A'..'Z'].sample }.join
taylorthurlow
8

Attention: randest prévisible pour un attaquant et donc probablement peu sûr. Vous devez absolument utiliser SecureRandom s'il s'agit de générer des mots de passe. J'utilise quelque chose comme ça:

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
crayon
la source
Il s'agit probablement de la solution "la plus" sécurisée. SecureRandom tente d'utiliser les API de sécurité sous-jacentes fournies par le système d'exploitation. Si vous avez OpenSSL, il l'utilisera, si vous êtes sous Windows, il y aura la meilleure option. J'aime particulièrement cette solution car elle vous permet de spécifier un jeu de caractères à utiliser. Bien que cela ne fonctionnera pas si votre jeu de caractères est plus long que la valeur maximale d'un octet: 255. Je recommande de visualiser le code source de SecureRandom dans le doc: ruby-doc.org/stdlib-1.9.3/libdoc/securerandom/ rdoc /…
Breedly
8

Voici un code simple pour un mot de passe aléatoire de longueur 8:

rand_password=('0'..'z').to_a.shuffle.first(8).join
Thaha kp
la source
7

Une autre méthode que j'aime utiliser:

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

Ajoutez ljustsi vous êtes vraiment paranoïaque sur la longueur de chaîne correcte:

 rand(2**256).to_s(36).ljust(8,'a')[0..7]
user163365
la source
Encore mieux pour saisir la partie la moins significative du nombre aléatoire en utilisant le côté droit de la chaîne: rand (2 ** 64) .to_s (36) [- 10,10]
Jeff
6
SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')

Quelque chose de Devise

Teej
la source
Pourquoi remplace-t-il les caractères de chaîne à l'aide de .tr('+/=lIO0', 'pqrsxyz')?
miguelcobain
Les caractères spéciaux car ils ne sont pas protégés contre les URL. Et l / I ou O / 0 car ils sont très faciles à confondre si vous utilisez la technique pour générer des mots de passe utilisateur lisibles.
ToniTornado
Cette fonction a un peu de parti pris envers certains personnages. Aussi pour les autres longueurs (par exemple 16), le dernier caractère ne sera pas aléatoire. Voici un moyen d'éviter cela. SecureRandom.base64 (64) .tr ('+ / = lIO01', '') [0,16]
Shai Coleman
5

Je pense que c'est un bel équilibre entre concision, clarté et facilité de modification.

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

Facilement modifié

Par exemple, y compris les chiffres:

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

Hexadécimal majuscule:

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

Pour un éventail de caractères vraiment impressionnant:

characters = (32..126).to_a.pack('U*').chars.to_a
Nathan Long
la source
1
je recommanderais ceci pour utiliser uniquement des majuscules + des chiffres, supprimez également les caractères "confus" charset = (1..9) .to_a.concat (('A' .. 'Z'). to_a) .reject {| a | [0, 1, 'O', 'I'].. Include? (A)} (0 ... taille) .map {charset [rand (charset.size)]} .join
lustre
5

J'ajoute juste mes cents ici ...

def random_string(length = 8)
  rand(32**length).to_s(32)
end
pduersteler
la source
3
NB: cela ne renvoie pas toujours une chaîne exactement + longueur + long - elle peut être plus courte. Cela dépend du nombre retourné parrand
tard le
5

Vous pouvez utiliser String#random partir des facettes de Ruby Gemfacets .

Il fait essentiellement ceci:

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
la source
4

Mon préféré est (:A..:Z).to_a.shuffle[0,8].join. Notez que le shuffle nécessite Ruby> 1.9.

Josh
la source
4

Cette solution nécessite une dépendance externe, mais semble plus jolie qu'une autre.

  1. Installer Gem Faker
  2. Faker::Lorem.characters(10) # => "ang9cbhoa8"
asiniy
la source
4

Donné:

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

Une seule expression, pouvant être passée en argument, autorise les caractères en double:

Array.new(len) { chars.sample }.join
Tim James
la source
3

Je préfère la réponse de Radar, jusqu'à présent, je pense. Je voudrais modifier un peu comme ceci:

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
la source
Pourquoi ne pas utiliser (CHARS*length).sample(length).join?
niels
@niels Cette suggestion générerait une chaîne pondérée , en faveur des caractères non répétés. Par exemple, si CHARS=['a','b']alors votre méthode générerait "aa"ou "bb"seulement 33% du temps, mais "ab"ou "ba"67% du temps. Ce n'est peut-être pas un problème, mais cela vaut la peine de garder à l'esprit!
Tom Lord
bon point, @TomLord, je pense que je ne m'étais pas rendu compte que lorsque j'ai posté cette suggestion (bien que je dois admettre que je ne me souviens pas du tout d'avoir posté cela: D)
niels
3
''.tap {|v| 4.times { v << ('a'..'z').to_a.sample} }
eric
la source
3

Mes 2 cents:

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

Je viens d'écrire un petit bijou random_tokenpour générer des jetons aléatoires pour la plupart des cas d'utilisation, profitez de ~

https://github.com/sibevin/random_token

Sibevin Wang
la source
Super petit bijou. Merci :)
Mirko Akov
3

2 solutions pour une chaîne aléatoire composée de 3 plages:

(('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 personnage de chaque plage.

Et si vous avez besoin d'au moins un caractère de chaque plage, comme la création d'un mot de passe aléatoire qui a une majuscule, une lettre minuscule et un chiffre, vous pouvez faire quelque chose comme ceci:

( ('a'..'z').to_a.sample(8) + ('A'..'Z').to_a.sample(8) + (0..9).to_a.sample(8) ).shuffle.join 
#=> "Kc5zOGtM0H796QgPp8u2Sxo1"
peter
la source
Et si vous voulez appliquer un certain nombre de chaque plage, vous pouvez faire quelque chose comme ceci:( ('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

Je faisais quelque chose comme ça récemment pour générer une chaîne aléatoire de 8 octets à partir de 62 caractères. Les caractères étaient 0-9, az, AZ. J'en avais un tableau comme en boucle 8 fois et en choisissant une valeur aléatoire dans le tableau. C'était dans une application Rails.

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

La chose étrange est que j'ai eu un bon nombre de doublons. Maintenant, au hasard, cela ne devrait pratiquement jamais se produire. 62 ^ 8 est énorme, mais sur 1200 codes environ dans la base de données, j'ai eu un bon nombre de doublons. Je les ai vus se produire aux limites des heures les uns des autres. En d'autres termes, je pourrais voir un double à 12:12:23 et 2:12:22 ou quelque chose comme ça ... je ne sais pas si le temps est le problème ou non.

Ce code était dans la création antérieure d'un objet ActiveRecord. Avant la création de l'enregistrement, ce code s'exécutait et générait le code «unique». Les entrées dans la base de données étaient toujours produites de manière fiable, mais le code ( strdans la ligne ci-dessus) était dupliqué beaucoup trop souvent.

J'ai créé un script pour exécuter 100 000 itérations de cette ligne ci-dessus avec un petit retard, il faudrait donc 3-4 heures en espérant voir une sorte de motif de répétition toutes les heures, mais je n'ai rien vu. Je ne sais pas pourquoi cela se produisait dans mon application Rails.

erik
la source