¿Cómo generar una cadena aleatoria de una longitud fija en Go?

300

Quiero una cadena aleatoria de caracteres solamente (mayúsculas o minúsculas), sin números, en Ir. ¿Cuál es la forma más rápida y sencilla de hacer esto?

Anish Shah
fuente
2
@VinceEmigh: Aquí hay un meta tema que discute preguntas básicas. meta.stackoverflow.com/q/274645/395461 Personalmente, creo que las preguntas básicas están bien si están bien escritas y son sobre el tema. Mira las respuestas a continuación, ilustran un montón de cosas que serían útiles para alguien nuevo. Para bucles, tipo casting, make (), etc.
Shannon Matthews
2
@Shannon " Esta pregunta no muestra ningún esfuerzo de investigación " (primera respuesta altamente votada en su enlace) - A eso me refería. No muestra ningún esfuerzo de investigación. Ningún esfuerzo (un intento, o incluso afirmar que buscó en línea, lo que obviamente no ha hecho). Aunque sería útil para alguien nuevo , este sitio no está enfocado en enseñar nuevas personas. Se centra en responder problemas / preguntas de programación específicos, no en tutoriales / guías. Aunque podría usarse para este último, ese no es el enfoque y, por lo tanto, esta pregunta debería cerrarse. En cambio, su cuchara /:
Vince Emigh
99
@VinceEmigh Hice esta pregunta hace un año. Había buscado en línea cadenas al azar y también había leído documentos. Pero no fue útil. Si no he escrito en la pregunta, no significa que no haya investigado.
Anish Shah

Respuestas:

809

La solución de Paul proporciona una solución simple y general.

La pregunta pide "la forma más rápida y sencilla" . Abordemos también la parte más rápida . Llegaremos a nuestro código final y más rápido de manera iterativa. La evaluación comparativa de cada iteración se puede encontrar al final de la respuesta.

Todas las soluciones y el código de evaluación comparativa se pueden encontrar en Go Playground . El código en Playground es un archivo de prueba, no un ejecutable. Tienes que guardarlo en un archivo llamado XX_test.goy ejecutarlo con

go test -bench . -benchmem

Prólogo :

La solución más rápida no es una solución de acceso si solo necesita una cadena aleatoria. Para eso, la solución de Paul es perfecta. Esto es si el rendimiento sí importa. Aunque los primeros 2 pasos ( Bytes y resto ) pueden ser un compromiso aceptable: mejoran el rendimiento en un 50% (vea los números exactos en la sección II. Punto de referencia ), y no aumentan la complejidad de manera significativa.

Dicho esto, incluso si no necesita la solución más rápida, leer esta respuesta puede ser aventurero y educativo.

I. Mejoras

1. Génesis (Runas)

Como recordatorio, la solución general original que estamos mejorando es esta:

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

Si los caracteres para elegir y ensamblar la cadena aleatoria contienen solo las letras mayúsculas y minúsculas del alfabeto inglés, podemos trabajar con bytes solo porque las letras del alfabeto inglés se asignan a los bytes 1 a 1 en la codificación UTF-8 (que así es como Go almacena cadenas).

Entonces en lugar de:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

nosotros podemos usar:

var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

O mejor:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

Ahora esto ya es una gran mejora: podríamos lograr que sea un const(hay stringconstantes pero no hay constantes de corte ). Como ganancia adicional, la expresión len(letters)también será a const! (La expresión len(s)es constante si ses una cadena constante).

¿Y a qué costo? Nada en absoluto. stringSe puede indexar s que indexa sus bytes, perfecto, exactamente lo que queremos.

Nuestro próximo destino se ve así:

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

Las soluciones anteriores obtienen un número aleatorio para designar una letra aleatoria llamando a rand.Intn()qué delegados a Rand.Intn()qué delegados Rand.Int31n().

Esto es mucho más lento en comparación con el rand.Int63()que produce un número aleatorio con 63 bits aleatorios.

Entonces podríamos simplemente llamar rand.Int63()y usar el resto después de dividir por 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)
}

Esto funciona y es significativamente más rápido, la desventaja es que la probabilidad de todas las letras no será exactamente la misma (suponiendo que rand.Int63()produzca todos los números de 63 bits con la misma probabilidad). Aunque la distorsión es extremadamente pequeña ya que el número de letras 52es mucho más pequeño que 1<<63 - 1, por lo tanto, en la práctica esto está perfectamente bien.

