Comment générer une chaîne aléatoire d'une longueur fixe dans Go?

300

Je veux une chaîne aléatoire de caractères uniquement (majuscules ou minuscules), pas de chiffres, dans Go. Quelle est la manière la plus rapide et la plus simple de procéder?

Anish Shah
la source
2
@VinceEmigh: Voici un méta-sujet traitant des questions de base. meta.stackoverflow.com/q/274645/395461 Personnellement, je pense que les questions de base sont correctes si elles sont bien écrites et sont sur le sujet. Regardez les réponses ci-dessous, elles illustrent un tas de choses qui pourraient être utiles pour quelqu'un de nouveau. Pour les boucles, tapez casting, make (), etc.
Shannon Matthews
2
@Shannon " Cette question ne montre aucun effort de recherche " (première réponse très appréciée dans votre lien) - C'est à cela que je faisais référence. Il ne montre aucun effort de recherche. Aucun effort du tout (une tentative, ou même affirmer qu'il avait regardé en ligne, ce qu'il n'a évidemment pas fait). Bien qu'il soit utile pour quelqu'un de nouveau , ce site n'est pas axé sur l'enseignement de nouvelles personnes. Il est axé sur la réponse à des problèmes / questions de programmation spécifiques, pas sur des tutoriels / guides. Bien qu'il puisse être utilisé pour ce dernier, ce n'est pas l'objectif, et donc cette question devrait être fermée. Au lieu de cela, son cuillère /:
Vince Emigh
9
@VinceEmigh J'ai posé cette question il y a un an. J'avais recherché en ligne des chaînes aléatoires et lu des documents aussi. Mais ce n'était pas utile. Si je n'ai pas écrit dans la question, cela ne signifie pas que je n'ai pas fait de recherche.
Anish Shah

Réponses:

809

La solution de Paul fournit une solution simple et générale.

La question demande "le moyen le plus rapide et le plus simple" . Parlons le plus rapidement partie . Nous arriverons à notre code final le plus rapide de manière itérative. L'analyse comparative de chaque itération se trouve à la fin de la réponse.

Toutes les solutions et le code de référence peuvent être trouvés sur le Go Playground . Le code sur le Playground est un fichier de test, pas un exécutable. Vous devez l'enregistrer dans un fichier nommé XX_test.goet l'exécuter avec

go test -bench . -benchmem

Avant - propos :

La solution la plus rapide n'est pas une solution idéale si vous avez juste besoin d'une chaîne aléatoire. Pour cela, la solution de Paul est parfaite. C'est si les performances comptent. Bien que les 2 premières étapes ( octets et reste ) puissent être un compromis acceptable: elles améliorent les performances de 50% (voir les chiffres exacts dans la section II. Benchmark ) et n'augmentent pas la complexité de manière significative.

Cela dit, même si vous n'avez pas besoin de la solution la plus rapide, la lecture de cette réponse peut être aventureuse et éducative.

I. Améliorations

1. Genesis (Runes)

Pour rappel, la solution générale originale que nous améliorons est la suivante:

func init() {
    rand.Seed(time.Now().UnixNano())
}

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func RandStringRunes(n int) string {
    b := make([]rune, n)
    for i := range b {
        b[i] = letterRunes[rand.Intn(len(letterRunes))]
    }
    return string(b)
}

2. octets

Si les caractères à choisir et à assembler la chaîne aléatoire contiennent uniquement les lettres majuscules et minuscules de l'alphabet anglais, nous ne pouvons travailler avec des octets que parce que les lettres de l'alphabet anglais sont mappées aux octets 1 à 1 dans l'encodage UTF-8 (qui est la façon dont Go stocke les chaînes).

Donc au lieu de:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

on peut utiliser:

var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

Ou encore mieux:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

