Come generare una stringa casuale di lunghezza fissa in Go?


300

Voglio solo una stringa casuale di caratteri (maiuscoli o minuscoli), senza numeri, in Go. Qual è il modo più veloce e semplice per farlo?


2
@VinceEmigh: ecco un meta argomento che discute le domande di base. meta.stackoverflow.com/q/274645/395461 Personalmente, penso che le domande di base siano ok se scritte bene e in tema. Guarda le risposte qui sotto, illustrano un sacco di cose che potrebbero essere utili a chi è nuovo. Per i loop, digitare casting, make (), ecc.
Shannon Matthews,

2
@Shannon " Questa domanda non mostra alcuno sforzo di ricerca " (prima risposta altamente votata nel tuo link) - Ecco a cosa mi riferivo. Non mostra alcuno sforzo di ricerca. Nessuno sforzo (un tentativo, o addirittura affermando che ha cercato online, cosa che ovviamente non ha). Anche se sarebbe utile per qualcuno di nuovo , questo sito non è focalizzato sull'insegnamento di nuove persone. Si concentra sulla risposta a specifici problemi / domande di programmazione, non su tutorial / guide. Anche se potrebbe essere usato per quest'ultimo, non è questo il punto focale, e quindi questa domanda dovrebbe essere chiusa. Invece, è viziato /:
Vince Emigh il

9
@VinceEmigh ho fatto questa domanda un anno fa. Avevo cercato online stringhe casuali e avevo letto anche documenti. Ma non è stato utile. Se non ho scritto nella domanda, non significa che non ho fatto ricerche.
Anish Shah,

Risposte:


809

La soluzione di Paul fornisce una soluzione semplice e generale.

La domanda chiede "il modo più veloce e semplice" . Affrontiamo anche la parte più veloce . Arriveremo al nostro codice finale, il più veloce in modo iterativo. Il benchmarking di ogni iterazione può essere trovato alla fine della risposta.

Tutte le soluzioni e il codice di benchmarking sono disponibili nel Go Playground . Il codice nel Playground è un file di prova, non un eseguibile. Devi salvarlo in un file chiamato XX_test.goed eseguirlo

go test -bench . -benchmem

Prefazione :

La soluzione più veloce non è una soluzione di riferimento se hai solo bisogno di una stringa casuale. Per questo, la soluzione di Paul è perfetta. Questo è se le prestazioni sono importanti. Sebbene i primi 2 passaggi ( byte e resto ) possano essere un compromesso accettabile: migliorano le prestazioni di circa il 50% (vedere i numeri esatti nella sezione II. Benchmark ) e non aumentano la complessità in modo significativo.

Detto questo, anche se non hai bisogno della soluzione più veloce, leggere questa risposta potrebbe essere avventuroso ed educativo.

I. Miglioramenti

1. Genesi (rune)

Come promemoria, la soluzione originale e generale che stiamo migliorando è questa:

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. Byte

Se i caratteri tra cui scegliere e assemblare la stringa casuale contengono solo le lettere maiuscole e minuscole dell'alfabeto inglese, possiamo lavorare con i byte solo perché le lettere dell'alfabeto inglese mappano i byte da 1 a 1 nella codifica UTF-8 (che è come Go archivia le stringhe).

Quindi invece di:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

possiamo usare:

var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

O ancora meglio:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

Ora questo è già un grande miglioramento: potremmo ottenere che sia un const(ci sono stringcostanti ma non ci sono costanti di fetta ). Come guadagno extra, anche l'espressione len(letters)sarà a const! (L'espressione len(s)è costante se sè una costante di stringa.)

E a quale costo? Niente di niente. strings può essere indicizzato che indicizza i suoi byte, perfetto, esattamente quello che vogliamo.

La nostra prossima destinazione si presenta così:

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. Resto

Le soluzioni precedenti ottengono un numero casuale per designare una lettera casuale chiamando i rand.Intn()delegati a Rand.Intn()quali delegati Rand.Int31n().

Questo è molto più lento rispetto al rand.Int63()quale produce un numero casuale con 63 bit casuali.

Quindi potremmo semplicemente chiamare rand.Int63()e usare il resto dopo aver diviso per 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)
}

Questo funziona ed è significativamente più veloce, lo svantaggio è che la probabilità di tutte le lettere non sarà esattamente la stessa (supponendo che rand.Int63()produca tutti i numeri a 63 bit con uguale probabilità). Anche se la distorsione è estremamente ridotta in quanto il numero di lettere 52è molto più piccolo di 1<<63 - 1, quindi in pratica questo va benissimo.