Para que esto sea más fácil de entender: digamos que desea un número aleatorio en el rango de 0..5. Usando 3 bits aleatorios, esto produciría los números 0..1con doble probabilidad que desde el rango 2..5. Usando 5 bits aleatorios, los números dentro del rango 0..1ocurrirían con 6/32probabilidad y los números dentro del rango 2..5con 5/32probabilidad que ahora está más cerca del deseado. Aumentar el número de bits hace que esto sea menos significativo, cuando alcanza 63 bits, es insignificante.

4. Enmascaramiento

Sobre la base de la solución anterior, podemos mantener la distribución equitativa de las letras utilizando solo la mayor cantidad posible de bits del número aleatorio para representar la cantidad de letras. Así por ejemplo, si tenemos 52 cartas, se requiere 6 bits para representarlo: 52 = 110100b. Entonces solo usaremos los 6 bits más bajos del número devuelto por rand.Int63(). Y para mantener una distribución equitativa de las letras, solo "aceptamos" el número si cae dentro del rango 0..len(letterBytes)-1. Si los bits más bajos son mayores, lo descartamos y consultamos un nuevo número aleatorio.

Tenga en cuenta que la probabilidad de que los bits más bajos sean mayores o iguales len(letterBytes)es menor que 0.5en general ( 0.25en promedio), lo que significa que incluso si este fuera el caso, repetir este caso "raro" disminuye la posibilidad de no encontrar un buen número. Después de la nrepetición, la posibilidad de que todavía no tengamos un buen índice es mucho menor pow(0.5, n), y esto es solo una estimación superior. En el caso de 52 letras, la posibilidad de que los 6 bits más bajos no sean buenos es solo (64-52)/64 = 0.19; lo que significa, por ejemplo, que hay posibilidades de no tener un buen número después de 10 repeticiones 1e-8.

Entonces aquí está la solución:

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. Enmascaramiento mejorado

La solución anterior solo usa los 6 bits más bajos de los 63 bits aleatorios devueltos por rand.Int63(). Esto es un desperdicio ya que obtener los bits aleatorios es la parte más lenta de nuestro algoritmo.

Si tenemos 52 letras, eso significa que 6 bits codifican un índice de letras. Entonces 63 bits aleatorios pueden designar 63/6 = 10diferentes índices de letras. Usemos todos esos 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. Fuente

El Enmascaramiento mejorado es bastante bueno, no hay mucho que podamos mejorar. Podríamos, pero no vale la pena la complejidad.

Ahora busquemos algo más para mejorar. La fuente de los números aleatorios.

Hay un crypto/randpaquete que proporciona una Read(b []byte)función, por lo que podríamos usarla para obtener tantos bytes con una sola llamada como necesitemos. Esto no ayudaría en términos de rendimiento, ya que crypto/randimplementa un generador de números pseudoaleatorios criptográficamente seguro, por lo que es mucho más lento.

Así que ceñámonos al math/randpaquete. El rand.Randutiliza un rand.Sourcecomo la fuente de bits aleatorios. rand.Sourcees una interfaz que especifica un Int63() int64método: exactamente y lo único que necesitábamos y usábamos en nuestra última solución.

Entonces, realmente no necesitamos un rand.Rand(ya sea explícito o global, uno compartido del randpaquete), a rand.Sourcees perfectamente suficiente para nosotros:

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

También tenga en cuenta que esta última solución no requiere que inicialice (inicialice) el global Randdel math/randpaquete, ya que no se utiliza (y nuestro rand.Sourcese inicializa / sembró correctamente).

Una cosa más a tener en cuenta aquí: paquete de documentos de math/randestados:

La fuente predeterminada es segura para uso concurrente por múltiples goroutines.

Por lo tanto, la fuente predeterminada es más lenta que la Sourceque se puede obtener rand.NewSource(), porque la fuente predeterminada debe proporcionar seguridad bajo acceso / uso concurrente, mientras rand.NewSource()que no ofrece esto (y, Sourcepor lo tanto, es más probable que la devolución sea más rápida).