Maintenant, c'est déjà une grande amélioration: nous pourrions y parvenir const(il y a des stringconstantes mais pas de constantes de tranche ). Comme gain supplémentaire, l'expression len(letters)sera également un const! (L'expression len(s)est constante si sest une constante chaîne.)

Et à quel prix? Rien du tout. strings peut être indexé qui indexe ses octets, parfait, exactement ce que nous voulons.

Notre prochaine destination ressemble à ceci:

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func RandStringBytes(n int) string {
    b := make([]byte, n)
    for i := range b {
        b[i] = letterBytes[rand.Intn(len(letterBytes))]
    }
    return string(b)
}

3. Reste

Les solutions précédentes obtiennent un nombre aléatoire pour désigner une lettre aléatoire en appelant les rand.Intn()délégués Rand.Intn()auxquels les délégués àRand.Int31n() .

Ceci est beaucoup plus lent que celui rand.Int63()qui produit un nombre aléatoire avec 63 bits aléatoires.

Ainsi, nous pourrions simplement appeler rand.Int63()et utiliser le reste après la division par len(letterBytes):

func RandStringBytesRmndr(n int) string {
    b := make([]byte, n)
    for i := range b {
        b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))]
    }
    return string(b)
}

Cela fonctionne et est beaucoup plus rapide, l'inconvénient est que la probabilité de toutes les lettres ne sera pas exactement la même (en supposant que rand.Int63()tous les nombres de 63 bits soient identiques ). Bien que la distorsion soit extrêmement faible car le nombre de lettres 52est beaucoup plus petit que 1<<63 - 1, donc en pratique, c'est parfaitement bien.

Pour faciliter la compréhension: disons que vous voulez un nombre aléatoire dans la plage de 0..5. En utilisant 3 bits aléatoires, cela produirait les nombres 0..1avec une probabilité double par rapport à la plage 2..5. En utilisant 5 bits aléatoires, les nombres dans la plage 0..1se produiraient avec 6/32probabilité et les nombres dans la plage 2..5avec5/32 probabilité qui est maintenant plus proche de la valeur souhaitée. L'augmentation du nombre de bits la rend moins significative, lorsqu'elle atteint 63 bits, elle est négligeable.

4. Masquage

En s'appuyant sur la solution précédente, nous pouvons maintenir la distribution égale des lettres en utilisant uniquement autant de bits les plus bas du nombre aléatoire que nécessaire pour représenter le nombre de lettres. Ainsi , par exemple , si nous avons 52 lettres, il faut 6 bits pour le représenter: 52 = 110100b. Nous n'utiliserons donc que les 6 bits les plus bas du nombre renvoyé par rand.Int63(). Et pour maintenir une distribution égale des lettres, nous n'acceptons le nombre que s'il tombe dans la plage 0..len(letterBytes)-1. Si les bits les plus bas sont supérieurs, nous les éliminons et demandons un nouveau nombre aléatoire.

Notez que la probabilité que les bits les plus bas soient supérieurs ou égaux à len(letterBytes)est inférieure à celle 0.5en général ( 0.25en moyenne), ce qui signifie que même si tel est le cas, la répétition de ce cas "rare" diminue les chances de ne pas trouver un bon nombre. Après la nrépétition, la chance que nous n'ayons pas un bon indice est bien inférieure à pow(0.5, n), et ce n'est qu'une estimation supérieure. Dans le cas de 52 lettres, la chance que les 6 bits les plus bas ne soient pas bons est seulement (64-52)/64 = 0.19; ce qui signifie par exemple que les chances de ne pas avoir un bon nombre après 10 répétitions sont 1e-8.

Voici donc la solution:

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
    letterIdxBits = 6                    // 6 bits to represent a letter index
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)

func RandStringBytesMask(n int) string {
    b := make([]byte, n)
    for i := 0; i < n; {
        if idx := int(rand.Int63() & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i++
        }
    }
    return string(b)
}

5. Masquage amélioré

La solution précédente utilise uniquement les 6 bits les plus bas des 63 bits aléatoires renvoyés par rand.Int63(). C'est un gaspillage car obtenir les bits aléatoires est la partie la plus lente de notre algorithme.

