Go에서 고정 길이의 임의 문자열을 생성하는 방법은 무엇입니까?


300

Go에서 임의의 문자열 만 (대문자 또는 소문자), 숫자 없음을 원합니다. 가장 빠르고 간단한 방법은 무엇입니까?


2
@VinceEmigh : 기본적인 질문을 다루는 메타 주제가 있습니다. meta.stackoverflow.com/q/274645/395461 개인적으로, 기본 질문은 잘 작성되어 있고 주제에 관한 것이 좋다고 생각합니다. 아래 답변을 살펴보십시오. 새로운 사람이가는 데 도움이 될 많은 것들을 보여줍니다. 루프의 경우, 타입 캐스팅, make () 등
Shannon Matthews

2
@Shannon " 이 질문은 어떠한 연구 노력도 보여주지 않습니다 "(귀하의 링크에서 첫 번째로 높은 답변을 얻음 )-그것이 제가 말한 것입니다. 그는 연구 노력을 보여주지 않습니다. 전혀 노력하지 않습니다 (시도하거나 온라인에서 보았지만 분명히하지 않았습니다). 이 사이트는 새로운 사람에게 유용하지만 이 사이트는 새로운 사람들을 가르치는 데 중점을 두지 않습니다. 튜토리얼 / 가이드가 아닌 특정 프로그래밍 문제 / 질문에 답하는 데 중점을 둡니다. 후자를 위해 사용될 수 있지만, 그것은 초점이 아니므 로이 질문은 종결되어야합니다. 대신, 그 숟가락 / :
Vince Emigh 1

9
@VinceEmigh 1 년 전에이 질문을했습니다. 온라인에서 임의의 문자열을 검색하고 문서도 읽었습니다. 그러나 도움이되지 않았습니다. 내가 질문에 글을 쓰지 않았다고해서 내가 연구하지 않았다는 의미는 아닙니다.
Anish Shah

답변:


809

Paul의 솔루션단순 하고 일반적인 솔루션을 제공합니다.

질문은 "가장 빠르고 간단한 방법"을 요구 합니다. 가장 빠른 부분도 다루겠습니다 . 반복적 인 방식으로 가장 빠른 최종 코드에 도달합니다. 각 반복 벤치마킹은 답변 끝에서 찾을 수 있습니다.

모든 솔루션과 벤치마킹 코드는 Go Playground 에서 찾을 수 있습니다 . 놀이터의 코드는 테스트 파일이며 실행 파일이 아닙니다. 당신은라는 이름의 파일로 저장해야 XX_test.go하고 그것을 실행

go test -bench . -benchmem

서문 :

임의의 문자열 만 필요한 경우 가장 빠른 솔루션은 사용하지 않는 솔루션입니다. 이를 위해 Paul의 솔루션은 완벽합니다. 성능이 중요한 경우입니다. 처음 두 단계 ( Bytes and Remainder )는 허용 가능한 타협 일 수 있지만 성능을 50 % 정도 향상 시키며 ( II. 벤치 마크 섹션 의 정확한 숫자 참조 ) 복잡성을 크게 증가시키지 않습니다.

가장 빠른 솔루션이 필요하지 않더라도이 답변을 읽는 것은 모험적이고 교육적 일 수 있습니다.

I. 개선

1. 창세기 (룬)

우리가 개선하고있는 원래의 일반적인 해결책은 다음과 같습니다.

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. 바이트

임의의 문자열에서 선택하고 어셈블 할 문자에 영어 알파벳의 대문자와 소문자 만 포함 된 경우 UTF-8 인코딩에서 영어 알파벳 문자가 1에서 1로 바이트로 매핑되므로 바이트 만 사용할 수 있습니다. Go가 문자열을 저장하는 방법입니다).

따라서 대신 :

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

우리는 사용할 수 있습니다 :

var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

또는 더 나은 :

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

이제 이것은 이미 큰 개선입니다 : 우리는 그것을 달성 할 수 있습니다 const( string상수는 있지만 슬라이스 상수는 없습니다 ). 추가 이득으로 표현 len(letters)const! ( 문자열 상수 인 len(s)경우 표현식 은 s상수입니다.)

그리고 어떤 비용으로? 전혀 없습니다. string바이트를 인덱싱 할 수 있습니다. 정확히 원하는만큼 완벽합니다.

다음 목적지는 다음과 같습니다.

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. 나머지

이전 솔루션은 rand.Intn()어느 델리게이트에 Rand.Intn()어떤 델리게이트를 호출하여 랜덤 문자를 지정하는 난수를 얻 습니다 Rand.Int31n().