7. Utilizando strings.Builder

Todas las soluciones anteriores devuelven un stringcontenido cuyo primero se construye en un segmento ( []runeen Génesis y []byteen soluciones posteriores) y luego se convierte a string. Esta conversión final tiene que hacer una copia del contenido de la porción, porque los stringvalores son inmutables, y si la conversión no hiciera una copia, no se podría garantizar que el contenido de la cadena no se modifique a través de su porción original. Para obtener detalles, consulte ¿Cómo convertir la cadena utf8 a [] byte? y golang: [] byte (cadena) vs [] byte (* cadena) .

Ir 1.10 introducido strings.Builder. strings.Builderun nuevo tipo que se puede utilizar para construir contenido de un stringsimilar al bytes.Buffer. Lo hace internamente usando a []byte, y cuando terminamos, podemos obtener el stringvalor final usando su Builder.String()método. Pero lo bueno es que hace esto sin realizar la copia de la que acabamos de hablar. Se atreve a hacerlo porque el segmento de bytes utilizado para construir el contenido de la cadena no está expuesto, por lo que se garantiza que nadie puede modificarlo de manera involuntaria o maliciosa para alterar la cadena "inmutable" producida.

Entonces, nuestra siguiente idea es no construir la cadena aleatoria en un segmento, sino con la ayuda de a strings.Builder, de modo que una vez que hayamos terminado, podamos obtener y devolver el resultado sin tener que hacer una copia. Esto puede ayudar en términos de velocidad, y definitivamente ayudará en términos de uso de memoria y asignaciones.

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

Tenga en cuenta que después de crear un nuevo strings.Buidler, llamamos a su Builder.Grow()método, asegurándonos de que asigne un segmento interno lo suficientemente grande (para evitar reasignaciones a medida que agregamos letras al azar).

8. "Mimicing" strings.Buildercon paqueteunsafe

strings.Builderconstruye la cadena de forma interna []byte, igual que nosotros mismos. Básicamente, hacerlo a través de a strings.Buildertiene algo de sobrecarga, lo único a lo que nos cambiamos strings.Builderes evitar la copia final del segmento.

strings.Builderevita la copia final usando el paquete unsafe:

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

La cuestión es que también podemos hacer esto nosotros mismos. Entonces, la idea aquí es volver a construir la cadena aleatoria en a []byte, pero cuando hayamos terminado, no la convierta para stringque regrese, sino que realice una conversión insegura: obtenga una stringque apunte a nuestro segmento de bytes como datos de cadena .

Así es como se puede hacer:

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

Go 1.7 agregó una rand.Read()función y un Rand.Read()método. Deberíamos tener la tentación de usarlos para leer tantos bytes como sea necesario en un solo paso, a fin de lograr un mejor rendimiento.

Hay un pequeño "problema" con esto: ¿cuántos bytes necesitamos? Podríamos decir: tanto como el número de letras de salida. Pensaríamos que esta es una estimación superior, ya que un índice de letras usa menos de 8 bits (1 byte). Pero en este punto ya estamos peor (ya que obtener los bits aleatorios es la "parte difícil"), y estamos obteniendo más de lo necesario.

También tenga en cuenta que para mantener una distribución equitativa de todos los índices de letras, puede haber algunos datos aleatorios "basura" que no podremos usar, por lo que terminaríamos omitiendo algunos datos y, por lo tanto, terminaremos cortos cuando revisemos todos la rebanada de bytes. Necesitaríamos obtener más bytes aleatorios, "recursivamente". Y ahora incluso estamos perdiendo la randventaja de " llamada única al paquete" ...

Podríamos "algo" optimizar el uso de los datos aleatorios que adquirimos math.Rand(). Podemos estimar cuántos bytes (bits) necesitaremos. 1 letra requiere letterIdxBitsbits, y necesitamos nletras, por lo que necesitamos n * letterIdxBits / 8.0redondear bytes. Podemos calcular la probabilidad de que un índice aleatorio no sea utilizable (ver arriba), por lo que podríamos solicitar más que será "más probable" suficiente (si resulta que no lo es, repetimos el proceso). Podemos procesar el segmento de bytes como un "flujo de bits", por ejemplo, para lo cual tenemos una buena biblioteca de terceros: github.com/icza/bitio(divulgación: soy el autor).

