web-dev-qa-db-ja.com

Goで固定長のランダムな文字列を生成する方法

Goでは、ランダムな文字列(大文字または小文字)のみを使用し、数字は使用しないでください。これを行うための最速かつ最も簡単な方法は何ですか?

242
Anish Shah

あなたはそれのためのコードを書くことができます。このコードは、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))
}
108
Paul Hankin

考えられる選択肢は2つあります(もちろんもっとあります)。

  1. (/ dev/urandomからの)ランダムバイト配列の読み取りをサポートし、暗号化ランダム生成を対象としたcrypto/Randパッケージを使用できます。 http://golang.org/pkg/crypto/Rand/#example_Read を参照してください。ただし、通常の擬似乱数生成よりも遅くなる可能性があります。

  2. 乱数を取り、md5またはこれに似たものを使ってハッシュします。

16
Not_a_Golfer

暗号的に安全で統一された(バイアスのない)文字列を生成するpackage uniuri を使用してください。

免責事項:私はパッケージの作者です

10
dchest

icza'sが素晴らしく説明された解決策に続いて、これはcrypto/Randの代わりに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
}

より一般的な解決策が必要な場合は、文字バイトのスライスを渡して文字列を作成することができます。これを使用して試すことができます。

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

独自の乱数の発生源を渡したい場合は、io.Readerを使用するのではなく、crypto/Randを受け入れるように上記を変更することが簡単です。

3
Chris
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割り当て/ op

0
user10987909

暗号学的に安全な乱数が必要で、正確な文字セットが柔軟である場合(base64が問題ないなど)、必要な乱数の長さを正確に計算できます。希望する出力サイズ.

基数64のテキストは、基数256よりも1/3長くなります。(2 ^ 8対2 ^ 6、8ビット/ 6ビット= 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
}

注:+と/の文字を - と_よりも使用したい場合は、RawStdEncodingも使用できます。

16進数が必要な場合は、基数16の方が基数256よりも2倍長くなります(2 ^ 8対2 ^ 4、8ビット/ 4ビット= 2倍の比率)。

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
}

ただし、文字セットにbase256からbaseNへのエンコーダがある場合は、これを任意の文字セットに拡張できます。文字セットを表すのに必要なビット数を使用して、同じサイズの計算を実行できます。任意の文字セットに対する比率の計算は、ratio = 8 / log2(len(charset)))です。

これらの解決策はどちらも安全で単純で、高速でなければならず、暗号エントロピープールを無駄にしないでください。

これは、それがあらゆるサイズで機能することを示す遊び場です。 https://play.golang.org/p/i61WUVR8_3Z

0
Steven Soroka

これが私のやり方です)あなたが望むように数学のRandか暗号のRandを使ってください。

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
Dima