Si nous avons 52 lettres, cela signifie que 6 bits codent un index de lettres. Ainsi, 63 bits aléatoires peuvent désigner 63/6 = 10différents indices de lettres. Utilisons tous ces 10:

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
    letterIdxBits = 6                    // 6 bits to represent a letter index
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
    letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
)

func RandStringBytesMaskImpr(n int) string {
    b := make([]byte, n)
    // A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
    for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = rand.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return string(b)
}

6. Source

Le masquage amélioré est assez bon, nous ne pouvons pas améliorer grand-chose. Nous pourrions, mais ne valons pas la complexité.

Maintenant, trouvons autre chose à améliorer. La source des nombres aléatoires.

Il y a un crypto/randpaquet qui fournit une Read(b []byte)fonction, donc nous pourrions l'utiliser pour obtenir autant d'octets avec un seul appel que nous en avons besoin. Cela n'aiderait pas en termes de performances, car il crypto/randimplémente un générateur de nombres pseudo-aléatoires cryptographiquement sécurisé, il est donc beaucoup plus lent.

Restons donc sur le math/randpaquet. Le rand.Randutilise un rand.Sourcecomme source de bits aléatoires. rand.Sourceest une interface qui spécifie une Int63() int64méthode: exactement et la seule chose dont nous avions besoin et utilisée dans notre dernière solution.

Nous n'avons donc pas vraiment besoin d'un rand.Rand(explicite ou global, partagé d'un randpackage), un rand.Sourceest parfaitement suffisant pour nous:

var src = rand.NewSource(time.Now().UnixNano())

func RandStringBytesMaskImprSrc(n int) string {
    b := make([]byte, n)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
    for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return string(b)
}

Notez également que cette dernière solution ne vous oblige pas à initialiser (amorcer) le global Randdu math/randpaquet car il n'est pas utilisé (et notre rand.Sourceest correctement initialisé / amorcé).

Encore une chose à noter ici: package doc des math/randétats:

La source par défaut est sûre pour une utilisation simultanée par plusieurs goroutines.

Ainsi, la source par défaut est plus lente que Sourcecelle qui peut être obtenue par rand.NewSource(), car la source par défaut doit fournir une sécurité sous accès / utilisation simultanés, tout en rand.NewSource()ne l'offrant pas (et donc le Sourceretour par elle est plus susceptible d'être plus rapide).

7. Utilisation strings.Builder

Toutes les solutions précédentes renvoient un stringdont le contenu est d'abord construit dans une tranche ( []runedans Genesis et []bytedans les solutions suivantes), puis converti en string. Cette conversion finale doit faire une copie du contenu de la tranche, car les stringvaleurs sont immuables, et si la conversion ne faisait pas de copie, il ne pouvait être garanti que le contenu de la chaîne ne serait pas modifié via sa tranche d'origine. Pour plus de détails, voir Comment convertir une chaîne utf8 en [] octet? et golang: [] octet (chaîne) vs [] octet (* chaîne) .

Go 1.10 introduit strings.Builder. strings.Builderun nouveau type que nous pouvons utiliser pour créer un contenu stringsimilaire à bytes.Buffer. Il le fait en interne en utilisant un []byte, et lorsque nous avons terminé, nous pouvons obtenir la stringvaleur finale en utilisant sonBuilder.String() méthode. Mais ce qui est cool, c'est qu'il fait cela sans effectuer la copie dont nous venons de parler ci-dessus. Il ose le faire parce que la tranche d'octets utilisée pour construire le contenu de la chaîne n'est pas exposée, il est donc garanti que personne ne peut la modifier involontairement ou par malveillance pour altérer la chaîne "immuable" produite.

Donc, notre prochaine idée est de ne pas construire la chaîne aléatoire dans une tranche, mais avec l'aide de a strings.Builder, donc une fois que nous avons terminé, nous pouvons obtenir et retourner le résultat sans avoir à en faire une copie. Cela peut aider en termes de vitesse, et cela aidera certainement en termes d'utilisation de la mémoire et d'allocations.