Per renderlo più semplice: supponiamo che tu voglia un numero casuale nell'intervallo di 0..5. Usando 3 bit casuali, questo produrrebbe i numeri 0..1con doppia probabilità rispetto all'intervallo 2..5. Usando 5 bit casuali, 0..1si verificherebbero numeri nell'intervallo con 6/32probabilità e numeri nell'intervallo 2..5con 5/32probabilità che ora è più vicino al desiderato. Aumentare il numero di bit rende questo meno significativo, quando si raggiunge 63 bit, è trascurabile.

4. Mascheratura

Basandoci sulla soluzione precedente, possiamo mantenere la distribuzione equa delle lettere utilizzando solo il numero più basso di bit del numero casuale quanti ne sono necessari per rappresentare il numero di lettere. Così per esempio se abbiamo 52 lettere, richiede 6 bit per rappresentare esso: 52 = 110100b. Quindi utilizzeremo solo i 6 bit più bassi del numero restituito da rand.Int63(). E per mantenere un'equa distribuzione delle lettere, "accettiamo" il numero solo se rientra nell'intervallo 0..len(letterBytes)-1. Se i bit più bassi sono maggiori, lo scartiamo e richiediamo un nuovo numero casuale.

Si noti che la probabilità che i bit più bassi siano maggiori o uguali a len(letterBytes)è inferiore rispetto 0.5al generale ( 0.25in media), il che significa che, anche se così fosse, ripetere questo caso "raro" diminuisce la possibilità di non trovare un buon numero. Dopo la nripetizione, la possibilità che non abbiamo un buon indice è molto inferiore pow(0.5, n), e questa è solo una stima superiore. Nel caso di 52 lettere la possibilità che i 6 bit più bassi non siano buoni è solo (64-52)/64 = 0.19; il che significa ad esempio che è probabile che non ci sia un buon numero dopo 10 ripetizioni 1e-8.

Quindi ecco la soluzione:

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. Miglioramento del mascheramento

La soluzione precedente utilizza solo i 6 bit più bassi dei 63 bit casuali restituiti da rand.Int63(). Questo è uno spreco poiché ottenere i bit casuali è la parte più lenta del nostro algoritmo.

Se abbiamo 52 lettere, ciò significa che 6 bit codificano un indice di lettere. Quindi 63 bit casuali possono designare 63/6 = 10diversi indici di lettere. Usiamo tutti quei 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. Fonte

Il mascheramento migliorato è abbastanza buono, non possiamo migliorarlo molto. Potremmo, ma non ne vale la pena.

Ora troviamo qualcos'altro da migliorare. La fonte di numeri casuali.

Esiste un crypto/randpacchetto che fornisce una Read(b []byte)funzione, quindi potremmo usarlo per ottenere tanti byte con una sola chiamata quanti ne abbiamo bisogno. Ciò non aiuterebbe in termini di prestazioni in quanto crypto/randimplementa un generatore di numeri pseudocasuali crittograficamente sicuro, quindi è molto più lento.

Quindi atteniamoci al math/randpacchetto. Il rand.Randutilizza una rand.Sourcecome sorgente di bit casuali. rand.Sourceè un'interfaccia che specifica un Int63() int64metodo: esattamente e l'unica cosa di cui avevamo bisogno e che abbiamo usato nella nostra ultima soluzione.

Quindi non abbiamo davvero bisogno di un rand.Rand(esplicito o globale, condiviso del randpacchetto), un rand.Sourceè abbastanza perfetto per noi:

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

Si noti inoltre che quest'ultima soluzione non richiede l'inizializzazione (seed) del globale Randdel math/randpacchetto in quanto non utilizzato (e il nostro rand.Sourceè correttamente inizializzato / seeded).

Un'altra cosa da notare qui: pacchetto doc di math/randstati:

La fonte predefinita è sicura per l'uso simultaneo da parte di più goroutine.

Quindi la fonte predefinita è più lenta di Sourcequella che può essere ottenuta da rand.NewSource(), perché la fonte predefinita deve fornire sicurezza in caso di accesso / uso simultaneo, mentre rand.NewSource()non lo offre (e quindi Sourceè più probabile che la restituzione da essa sia più veloce).

7. Utilizzo strings.Builder