이것은 rand.Int63()63 개의 랜덤 비트를 갖는 임의의 수를 생성하는 것에 비해 훨씬 느리다 .

따라서 다음 rand.Int63()과 같이 나눈 후 나머지 를 호출 하여 사용할 수 있습니다 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)
}

이것은 효과적이고 훨씬 빠릅니다. 단점은 모든 문자의 확률이 정확히 동일하지 않을 것 rand.Int63()입니다 (같은 확률로 모든 63 비트 숫자를 생성 한다고 가정 ). 글자 수가 52보다 훨씬 적으므로 왜곡이 매우 작지만 1<<63 - 1실제로는 완벽하게 좋습니다.

이해하기 쉽도록 : 범위 내의 난수를 원한다고 가정 해 봅시다 0..5. 3 개의 랜덤 비트를 사용 0..1하면 범위에서보다 확률이 두 배인 숫자가 생성 2..5됩니다. 5 비트를 이용하여 임의의 범위의 숫자 0..1와 함께 발생할 수있는 6/32범위 내에서 확률 번호 2..55/32근접 원하는 이제 확률이다. 비트 수를 늘리면 63 비트에 도달 할 때 무시할만한 수준이됩니다.

4. 마스킹

이전 솔루션을 기반으로, 문자 수를 나타내는 데 필요한 수만큼의 난수 중 가장 낮은 비트 만 사용하여 문자의 동일한 분포를 유지할 수 있습니다. 예를 들어 52 개의 문자가있는 경우이를 나타내는 데 6 비트가 필요합니다 52 = 110100b. 따라서 우리는에서 반환 한 숫자 중 가장 낮은 6 비트 만 사용합니다 rand.Int63(). 그리고 문자의 균등 한 분포를 유지하기 위해 숫자가 범위 안에있는 경우에만 "수락"합니다 0..len(letterBytes)-1. 가장 낮은 비트가 더 크면 버리고 새로운 난수를 쿼리합니다.

가장 낮은 비트가 일반적으로 크거나 같을 확률은 평균 len(letterBytes)보다 작습니다 0.5. 0.25즉,이 경우에도이 "희귀 한"경우를 반복하면 좋은 결과를 찾지 못할 가능성이 줄어 듭니다. 번호. n반복 후 , 지수가 좋지 않을 확률은보다 적으며 pow(0.5, n)이는 단지 상위 추정치 일뿐입니다. 52 글자의 경우 6 개의 가장 낮은 비트가 좋지 않을 가능성은 단지 오로지 (64-52)/64 = 0.19; 예를 들어 10 번 반복 한 후 좋은 숫자를 얻지 못할 가능성은 1e-8입니다.

솔루션은 다음과 같습니다.

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. 마스킹 개선

이전 솔루션은에서 반환 한 63 개의 임의 비트 중 가장 낮은 6 비트 만 사용합니다 rand.Int63(). 랜덤 비트를 얻는 것이 알고리즘에서 가장 느리기 때문에 낭비입니다.

만약 우리가 52 개의 문자를 가지고 있다면, 그것은 6 비트의 문자 인덱스를 의미합니다. 따라서 63 개의 임의의 비트가 63/6 = 10다른 문자 인덱스를 지정할 수 있습니다 . 그 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. 출처

개선 마스킹은 훨씬 우리가 그것을 개선 할 수없는, 아주 좋은 것입니다. 우리는 복잡하지만 그만한 가치가 없었습니다.

이제 개선 할 다른 것을 찾으십시오. 난수의 출처.

함수 crypto/rand를 제공 하는 패키지가 Read(b []byte)있으므로 필요한만큼의 단일 호출로 많은 바이트를 얻을 수 있습니다. 이것은 crypto/rand암호로 안전한 의사 난수 생성기 를 구현하므로 성능면에서 도움이되지 않으므로 속도가 훨씬 느립니다.

math/rand패키지를 고집합시다 . 는 rand.Rand를 사용하여 rand.Source랜덤 비트의 소스로서. 메소드 rand.Source를 지정하는 인터페이스입니다 Int63() int64. 최신 솔루션에서 필요하고 사용한 유일한 방법입니다.

따라서 우리는 rand.Rand(명시 적이 거나 전역 적이며 rand패키지 중 하나를 공유 하는) 실제로 필요하지 않습니다 rand.Source.

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

또한이 마지막 솔루션은 패키지 의 전역 Rand을 사용 하지 않기 때문에 초기화 할 필요 math/rand가 없습니다.rand.Source 제대로 시드 / 초기화).