func RandStringBytesMaskImprSrcSB(n int) string {
    sb := strings.Builder{}
    sb.Grow(n)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
    for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            sb.WriteByte(letterBytes[idx])
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return sb.String()
}

Notez qu'après avoir créé une nouvelle strings.Buidler, nous avons appelé sa Builder.Grow()méthode, en nous assurant qu'elle alloue une tranche interne suffisamment grande (pour éviter les réallocations lorsque nous ajoutons les lettres aléatoires).

8. "Imitation" strings.Builderavec le packageunsafe

strings.Builderconstruit la chaîne dans un interne []byte, comme nous l'avons fait nous-mêmes. Donc, fondamentalement, le faire via un strings.Buildera une surcharge, la seule chose pour laquelle nous sommes passés strings.Builderest d'éviter la copie finale de la tranche.

strings.Builderévite la copie finale en utilisant le package unsafe:

// String returns the accumulated string.
func (b *Builder) String() string {
    return *(*string)(unsafe.Pointer(&b.buf))
}

Le fait est que nous pouvons aussi le faire nous-mêmes. Donc, l'idée ici est de revenir à la construction de la chaîne aléatoire dans a []byte, mais lorsque nous avons terminé, ne la convertissez pas stringen retour, mais effectuez une conversion non sécurisée: obtenez un stringqui pointe vers notre tranche d'octets comme données de chaîne .

Voici comment cela peut être fait:

func RandStringBytesMaskImprSrcUnsafe(n int) string {
    b := make([]byte, n)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
    for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return *(*string)(unsafe.Pointer(&b))
}

(9. Utilisation rand.Read())

Go 1.7 a ajouté une rand.Read()fonction et une Rand.Read()méthode. Nous devrions être tentés de les utiliser pour lire autant d'octets que nécessaire en une seule étape, afin d'obtenir de meilleures performances.

Il y a un petit "problème" avec ceci: de combien d'octets avons-nous besoin? On pourrait dire: autant que le nombre de lettres de sortie. Nous penserions qu'il s'agit d'une estimation supérieure, car un index de lettres utilise moins de 8 bits (1 octet). Mais à ce stade, nous faisons déjà pire (car obtenir les bits aléatoires est la «partie difficile»), et nous obtenons plus que nécessaire.

Notez également que pour maintenir une distribution égale de tous les indices de lettres, il pourrait y avoir des données aléatoires "poubelles" que nous ne serons pas en mesure d'utiliser, donc nous finirions par sauter certaines données, et donc nous retrouverons court quand nous passerons par tous la tranche d'octets. Nous aurions besoin d'obtenir davantage d'octets aléatoires, "récursivement". Et maintenant, nous perdons même l' randavantage du "seul appel au forfait" ...