Tutte le soluzioni precedenti restituiscono a il stringcui contenuto viene prima creato in una sezione ( []runein Genesi e []bytenelle soluzioni successive), quindi convertito in string. Questa conversione finale deve creare una copia del contenuto della sezione, poiché i stringvalori sono immutabili e se la conversione non ne crea una copia, non è possibile garantire che il contenuto della stringa non venga modificato tramite la sezione originale. Per i dettagli, vedere Come convertire la stringa utf8 in [] byte? e golang: [] byte (stringa) vs [] byte (* stringa) .

Go 1.10 introdotto strings.Builder. strings.Builderun nuovo tipo che possiamo usare per creare contenuti di un stringsimile bytes.Buffer. Lo fa internamente usando a []byte, e quando abbiamo finito, possiamo ottenere il stringvalore finale usando il suo Builder.String()metodo. Ma la cosa interessante è che lo fa senza eseguire la copia di cui abbiamo appena parlato sopra. Osa farlo perché la porzione di byte utilizzata per costruire il contenuto della stringa non è esposta, quindi è garantito che nessuno può modificarlo involontariamente o maliziosamente per alterare la stringa "immutabile" prodotta.

Quindi la nostra prossima idea è di non costruire la stringa casuale in una sezione, ma con l'aiuto di a strings.Builder, quindi una volta che abbiamo finito, possiamo ottenere e restituire il risultato senza doverne fare una copia. Questo può aiutare in termini di velocità e sicuramente aiuterà in termini di utilizzo della memoria e allocazioni.

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

Nota che dopo aver creato un nuovo strings.Buidler, abbiamo chiamato il suo Builder.Grow()metodo, assicurandoci che alloca una porzione interna abbastanza grande (per evitare riallocazioni quando aggiungiamo le lettere casuali).

8. "Mimizing" strings.Buildercon pacchettounsafe

strings.Buildercostruisce la stringa in un interno []byte, lo stesso di noi stessi. Quindi fondamentalmente farlo tramite un strings.Builderha un certo sovraccarico, l'unica cosa a cui siamo passati strings.Builderè evitare la copia finale della porzione.

strings.Builderevita la copia finale usando il pacchetto unsafe:

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

Il fatto è che possiamo farlo anche noi stessi. Quindi l'idea qui è di tornare a costruire la stringa casuale in a []byte, ma quando abbiamo finito, non convertirla in stringper restituirla, ma fai una conversione non sicura: ottieni una stringche punta alla nostra porzione di byte come dati della stringa .

Ecco come si può fare:

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. Utilizzo rand.Read())

Go 1.7 ha aggiunto una rand.Read()funzione e un Rand.Read()metodo. Dovremmo essere tentati di usarli per leggere tutti i byte necessari in un solo passaggio, al fine di ottenere prestazioni migliori.

C'è un piccolo "problema" con questo: di quanti byte abbiamo bisogno? Potremmo dire: fino al numero di lettere di output. Pensiamo che questa sia una stima superiore, poiché un indice di lettere utilizza meno di 8 bit (1 byte). Ma a questo punto stiamo già facendo di peggio (poiché ottenere i bit casuali è la "parte difficile") e stiamo ottenendo più del necessario.

Si noti inoltre che per mantenere una distribuzione equa di tutti gli indici di lettere, potrebbero esserci alcuni dati casuali "spazzatura" che non saremo in grado di utilizzare, quindi finiremmo per saltare alcuni dati, e quindi finiremmo brevemente quando esamineremo tutti la porzione di byte. Dovremmo ottenere ulteriori byte casuali, "ricorsivamente". E ora stiamo persino perdendo il vantaggio di "chiamata singola per randpacchetto" ...

Potremmo "in qualche modo" ottimizzare l'utilizzo dei dati casuali da cui acquisiamo math.Rand(). Potremmo stimare quanti byte (bit) avremo bisogno. 1 lettera richiede letterIdxBitsbit e abbiamo bisogno di nlettere, quindi abbiamo bisogno di n * letterIdxBits / 8.0arrotondare i byte. Siamo in grado di calcolare la probabilità che un indice casuale non sia utilizzabile (vedi sopra), quindi potremmo richiedere di più che "più probabilmente" sarà sufficiente (se si scopre che non lo è, ripetiamo il processo). Possiamo elaborare la porzione di byte come un "flusso di bit" per esempio, per il quale abbiamo una bella libreria di terze parti: github.com/icza/bitio(divulgazione: sono l'autore).