여기서 주목할 사항이 하나 더 있습니다 : package doc of math/randstates :

기본 소스는 여러 고 루틴이 동시에 사용할 수 있습니다.

따라서 기본 소스는 동시 액세스 / 사용시 안전을 제공해야하지만 이를 제공하지 않기 때문에 기본 소스는에 Source의해 얻을 수있는 것보다 느립니다 (따라서 반환되는 것이 더 빠를 가능성이 높습니다).rand.NewSource()rand.NewSource()Source

7. 활용 strings.Builder

이전의 모든 솔루션은 돌려 string그 내용이 먼저 슬라이스에 내장되어 있습니다 ( []rune창세기 , 그리고 []byte이후의 솔루션), 다음으로 변환 string. 이 최종 변환은 string값을 변경할 수 없기 때문에 슬라이스 내용의 사본을 만들어야하며 , 변환이 사본을 만들지 않으면 문자열 내용이 원래 슬라이스를 통해 수정되지 않을 수도 있습니다. 자세한 내용은 utf8 문자열을 [] 바이트로 변환하는 방법을 참조하십시오 . golang [] 바이트 (문자열)] 바이트 (* 문자열) 대 .

이동 1.10 도입 strings.Builder. 와 비슷한 strings.Builder내용을 만드는 데 사용할 수있는 새로운 유형 입니다. 내부적으로를 사용하여 수행하며 완료되면 해당 값을 사용하여 최종 값을 얻을 수 있습니다.stringbytes.Buffer[]bytestringBuilder.String() 방법을 . 그러나 멋진 점은 위에서 언급 한 사본을 수행하지 않고이 작업을 수행한다는 것입니다. 문자열의 내용을 구성하는 데 사용 된 바이트 슬라이스가 노출되지 않으므로 감히 또는 악의적으로 생성 된 "불변의"문자열을 변경하기 위해 수정할 수 없다는 것이 보장됩니다.

따라서 우리의 다음 아이디어는 슬라이스에 임의의 문자열을 작성하는 것이 아니라 a의 도움으로 strings.Builder완료되면 복사하지 않고도 결과를 얻고 반환 할 수 있습니다. 이것은 속도 측면에서 도움이 될 수 있으며 메모리 사용 및 할당 측면에서 확실히 도움이 될 것입니다.

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

new을 만든 후에 strings.BuidlerBuilder.Grow()메소드를 호출 하여 임의의 문자를 추가 할 때 재 할당을 피하기 위해 충분히 큰 내부 슬라이스를 할당해야합니다.

8. "모방" strings.Builder패키지unsafe

strings.Builder[]byte우리와 마찬가지로 내부에 문자열을 작성합니다 . 따라서 기본적으로 a strings.Builder를 통해 수행하는 것은 약간의 오버 헤드가 있습니다. 우리가 전환 한 유일한 것은 strings.Builder슬라이스의 최종 복사를 피하는 것입니다.

strings.Builderpackage를 사용하여 최종 사본을 피하십시오 unsafe.

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

문제는 우리도 직접 할 수 있다는 것입니다. 그래서 여기서 아이디어는에서 임의의 문자열을 빌드하는 것으로 다시 전환하는 []byte것이지만, 완료되면 string반환 하도록 변환하지 말고 안전하지 않은 변환을 수행하십시오 : string바이트 슬라이스를 가리키는 문자열 데이터를 문자열 데이터로 가져옵니다 .

이것이 가능한 방법입니다 :

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

1.7rand.Read()기능과 Rand.Read()방법을 추가 했습니다 . 우리는 더 나은 성능을 달성하기 위해 한 단계에서 필요한만큼의 바이트를 읽는 데 이것을 사용하도록 유혹해야합니다.

이것에 대한 하나의 작은 "문제"가 있습니다 : 얼마나 많은 바이트가 필요합니까? 출력 문자 수만큼을 말할 수 있습니다. 문자 인덱스가 8 비트 (1 바이트) 미만을 사용하기 때문에 이것이 상위 추정치라고 생각할 것입니다. 그러나이 시점에서 우리는 이미 랜덤 비트를 얻는 것이 "어려운 부분"이기 때문에 더 나 빠지고 있으며 필요한 것 이상을 얻고 있습니다.