Nous pourrions "quelque peu" optimiser l'utilisation des données aléatoires que nous acquérons math.Rand(). Nous pouvons estimer le nombre d'octets (bits) dont nous aurons besoin. 1 lettre nécessite des letterIdxBitsbits, et nous avons besoin de nlettres, nous avons donc besoin d' n * letterIdxBits / 8.0arrondir les octets. Nous pouvons calculer la probabilité qu'un indice aléatoire ne soit pas utilisable (voir ci-dessus), nous pourrions donc en demander plus qui sera "plus probable" suffisant (s'il s'avère que ce n'est pas le cas, nous répétons le processus). Nous pouvons traiter la tranche d'octets comme un "flux binaire" par exemple, pour lequel nous avons une belle bibliothèque tierce: github.com/icza/bitio(divulgation: je suis l'auteur).

Mais le code de référence montre toujours que nous ne gagnons pas. Pourquoi en est-il ainsi?

La réponse à la dernière question est car rand.Read()utilise une boucle et continue d'appeler Source.Int63()jusqu'à ce qu'elle remplisse la tranche passée. Exactement ce que fait la RandStringBytesMaskImprSrc()solution, sans le tampon intermédiaire et sans la complexité supplémentaire. C'est pourquoi RandStringBytesMaskImprSrc()reste sur le trône. Oui, RandStringBytesMaskImprSrc()utilise un non synchronisé rand.Sourcecontrairement rand.Read(). Mais le raisonnement s'applique toujours; et qui est prouvé si nous utilisons à la Rand.Read()place de rand.Read()(le premier est également non synchronisé).

II. Référence

Très bien, il est temps de comparer les différentes solutions.

Moment de vérité:

BenchmarkRunes-4                     2000000    723 ns/op   96 B/op   2 allocs/op
BenchmarkBytes-4                     3000000    550 ns/op   32 B/op   2 allocs/op
BenchmarkBytesRmndr-4                3000000    438 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMask-4                 3000000    534 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImpr-4            10000000    176 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImprSrc-4         10000000    139 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImprSrcSB-4       10000000    134 ns/op   16 B/op   1 allocs/op
BenchmarkBytesMaskImprSrcUnsafe-4   10000000    115 ns/op   16 B/op   1 allocs/op

En passant simplement des runes aux octets, nous avons immédiatement un gain de performances de 24% et les besoins en mémoire tombent à un tiers .

Se débarrasser rand.Intn()et utiliser à la rand.Int63()place donne encore 20% coup de pouce de .

Le masquage (et la répétition en cas de gros indices) ralentit un peu (à cause des appels de répétition): -22% ...

Mais lorsque nous utilisons tous (ou la plupart) des 63 bits aléatoires (10 indices d'un rand.Int63()appel): cela accélère beaucoup: 3 fois .

Si nous nous contentons d'un (non-défaut, nouveau) rand.Sourceau lieu de rand.Rand, nous gagnons à nouveau 21%.

Si nous utilisons strings.Builder, nous gagnons un minuscule 3,5% de vitesse , mais nous avons également obtenu une réduction de 50% de l'utilisation et des allocations de mémoire! C'est zonte!

Enfin si nous osons utiliser le package unsafeau lieu de strings.Builder, nous gagnons à nouveau un joli 14% .

Comparer la solution finale à la solution initiale: RandStringBytesMaskImprSrcUnsafe()est 6,3 fois plus rapide que RandStringRunes(), utilise un sixième de la mémoire et deux fois moins d'allocations . Mission accomplie.

icza
la source
8
@RobbieV Yup, car un partage rand.Sourceest utilisé. Une meilleure solution de contournement serait de passer un rand.Sourceà la RandStringBytesMaskImprSrc()fonction, et de cette façon aucun verrouillage n'est requis et donc la performance / efficacité n'est pas effectuée. Chaque goroutine pourrait avoir la sienne Source.
icza
113
@icza, c'est l'une des meilleures réponses que j'ai vues depuis longtemps sur SO!
astropanique du
1
@MikeAtlas: devrait éviter d'utiliser deferlorsqu'il est évident que vous n'en avez pas besoin. Voir grokbase.com/t/gg/golang-nuts/158zz5p42w/…
Zan Lynx
1
@ZanLynx thx pour la pointe; bien que deferdéverrouiller un mutex immédiatement avant ou après avoir appelé un verrou soit IMO surtout une très bonne idée; vous êtes assuré à la fois de ne pas oublier de déverrouiller mais également de déverrouiller même dans une fonction de panique non fatale.
Mike Atlas
1
@RobbieV il semble que ce code soit sûr pour les threads / goroutines car la source partagée sous-jacente est déjà une LockedSource qui implémente le mutex ( golang.org/src/math/rand/rand.go:259 ).
adityajones
130

Vous pouvez simplement écrire du code pour cela. Ce code peut être un peu plus simple si vous souhaitez que les lettres soient toutes des octets uniques lorsqu'elles sont encodées en UTF-8.

package main

import (
    "fmt"
    "time"
    "math/rand"
)

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func randSeq(n int) string {
    b := make([]rune, n)
    for i := range b {
        b[i] = letters[rand.Intn(len(letters))]
    }
    return string(b)
}

func main() {
    rand.Seed(time.Now().UnixNano())

    fmt.Println(randSeq(10))
}
Paul Hankin
la source
30
N'oubliez pas le rand.Seed (), sinon vous obtenez la même chaîne à chaque premier lancement ... rand.Seed (time.Now (). UTC (). UnixNano ())
Evan Lin
2
L'ajout d'Evan est correct, mais il existe d'autres options similaires: rand.Seed(time.Now().Unix())ourand.Seed(time.Now().UnixNano())
openwonk
7
Pour un secret difficile à deviner - un mot de passe, une clé de chiffrement, etc. - ne jamais utiliser math/rand; utilisez crypto/rand(comme l'option 1 de @ Not_A_Golfer) à la place.
twotwotwo
1
@EvanLin Ce ne sera pas devinable? Si je dois amorcer le générateur, l'attaquant pourrait deviner le temps avec lequel je l'ensemencer et prédire la même sortie que je génère.
Matej
4
Notez que si vous essayez le programme ci-dessus avec des graines, sur le terrain de jeu, il retournera le même résultat tout le temps. Je l'essayais sur le terrain de jeu et après un certain temps, j'ai réalisé cela. Sinon, ça a bien fonctionné pour moi. J'espère que cela fera gagner du temps à quelqu'un :)
Gaurav Sinha
18

Utilisez le package uniuri , qui génère des chaînes uniformes (non biaisées) cryptographiquement sécurisées.

Avertissement: je suis l'auteur du package

dchest
la source
1
À part: l'auteur, dchest, est un excellent développeur et a produit un certain nombre de petits packages utiles comme celui-ci.
Roshambo
16

Deux options possibles (il pourrait y en avoir plus bien sûr):

  1. Vous pouvez utiliser le crypto/randpackage qui prend en charge la lecture de tableaux d'octets aléatoires (à partir de / dev / urandom) et est orienté vers la génération aléatoire cryptographique. voir http://golang.org/pkg/crypto/rand/#example_Read . Cependant, cela pourrait être plus lent que la génération de nombres pseudo-aléatoires normaux.

  2. Prenez un nombre aléatoire et hachez-le en utilisant md5 ou quelque chose comme ça.

Not_a_Golfer
la source
4

Après icza'sune solution merveilleusement expliquée, en voici une modification qui utilise à la crypto/randplace de math/rand.

const (
    letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // 52 possibilities
    letterIdxBits = 6                    // 6 bits to represent 64 possibilities / indexes
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)

func SecureRandomAlphaString(length int) string {

    result := make([]byte, length)
    bufferSize := int(float64(length)*1.3)
    for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
        if j%bufferSize == 0 {
            randomBytes = SecureRandomBytes(bufferSize)
        }
        if idx := int(randomBytes[j%length] & letterIdxMask); idx < len(letterBytes) {
            result[i] = letterBytes[idx]
            i++
        }
    }

    return string(result)
}

// SecureRandomBytes returns the requested number of bytes using crypto/rand
func SecureRandomBytes(length int) []byte {
    var randomBytes = make([]byte, length)
    _, err := rand.Read(randomBytes)
    if err != nil {
        log.Fatal("Unable to generate random bytes")
    }
    return randomBytes
}

Si vous voulez une solution plus générique, qui vous permet de passer la tranche d'octets de caractères pour créer la chaîne, vous pouvez essayer d'utiliser ceci:

// SecureRandomString returns a string of the requested length,
// made from the byte characters provided (only ASCII allowed).
// Uses crypto/rand for security. Will panic if len(availableCharBytes) > 256.
func SecureRandomString(availableCharBytes string, length int) string {

    // Compute bitMask
    availableCharLength := len(availableCharBytes)
    if availableCharLength == 0 || availableCharLength > 256 {
        panic("availableCharBytes length must be greater than 0 and less than or equal to 256")
    }
    var bitLength byte
    var bitMask byte
    for bits := availableCharLength - 1; bits != 0; {
        bits = bits >> 1
        bitLength++
    }
    bitMask = 1<<bitLength - 1

    // Compute bufferSize
    bufferSize := length + length / 3

    // Create random string
    result := make([]byte, length)
    for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
        if j%bufferSize == 0 {
            // Random byte buffer is empty, get a new one
            randomBytes = SecureRandomBytes(bufferSize)
        }
        // Mask bytes to get an index into the character slice
        if idx := int(randomBytes[j%length] & bitMask); idx < availableCharLength {
            result[i] = availableCharBytes[idx]
            i++
        }
    }

    return string(result)
}

Si vous voulez transmettre votre propre source d'aléatoire, il serait trivial de modifier ce qui précède pour accepter un io.Readerau lieu de l'utiliser crypto/rand.

Chris
la source
2

Si vous voulez des nombres aléatoires sécurisés cryptographiquement et que le jeu de caractères exact est flexible (par exemple, base64 est très bien), vous pouvez calculer exactement la longueur de caractères aléatoires dont vous avez besoin à partir de la taille de sortie souhaitée.

Le texte de la base 64 est 1/3 plus long que la base 256. (2 ^ 8 vs 2 ^ 6; rapport 8bits / 6bits = 1,333)

import (
    "crypto/rand"
    "encoding/base64"
    "math"
)

func randomBase64String(l int) string {
    buff := make([]byte, int(math.Round(float64(l)/float64(1.33333333333))))
    rand.Read(buff)
    str := base64.RawURLEncoding.EncodeToString(buff)
    return str[:l] // strip 1 extra character we get from odd length results
}

Remarque: vous pouvez également utiliser RawStdEncoding si vous préférez les caractères + et / à - et _

Si vous voulez un hex, la base 16 est 2x plus longue que la base 256. (2 ^ 8 vs 2 ^ 4; 8bits / 4bits = 2x ratio)

import (
    "crypto/rand"
    "encoding/hex"
    "math"
)


func randomBase16String(l int) string {
    buff := make([]byte, int(math.Round(float64(l)/2)))
    rand.Read(buff)
    str := hex.EncodeToString(buff)
    return str[:l] // strip 1 extra character we get from odd length results
}

Cependant, vous pouvez l'étendre à n'importe quel jeu de caractères arbitraire si vous disposez d'un encodeur base256 à baseN pour votre jeu de caractères. Vous pouvez faire le même calcul de taille avec le nombre de bits nécessaires pour représenter votre jeu de caractères. Le calcul du rapport pour tout jeu de caractères arbitraire est:) ratio = 8 / log2(len(charset)).

Bien que ces deux solutions soient sécurisées, simples, devraient être rapides et ne gaspillent pas votre pool d'entropie cryptographique.

Voici le terrain de jeu montrant qu'il fonctionne pour n'importe quelle taille. https://play.golang.org/p/i61WUVR8_3Z

Steven Soroka
la source
il convient de mentionner que Go Playground renvoie toujours le même nombre aléatoire, donc vous ne verrez pas là-dedans différentes chaînes aléatoires lors des différentes exécutions de ce code
TPPZ
2
func Rand(n int) (str string) {
    b := make([]byte, n)
    rand.Read(b)
    str = fmt.Sprintf("%x", b)
    return
}
Kevin
la source
Pourquoi cela génère-t-il n * 2 []byte?
M. Rostami
1

Voici ma façon) Utilisez math rand ou crypto rand comme vous le souhaitez.