Pero el código de referencia todavía muestra que no estamos ganando. ¿Por que es esto entonces?

La respuesta a la última pregunta es porque rand.Read()usa un bucle y sigue llamando Source.Int63()hasta que llena el segmento pasado. Exactamente lo que hace la RandStringBytesMaskImprSrc()solución, sin el búfer intermedio y sin la complejidad añadida. Por eso RandStringBytesMaskImprSrc()permanece en el trono. Sí, RandStringBytesMaskImprSrc()utiliza un no sincronizado a rand.Sourcediferencia rand.Read(). Pero el razonamiento aún se aplica; y lo cual se prueba si usamos en Rand.Read()lugar de rand.Read()(el primero también no está sincronizado).

II Punto de referencia

Muy bien, es hora de comparar las diferentes soluciones.

Momento de la verdad:

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

Simplemente al cambiar de runas a bytes, inmediatamente tenemos un aumento del rendimiento del 24% , y el requisito de memoria cae a un tercio .

Deshacerse rand.Intn()y usar en su rand.Int63()lugar le da otro impulso del 20% .

El enmascaramiento (y la repetición en caso de grandes índices) se ralentiza un poco (debido a las llamadas de repetición): -22% ...

Pero cuando hacemos uso de todos (o la mayoría) de los 63 bits aleatorios (10 índices de una rand.Int63()llamada): eso acelera a lo grande: 3 veces .

Si nos conformamos con un (no predeterminado, nuevo) en rand.Sourcelugar de rand.Rand, nuevamente ganamos un 21%.

Si utilizamos strings.Builder, ganamos un pequeño 3.5% en velocidad , ¡pero también logramos una reducción del 50% en el uso y las asignaciones de memoria! ¡Eso es bueno!

Finalmente, si nos atrevemos a usar el paquete en unsafelugar de strings.Builder, nuevamente ganamos un buen 14% .

Comparando la solución final con la inicial: RandStringBytesMaskImprSrcUnsafe()es 6.3 veces más rápido que RandStringRunes(), usa una sexta memoria y la mitad de las asignaciones . Misión cumplida.

icza
fuente
8
@RobbieV Sí, porque rand.Sourcese usa un compartido . Una mejor solución sería pasar un rand.Sourcea la RandStringBytesMaskImprSrc()función, y de esa manera no se requiere bloqueo y, por lo tanto, el rendimiento / eficiencia no se ve afectado. Cada gorutina podría tener la suya Source.
icza
113
@icza, esa es una de las mejores respuestas que vi durante mucho tiempo en SO!
Astropánico
1
@MikeAtlas: debe evitar el uso defercuando sea obvio que no lo necesita. Ver grokbase.com/t/gg/golang-nuts/158zz5p42w/…
Zan Lynx
1
@ZanLynx thx para la propina; aunque deferdesbloquear un mutex, ya sea inmediatamente antes o después de llamar a un candado, es sobre todo una buena idea; Tiene la garantía de que no se olvidará de desbloquear, sino que también se desbloqueará incluso en una función media de pánico no fatal.
Mike Atlas
1
@RobbieV parece que este código es seguro para subprocesos / rutinas porque la fuente compartida subyacente ya es un LockedSource que implementa el mutex ( golang.org/src/math/rand/rand.go:259 ).
adityajones
130

Simplemente puede escribir código para ello. Este código puede ser un poco más simple si desea confiar en que todas las letras sean bytes individuales cuando se codifican 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
fuente
30
No te olvides del rand.Seed (), de lo contrario, obtienes la misma cadena cada vez que inicias ... rand.Seed (time.Now (). UTC (). UnixNano ())
Evan Lin
2
La adición de Evan es correcta, sin embargo, hay otras opciones similares: rand.Seed(time.Now().Unix())orand.Seed(time.Now().UnixNano())
openwonk
77
Para un secreto difícil de adivinar: una contraseña, una clave criptográfica, etc., nunca use math/rand; use crypto/rand(como la opción 1 de @ Not_A_Golfer) en su lugar.
twotwotwo
1
@EvanLin ¿No será esto adivinable? Si tengo que sembrar el generador, entonces el atacante podría adivinar el tiempo con el que lo estoy sembrando y predecir la misma salida que estoy generando.
Matej
44
Tenga en cuenta que si está probando el programa anterior con seed, en el patio de recreo, obtendrá el mismo resultado todo el tiempo. Lo estaba probando en el patio de recreo y después de un tiempo me di cuenta de esto. Funcionó bien de lo contrario para mí. Espero que ahorre tiempo a alguien :)
Gaurav Sinha
18