또한 모든 문자 색인의 균등 한 분포를 유지하기 위해 사용할 수없는 "가비지"임의 데이터가있을 수 있으므로 일부 데이터를 건너 뛰어 모든 데이터를 처리 할 때 짧게 끝날 수 있습니다. 바이트 슬라이스 "재귀 적으로"더 많은 무작위 바이트를 가져와야합니다. 그리고 지금 우리는 "단일 전화 요청 rand"이점 을 잃고 있습니다 ...

우리는 우리가 획득 한 무작위 데이터의 사용을 "약간"최적화 할 수있었습니다 math.Rand(). 필요한 바이트 수를 추정 할 수 있습니다. 1 문자는 letterIdxBits비트가 필요하고 n문자가 필요하므로 n * letterIdxBits / 8.0바이트 반올림이 필요 합니다. 우리는 무작위 인덱스가 사용 가능하지 않을 확률을 계산할 수 있으므로 (위 참조) "더 많을 것"만으로도 더 많은 것을 요청할 수 있습니다 (그렇지 않으면 프로세스를 반복합니다). 예를 들어 바이트 슬라이스를 "비트 스트림"으로 처리 할 수 ​​있습니다. 여기에는 멋진 타사 라이브러리가 있습니다.github.com/icza/bitio (공개 : 저자입니다).

그러나 벤치 마크 코드는 여전히 우리가 이기지 못하고 있음을 보여줍니다. 왜 그래야만하지?

마지막 질문에 대한 대답은 rand.Read()루프를 사용 Source.Int63()하고 전달 된 슬라이스를 채울 때까지 계속 호출 하기 때문입니다. 정확하게 어떤 RandStringBytesMaskImprSrc()솔루션은하지 않고 중간 버퍼 및 복잡성없이. 그것이 RandStringBytesMaskImprSrc()왕좌에 남아있는 이유 입니다. 예, RandStringBytesMaskImprSrc()비동기 rand.Source와 달리를 사용합니다 rand.Read(). 그러나 추론은 여전히 ​​적용됩니다. 우리 Rand.Read()대신에 사용하면 입증rand.Read() (전자는 동기화되지 ).

II. 기준

이제 다양한 솔루션을 벤치마킹해야합니다.

진실의 순간:

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

룬에서 바이트로 전환하는 것만으로도 즉시 24 %의 성능 향상을 얻을 수 있으며 메모리 요구 사항은 1/3로 감소 .

제거 rand.Intn()하고 rand.Int63()대신 사용하면 20 %가 추가로 발생합니다 증가합니다.

마스킹 (그리고 큰 지수의 경우 반복)은 약간의 속도 저하 (반복 호출로 인해) : -22 % ...

그러나 63 개의 랜덤 비트 (한 번의 rand.Int63()호출 에서 10 개의 인덱스)를 모두 (또는 대부분) 사용하면 시간이 3 배 빨라집니다 .

rand.Source대신 ( 기본이 아닌 새로운)로 정착하면 rand.Rand다시 21 % 증가합니다.

우리가 사용하는 경우 strings.Builder, 우리는 작은 이득 3.5 %를속도 , 그러나 우리는 또한 달성 50 %의 메모리 사용 및 할당에 감소! 멋지다!

마지막으로 우리가 unsafe대신에 패키지를 사용한다면 strings.Builder, 다시 14 %를 얻을 수 있습니다.

초기 용액의 최종 비교 : RandStringBytesMaskImprSrcUnsafe()6.3 배 빠르게 보다 RandStringRunes()사용 육분의 일 메모리 및 거의 절반을 할당 . 임무 완수.


8
공유 rand.Source가 사용 되기 때문에 @RobbieV Yup . 더 나은 해결 방법은을 통과하는 것입니다 rand.Source받는 RandStringBytesMaskImprSrc()기능, 그리고 어떤 잠금이 필요하지 않은 방식 때문에 성능 / 효율은 영향을받지 않습니다. 각 goroutine은 자체적으로 가질 수 있습니다 Source.
icza

113
@icza, 그것은 내가 오랫동안 본 최고의 답변 중 하나입니다!
astropanic

1
@MikeAtlas : defer필요하지 않은 것이 분명 할 때는 사용을 피해야 합니다. 참조 grokbase.com/t/gg/golang-nuts/158zz5p42w/...
ZAN 살쾡이에게

1
팁을위한 @ZanLynx thx; 하지만 defer중 직전 또는 잠금을 호출하면 IMO 후 뮤텍스 잠금을 해제하는 대부분 아주 좋은 생각; 잠금을 해제하는 것을 잊지 말고 치명적이지 않은 공황 중반에도 잠금을 해제해야합니다.
Mike Atlas