func randStr(len int) string {
    buff := make([]byte, len)
    rand.Read(buff)
    str := base64.StdEncoding.EncodeToString(buff)
    // Base 64 can be longer than len
    return str[:len]
}
Dima
la source
0

Si vous êtes prêt à ajouter quelques caractères à votre pool de caractères autorisés, vous pouvez faire fonctionner le code avec tout ce qui fournit des octets aléatoires via un io.Reader. Ici, nous utilisons crypto/rand.

// len(encodeURL) == 64. This allows (x <= 265) x % 64 to have an even
// distribution.
const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"

// A helper function create and fill a slice of length n with characters from
// a-zA-Z0-9_-. It panics if there are any problems getting random bytes.
func RandAsciiBytes(n int) []byte {
    output := make([]byte, n)

    // We will take n bytes, one byte for each character of output.
    randomness := make([]byte, n)

    // read all random
    _, err := rand.Read(randomness)
    if err != nil {
        panic(err)
    }

    // fill output
    for pos := range output {
        // get random item
        random := uint8(randomness[pos])

        // random % 64
        randomPos := random % uint8(len(encodeURL))

        // put into output
        output[pos] = encodeURL[randomPos]
    }

    return output
}
0xcaff
la source
pourquoi est-il random % 64nécessaire?
Sung Cho
2
Parce que len(encodeURL) == 64. Si cela random % 64n'a pas été fait, cela randomPospourrait être> = 64 et provoquer une panique hors limites lors de l'exécution.
0xcaff
-1
/*
    korzhao
*/