Utilice el paquete uniuri , que genera cadenas uniformes (imparciales) criptográficamente seguras.

Descargo de responsabilidad: soy el autor del paquete

dchest
fuente
1
Aparte: el autor, dchest, es un excelente desarrollador y ha producido una serie de paquetes pequeños y útiles como este.
Roshambo
16

Dos opciones posibles (puede haber más, por supuesto):

  1. Puede utilizar el crypto/randpaquete que admite la lectura de conjuntos de bytes aleatorios (de / dev / urandom) y está orientado a la generación aleatoria criptográfica. ver http://golang.org/pkg/crypto/rand/#example_Read . Sin embargo, podría ser más lento que la generación normal de números pseudoaleatorios.

  2. Tome un número aleatorio y divídalo con md5 o algo así.

Not_a_Golfer
fuente
4

Siguiendo la icza'ssolución maravillosamente explicada, aquí hay una modificación que usa en crypto/randlugar 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 desea una solución más genérica, que le permita pasar la porción de bytes de caracteres para crear la cadena, puede intentar usar esto:

// 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 desea pasar su propia fuente de aleatoriedad, sería trivial modificar lo anterior para aceptar un en io.Readerlugar de usarlo crypto/rand.

Chris
fuente
2

Si desea números aleatorios criptográficamente seguros , y el juego de caracteres exacto es flexible (digamos, base64 está bien), puede calcular exactamente cuál es la longitud de caracteres aleatorios que necesita del tamaño de salida deseado.

El texto base 64 es 1/3 más largo que la base 256. (2 ^ 8 vs 2 ^ 6; 8bits / 6bits = relación 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: también puede usar RawStdEncoding si prefiere los caracteres + y / a - y _

Si quiere un hex, la base 16 es 2 veces más larga 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
}

Sin embargo, puede extender esto a cualquier conjunto de caracteres arbitrario si tiene un codificador de base256 a baseN para su conjunto de caracteres. Puede hacer el mismo cálculo de tamaño con cuántos bits se necesitan para representar su conjunto de caracteres. El cálculo de la relación para cualquier conjunto de caracteres arbitrario es:) ratio = 8 / log2(len(charset)).

Aunque ambas soluciones son seguras, simples, deberían ser rápidas y no desperdiciar su grupo de criptoentropía.

Aquí está el patio de juegos que muestra que funciona para cualquier tamaño. https://play.golang.org/p/i61WUVR8_3Z

Steven Soroka
fuente
Vale la pena mencionar que Go Playground siempre devuelve el mismo número aleatorio, por lo que no verá allí diferentes cadenas aleatorias en diferentes ejecuciones de ese código
TPPZ
2
func Rand(n int) (str string) {
    b := make([]byte, n)
    rand.Read(b)
    str = fmt.Sprintf("%x", b)
    return
}
Kevin
fuente
¿Por qué genera n * 2 []byte?
M. Rostami
1

Aquí está mi manera) Use math rand o crypto rand como lo desee.

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
fuente
0

Si está dispuesto a agregar algunos caracteres a su grupo de caracteres permitidos, puede hacer que el código funcione con cualquier cosa que proporcione bytes aleatorios a través de un io.Reader. Aquí lo estamos 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
}
0xcaff
fuente
¿Por qué es random % 64necesario?
Sung Cho
2
Debido len(encodeURL) == 64. Si random % 64no se hizo, randomPospodría ser> = 64 y provocar un pánico fuera de los límites en tiempo de ejecución.
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
fuente
¡Hola! Bienvenido a StackOverflow. Aunque agregó un fragmento de código, su respuesta no incluye ningún contexto sobre "cómo funciona" o "por qué se hace así". Además, recuerde que la pregunta se hace en inglés, por lo que sus comentarios también deben estar en inglés.
Cengiz Can hace
-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

usuario10987909
fuente