Ma il codice benchmark mostra ancora che non stiamo vincendo. Perché è così?

La risposta all'ultima domanda è perché rand.Read()utilizza un ciclo e continua a chiamare Source.Int63()fino a riempire la sezione passata. Esattamente quello che fa la RandStringBytesMaskImprSrc()soluzione, senza il buffer intermedio e senza la complessità aggiunta. Ecco perché RandStringBytesMaskImprSrc()rimane sul trono. Sì, RandStringBytesMaskImprSrc()utilizza un non sincronizzato a rand.Sourcedifferenza rand.Read(). Ma il ragionamento si applica ancora; e ciò è dimostrato se usiamo Rand.Read()invece di rand.Read()(anche il primo non è sincronizzato).

II. Prova delle prestazioni

Va bene, è tempo di confrontare le diverse soluzioni.

Momento della verità:

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

Passando da rune a byte, abbiamo immediatamente un aumento delle prestazioni del 24% e il requisito di memoria scende a un terzo .

Sbarazzarsi rand.Intn()e usare rand.Int63()invece dà un altro 20% di spinta.

Il mascheramento (e la ripetizione in caso di grandi indici) rallenta leggermente (a causa delle chiamate ripetute): -22% ...

Ma quando utilizziamo tutti (o la maggior parte) dei 63 bit casuali (10 indici da una rand.Int63()chiamata): questo accelera alla grande: 3 volte .

Se ci accontentiamo di un (non predefinito, nuovo) rand.Sourceinvece di rand.Rand, guadagniamo di nuovo il 21%.

Se lo utilizziamo strings.Builder, otteniamo un piccolo 3,5% in velocità , ma abbiamo anche ottenuto una riduzione del 50% nell'utilizzo della memoria e nelle allocazioni! Bello!

Infine, se osiamo usare il pacchetto unsafeinvece di strings.Builder, otteniamo di nuovo un bel 14% .

Confrontando la soluzione finale con la soluzione iniziale: RandStringBytesMaskImprSrcUnsafe()è 6,3 volte più veloce di RandStringRunes(), utilizza una sesta memoria e metà delle allocazioni . Missione compiuta.


8
@RobbieV Sì, perché rand.Sourceviene utilizzato un condiviso . Una soluzione alternativa sarebbe quella di passare rand.Sourcea alla RandStringBytesMaskImprSrc()funzione, e in questo modo non è richiesto alcun blocco e quindi le prestazioni / efficienza non vengono effettuate. Ogni goroutine potrebbe avere il suo Source.
Icza,

113
@icza, questa è una delle migliori risposte che ho visto a lungo su SO!
astropanico

1
@MikeAtlas: dovrebbe evitare l'uso deferquando è ovvio che non ne hai bisogno. Vedi grokbase.com/t/gg/golang-nuts/158zz5p42w/…
Zan Lynx,

1
@ZanLynx thx per la punta; anche se defersbloccare un mutex immediatamente prima o dopo aver chiamato un lucchetto, l'IMO è per lo più un'ottima idea; hai la garanzia di non dimenticare di sbloccare ma anche di sbloccare anche in una funzione intermedia di panico non fatale.
Mike Atlas,

1
@RobbieV sembra che questo codice sia thread / goroutine sicuro perché la sorgente condivisa sottostante è già una LockedSource che implementa il mutex ( golang.org/src/math/rand/rand.go:259 ).
adityajones,

130

Puoi semplicemente scrivere il codice per questo. Questo codice può essere un po 'più semplice se si desidera fare affidamento sul fatto che tutte le lettere sono byte singoli quando sono codificati in 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))
}

30
Non dimenticare il rand.Seed (), altrimenti avrai la stessa stringa ogni volta che si avvia ... rand.Seed (time.Now (). UTC (). UnixNano ())
Evan Lin

2
L'aggiunta di Evan è corretta, tuttavia ci sono altre opzioni simili: rand.Seed(time.Now().Unix())oppurerand.Seed(time.Now().UnixNano())
openwonk,