package rand

import (
    crand "crypto/rand"
    "math/rand"
    "sync"
    "time"
    "unsafe"
)

// Doesn't share the rand library globally, reducing lock contention
type Rand struct {
    Seed int64
    Pool *sync.Pool
}

var (
    MRand    = NewRand()
    randlist = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
)

// init random number generator
func NewRand() *Rand {
    p := &sync.Pool{New: func() interface{} {
        return rand.New(rand.NewSource(getSeed()))
    },
    }
    mrand := &Rand{
        Pool: p,
    }
    return mrand
}

// get the seed
func getSeed() int64 {
    return time.Now().UnixNano()
}

func (s *Rand) getrand() *rand.Rand {
    return s.Pool.Get().(*rand.Rand)
}
func (s *Rand) putrand(r *rand.Rand) {
    s.Pool.Put(r)
}

// get a random number
func (s *Rand) Intn(n int) int {
    r := s.getrand()
    defer s.putrand(r)

    return r.Intn(n)
}

//  bulk get random numbers
func (s *Rand) Read(p []byte) (int, error) {
    r := s.getrand()
    defer s.putrand(r)

    return r.Read(p)
}

func CreateRandomString(len int) string {
    b := make([]byte, len)
    _, err := MRand.Read(b)
    if err != nil {
        return ""
    }
    for i := 0; i < len; i++ {
        b[i] = randlist[b[i]%(62)]
    }
    return *(*string)(unsafe.Pointer(&b))
}