1
@RobbieV 기본 공유 소스가 이미 mutex를 구현하는 LockedSource ( golang.org/src/math/rand/rand.go:259 ) 이기 때문에이 코드는 스레드 / 고 루틴 안전합니다 .
adityajones

130

코드를 작성할 수 있습니다. 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
rand.Seed ()를 잊지 마십시오. 그렇지 않으면 처음 시작할 때마다 동일한 문자열을 얻습니다. rand.Seed (time.Now (). UTC (). UnixNano ())
Evan Lin

2
Evan의 추가는 정확하지만 다른 유사한 옵션이 있습니다. rand.Seed(time.Now().Unix())또는rand.Seed(time.Now().UnixNano())
openwonk

7
추측하기 어려운 비밀 (암호, 암호 키 등)의 경우 절대 사용하지 마십시오 math/rand. 사용하는 crypto/rand대신 (Not_A_Golfer의 옵션 1 @처럼).
twotwotwo

1
@EvanLin 이것은 추측 할 수 없습니까? 생성기를 시드해야하는 경우 공격자는 생성하는 시간을 추측하고 생성하는 것과 동일한 출력을 예측할 수 있습니다.
Matej

4
시드로 위의 프로그램을 시도하면 이동 놀이터에서 항상 동일한 결과를 반환합니다. 나는 운동장에서 그것을 시도하고 있었고 얼마 후에 이것을 깨달았습니다. 그렇지 않으면 나를 위해 잘 작동했습니다. 그것은 누군가의 시간을 절약하기를 바랍니다 :)
Gaurav Sinha

18

암호화 적으로 안전한 균일 (비 편향) 문자열을 생성하는 패키지 uniuri를 사용하십시오 .

면책 조항 : 나는 패키지의 저자입니다


1
따로 : dchest 저자는 훌륭한 개발자이며 이와 같이 작고 유용한 여러 패키지를 제작했습니다.
Roshambo

16

두 가지 가능한 옵션 (물론 더있을 수 있음) :

  1. crypto/rand/ dev / urandom에서 임의 바이트 배열 읽기를 지원 하는 패키지를 사용하여 암호화 임의 생성에 적합합니다. http://golang.org/pkg/crypto/rand/#example_Read를 참조하십시오 . 그래도 정상적인 의사 난수 생성보다 느릴 수 있습니다.

  2. 임의의 숫자를 가져 와서 md5 또는 이와 유사한 것을 사용하여 해시하십시오.


4

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.


2

당신이 원하는 경우 암호 보안 (예를 들어, base64로 괜찮) 임의의 숫자, 그리고 정확한 캐릭터 세트가 유연 정확히 임의의 문자의 길이가 원하는 출력 크기에서 필요한 계산할 수 있습니다.

기본 64 텍스트는 기본 256보다 1/3 더 깁니다. (2 ^ 8 vs 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 vs 2 ^ 4; 8 비트 / 4 비트 = 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
}

그러나 문자 집합에 대한 base256 to baseN 인코더가있는 경우 임의의 문자 집합으로 확장 할 수 있습니다. 문자 집합을 나타내는 데 필요한 비트 수로 동일한 크기 계산을 수행 할 수 있습니다. 임의의 문자 집합에 대한 비율 계산은 다음과 같습니다. ratio = 8 / log2(len(charset))).

이 두 가지 솔루션 모두 안전하고 단순하지만 빠르며 암호화 엔트로피 풀을 낭비하지 마십시오.

모든 크기에서 작동하는 놀이터는 다음과 같습니다. https://play.golang.org/p/i61WUVR8_3Z


Go Playground는 항상 동일한 난수를 반환하므로 해당 코드를 실행할 때마다 다른 임의의 문자열이 표시되지 않습니다.
TPPZ

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

왜 n * 2를 생성 []byte합니까?
M. Rostami

1

내 방법은 다음과 같습니다) 원하는대로 수학 랜드 또는 암호 랜드를 사용하십시오.

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

허용되는 문자 풀에 몇 개의 문자를 추가하려는 경우 io.Reader를 통해 임의의 바이트를 제공하는 모든 코드를 작동시킬 수 있습니다. 여기서 우리는을 사용하고 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
}

random % 64필요한가요?
Sung Cho

2
왜냐하면 len(encodeURL) == 64. random % 64완료되지 않은 경우 randomPos> = 64 일 수 있으며 런타임에 범위를 벗어난 패닉을 유발할 수 있습니다.
0xcaff

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

벤치 마크 LandStr16-8 20000000 68.1 ns / op 16 B / op 1 allocs / op

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.