7
Per un segreto difficile da indovinare - una password, una chiave crittografica, ecc. - non usare mai math/rand; usa invece crypto/rand(come l'opzione 1 di @ Not_A_Golfer).
twotwotwo

1
@EvanLin Non sarà indovinabile? Se devo seminare il generatore, allora l'attaccante potrebbe indovinare il tempo con cui lo sto seminando e prevedere lo stesso output che sto generando.
Matej,

4
Nota che se stai provando il programma sopra con seed, su go playground, restituirà sempre lo stesso risultato. Lo stavo provando nel parco giochi e dopo qualche tempo me ne sono reso conto. Per il resto ha funzionato bene. Spero che risparmi tempo a qualcuno :)
Gaurav Sinha

18

Usa il pacchetto uniuri , che genera stringhe uniformi (imparziali) crittograficamente sicure.

Disclaimer: sono l'autore del pacchetto


1
A parte: l'autore, dchest, è uno sviluppatore eccellente e ha prodotto una serie di piccoli e utili pacchetti come questo.
Roshambo,

16

Due possibili opzioni (ci potrebbero essere più ovviamente):

  1. È possibile utilizzare il crypto/randpacchetto che supporta la lettura di array di byte casuali (da / dev / urandom) ed è orientato alla generazione casuale crittografica. vedi http://golang.org/pkg/crypto/rand/#example_Read . Potrebbe essere più lento della normale generazione di numeri pseudo-casuali.

  2. Prendi un numero casuale e cancellalo usando md5 o qualcosa del genere.


4

Seguendo icza'suna soluzione meravigliosamente spiegata, ecco una modifica che utilizza crypto/randinvece di 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
}

Se desideri una soluzione più generica, che ti consenta di passare la porzione di byte di caratteri per creare la stringa da, puoi provare a usare questo:

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

Se vuoi passare alla tua fonte di casualità, sarebbe banale modificare quanto sopra per accettare un io.Readerinvece di usare crypto/rand.


2

Se vuoi numeri casuali crittograficamente sicuri e l'esatto set di caratteri è flessibile (diciamo, base64 va bene), puoi calcolare esattamente quale lunghezza di caratteri casuali hai bisogno dalla dimensione di output desiderata.

Il testo Base 64 è 1/3 più lungo della base 256. (Rapporto 2 ^ 8 vs 2 ^ 6; 8 bit / 6 bit = 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
}

Nota: puoi anche usare RawStdEncoding se preferisci + e / caratteri a - e _

Se si desidera esadecimale, la base 16 è 2x più lunga della base 256. (Rapporto 2 ^ 8 vs 2 ^ 4; 8 bit / 4 bit = 2x)

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
}

Tuttavia, è possibile estenderlo a qualsiasi set di caratteri arbitrario se si dispone di un codificatore da base256 a baseN per il proprio set di caratteri. Puoi fare lo stesso calcolo delle dimensioni con quanti bit sono necessari per rappresentare il tuo set di caratteri. Il calcolo del rapporto per qualsiasi set di caratteri arbitrario è:) ratio = 8 / log2(len(charset)).

Sebbene entrambe queste soluzioni siano sicure, semplici, dovrebbero essere veloci e non sprecare il tuo pool di entropia cripto.

Ecco il parco giochi che mostra che funziona per qualsiasi dimensione. https://play.golang.org/p/i61WUVR8_3Z


vale la pena ricordare che Go Playground restituisce sempre lo stesso numero casuale, quindi non vedrai lì diverse stringhe casuali in diverse esecuzioni di quel codice
TPPZ

2
func Rand(n int) (str string) {
    b := make([]byte, n)
    rand.Read(b)
    str = fmt.Sprintf("%x", b)
    return
}

Perché genera n * 2 []byte?
M. Rostami,

1

Ecco la mia strada) Usa la matematica rand o crypto rand come desideri.

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

0

Se sei disposto ad aggiungere alcuni caratteri al tuo pool di caratteri consentiti, puoi far funzionare il codice con qualsiasi cosa che fornisca byte casuali tramite un io.Reader. Qui stiamo usando 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
}

perché è random % 64necessario?
Sung Cho,

2
A causa len(encodeURL) == 64. Se random % 64non è stato fatto, randomPospotrebbe essere> = 64 e causare un panico fuori limite durante l'esecuzione.
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 alloc /


Ciao! Benvenuto in StackOverflow. Sebbene tu abbia aggiunto uno snippet di codice, la tua risposta non include alcun contesto su "come funziona" o "perché è così che viene fatto". Ricorda inoltre che la domanda viene posta in inglese, quindi i tuoi commenti dovrebbero essere anche in inglese.
Cengiz Can

-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 alloc / op

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.