24,0 ns / op 16 B / op 1 allocs /

korzhao
la source
salut! Bienvenue dans StackOverflow. Bien que vous ayez ajouté un extrait de code, votre réponse n'inclut aucun contexte sur "comment cela fonctionne" ou "pourquoi c'est ainsi". N'oubliez pas non plus que la question est posée en anglais. Vos commentaires doivent donc également être en anglais.
Cengiz Can Il y a
-2
const (
    chars       = "0123456789_abcdefghijkl-mnopqrstuvwxyz" //ABCDEFGHIJKLMNOPQRSTUVWXYZ
    charsLen    = len(chars)
    mask        = 1<<6 - 1
)

var rng = rand.NewSource(time.Now().UnixNano())

// RandStr 返回指定长度的随机字符串
func RandStr(ln int) string {
    /* chars 38个字符
     * rng.Int63() 每次产出64bit的随机数,每次我们使用6bit(2^6=64) 可以使用10次
     */
    buf := make([]byte, ln)
    for idx, cache, remain := ln-1, rng.Int63(), 10; idx >= 0; {
        if remain == 0 {
            cache, remain = rng.Int63(), 10
        }
        buf[idx] = chars[int(cache&mask)%charsLen]
        cache >>= 6
        remain--
        idx--
    }
    return *(*string)(unsafe.Pointer(&buf))
}

BenchmarkRandStr16-8 20000000 68,1 ns / op 16 B / op 1 allocs / op

user10987909
la source