Jak wygenerować losowy ciąg o stałej długości w Go?


300

Chcę tylko losowy ciąg znaków (wielkie lub małe), bez liczb, w Go. Jaki jest najszybszy i najprostszy sposób to zrobić?


2
@VinceEmigh: Oto meta temat omawiający podstawowe pytania. meta.stackoverflow.com/q/274645/395461 Osobiście uważam, że podstawowe pytania są w porządku, jeśli są dobrze napisane i dotyczą tematu. Spójrz na poniższe odpowiedzi, ilustrują one wiele rzeczy, które byłyby przydatne dla kogoś nowego. W przypadku pętli, rzutowania, make () itp.
Shannon Matthews,

2
@Shannon „ To pytanie nie pokazuje żadnego wysiłku badawczego ” (pierwsza bardzo pozytywna odpowiedź w twoim linku) - o to mi chodziło. Nie wykazuje wysiłku badawczego. Bez żadnego wysiłku (próba, a nawet stwierdzenie, że wyglądał online, czego oczywiście nie zrobił). Mimo, że byłby użyteczny dla kogoś nowego , ta strona nie koncentruje się na nauczaniu nowych ludzi. Koncentruje się na rozwiązywaniu konkretnych problemów / pytań programistycznych, a nie na samouczkach / przewodnikach. Chociaż można go wykorzystać w tym drugim przypadku, nie jest to celem, a zatem to pytanie powinno zostać zamknięte. Zamiast tego jego spoonfed /:
Vince Emigh

9
@VinceEmigh Zadałem to pytanie rok temu. Szukałem w Internecie losowych ciągów i czytałem dokumenty. Ale to nie było pomocne. Jeśli nie napisałem w pytaniu, to nie znaczy, że nie badałem.
Anish Shah,

Odpowiedzi:


809

Rozwiązanie Paula zapewnia proste , ogólne rozwiązanie.

Pytanie dotyczy „najszybszego i najprostszego sposobu” . Zajmijmy się także najszybszą częścią. Nasz ostateczny, najszybszy kod dojdziemy w sposób iteracyjny. Benchmarking każdej iteracji można znaleźć na końcu odpowiedzi.

Wszystkie rozwiązania i kod porównawczy można znaleźć na Go Playground . Kod na placu zabaw to plik testowy, a nie plik wykonywalny. Musisz zapisać go w pliku o nazwie XX_test.goi uruchomić go

go test -bench . -benchmem

Przedmowa :

Najszybsze rozwiązanie nie jest rozwiązaniem, jeśli potrzebujesz tylko losowego ciągu. Do tego rozwiązanie Paula jest idealne. Dzieje się tak, jeśli wydajność ma znaczenie. Chociaż pierwsze 2 kroki ( Bajty i pozostałe ) mogą być akceptowalnym kompromisem: poprawiają wydajność o około 50% (patrz dokładne liczby w sekcji II. Benchmark ) i nie zwiększają znacząco złożoności.

Powiedziawszy to, nawet jeśli nie potrzebujesz najszybszego rozwiązania, przeczytanie tej odpowiedzi może być ryzykowne i edukacyjne.

I. Ulepszenia

1. Geneza (runy)

Przypominamy, że oryginalne ogólne rozwiązanie, które ulepszamy, to:

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

Jeśli znaki do wyboru i złożenia losowego ciągu zawierają tylko wielkie i małe litery alfabetu angielskiego, możemy pracować z bajtami tylko dlatego, że litery alfabetu angielskiego odwzorowują bajty 1 do 1 w kodowaniu UTF-8 (które to jak Go przechowuje ciągi).

Więc zamiast:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

możemy użyć:

var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

Lub nawet lepiej:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

Jest to już duża poprawa: moglibyśmy ją osiągnąć const(są stringstałe, ale nie ma stałych plasterek ). Jako dodatkowy zysk, wyrażenie len(letters)będzie również const! (Wyrażenie len(s)jest stałe, jeśli sjest ciągiem ciągłym.)

A jakim kosztem? W ogóle nic. strings może być indeksowany, co indeksuje jego bajty, idealnie, dokładnie to, czego chcemy.

Nasz następny cel wygląda następująco:

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

Wcześniejsze rozwiązania otrzymują losowy numer do wyznaczenia losowej litery, dzwoniąc do rand.Intn()których delegatów, do Rand.Intn()których delegatów Rand.Int31n().

Jest to znacznie wolniejsze w porównaniu do tego, rand.Int63()który wytwarza losową liczbę z 63 losowymi bitami.

Więc możemy po prostu zadzwonić rand.Int63()i wykorzystać resztę po podzieleniu przez 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)
}

Działa to i jest znacznie szybsze, wadą jest to, że prawdopodobieństwo wszystkich liter nie będzie dokładnie takie samo (zakładając, że rand.Int63()wszystkie 63-bitowe liczby mają jednakowe prawdopodobieństwo). Chociaż zniekształcenie jest bardzo małe, ponieważ liczba liter 52jest znacznie mniejsza niż 1<<63 - 1, więc w praktyce jest to całkowicie w porządku.

Aby ułatwić zrozumienie: powiedzmy, że chcesz losową liczbę z zakresu 0..5. Przy użyciu 3 losowych bitów uzyskałoby to liczby 0..1z podwójnym prawdopodobieństwem niż z zakresu 2..5. Przy użyciu 5 losowych bitów liczby w zakresie 0..1wystąpiłyby z 6/32prawdopodobieństwem, a liczby w zakresie 2..5z 5/32prawdopodobieństwem, które jest teraz bliższe pożądanemu. Zwiększenie liczby bitów czyni to mniej znaczącym, gdy osiągając 63 bity, jest to nieistotne.

4. Maskowanie

Opierając się na poprzednim rozwiązaniu, możemy utrzymać równy rozkład liter, używając tylko tyle najniższych bitów liczby losowej, ile jest wymaganych do przedstawienia liczby liter. Tak na przykład, jeśli mamy 52 liter, wymaga 6 bitów do jej reprezentowania: 52 = 110100b. Wykorzystamy więc tylko 6 najniższych bitów z liczby zwróconej przez rand.Int63(). Aby utrzymać równy rozkład liter, „akceptujemy” liczbę tylko wtedy, gdy mieści się w zakresie 0..len(letterBytes)-1. Jeśli najniższe bity są większe, odrzucamy je i sprawdzamy nową liczbę losową.

Zauważ, że szansa, że ​​najniższe bity będą większe lub równe, len(letterBytes)jest mniejsza niż 0.5ogólnie ( 0.25średnio), co oznacza, że ​​nawet gdyby tak było, powtórzenie tego „rzadkiego” przypadku zmniejsza szansę na znalezienie dobrego numer. Po npowtórzeniu szansa, że ​​nie będziemy mieć dobrego indeksu, jest znacznie mniejsza niż pow(0.5, n), a to tylko górna ocena. W przypadku 52 liter szansa, że ​​6 najniższych bitów nie jest dobra, jest tylko (64-52)/64 = 0.19; co oznacza na przykład, że szanse na brak dobrej liczby po 10 powtórzeniach są 1e-8.

Oto rozwiązanie:

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. Ulepszone maskowanie

Poprzednie rozwiązanie wykorzystuje tylko najniższe 6 bitów z 63 losowych bitów zwróconych przez rand.Int63(). Jest to strata, ponieważ uzyskiwanie losowych bitów jest najwolniejszą częścią naszego algorytmu.

Jeśli mamy 52 litery, oznacza to, że 6 bitów koduje indeks liter. Tak więc 63 losowe bity mogą oznaczać 63/6 = 10różne indeksy literowe. Użyjmy tych wszystkich 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. Źródło

Maskowanie Lepsza jest całkiem dobry, niewiele możemy poprawić na nim. Moglibyśmy, ale nie było to skomplikowane.

Teraz znajdźmy coś jeszcze do poprawy. Źródło liczb losowych.

Istnieje crypto/randpakiet, który zapewnia Read(b []byte)funkcję, dzięki czemu możemy użyć tej liczby, aby uzyskać tyle bajtów za pomocą jednego wywołania, ile potrzebujemy. Nie pomogłoby to pod względem wydajności, ponieważ crypto/randimplementuje bezpieczny kryptograficznie generator liczb pseudolosowych, więc jest znacznie wolniejszy.

Trzymajmy się math/randpaczki. rand.RandWykorzystuje rand.Sourcejako źródło przypadkowych bitów. rand.Sourceto interfejs, który określa Int63() int64metodę: dokładnie i jedyną rzeczą, której potrzebowaliśmy i wykorzystaliśmy w naszym najnowszym rozwiązaniu.

Więc tak naprawdę nie potrzebujemy rand.Rand(jawnego lub globalnego, wspólnego jednego z randpakietu), a rand.Sourcejest dla nas całkowicie wystarczające:

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

Należy również pamiętać, że to ostatnie rozwiązanie nie wymaga inicjalizacji (nasienie) globalna Randz math/randpakietu jako że nie jest używany (i nasze rand.Sourcejest prawidłowo zainicjowany / zaszczepia).

Jeszcze jedna rzecz do zapamiętania: pakiet dokumentów math/randstanów:

Domyślne źródło jest bezpieczne do równoczesnego użycia przez wiele goroutine.

Tak więc domyślne źródło jest wolniejsze niż to, Sourcektóre można uzyskać rand.NewSource(), ponieważ domyślne źródło musi zapewniać bezpieczeństwo przy równoczesnym dostępie / użytkowaniu, podczas gdy rand.NewSource()nie oferuje tego (a zatem Sourcezwracane przez niego prawdopodobieństwo jest szybsze).

7. Wykorzystanie strings.Builder

Wszystkie poprzednie rozwiązania zwracają, stringktórego treść jest najpierw wbudowana w plasterek ( []runew Genesis i []bytekolejnych rozwiązaniach), a następnie przekonwertowana na string. Ta końcowa konwersja musi wykonać kopię zawartości wycinka, ponieważ stringwartości są niezmienne, a jeśli konwersja nie utworzy kopii, nie można zagwarantować, że zawartość ciągu nie zostanie zmodyfikowana za pomocą oryginalnego wycinka. Aby uzyskać szczegółowe informacje, zobacz Jak przekonwertować ciąg utf8 na bajt []? i golang: [] bajt (ciąg) vs [] bajt (* ciąg) .

Wprowadzono wersję 1.10 strings.Builder. strings.Buildernowy typ, którego możemy użyć do zbudowania treści stringpodobnej do bytes.Buffer. Robi to wewnętrznie za pomocą []byte, a kiedy skończymy, możemy uzyskać końcową stringwartość za pomocą jej Builder.String()metody. Ale fajne jest to, że robi to bez wykonywania kopii, o której mówiliśmy powyżej. Odważy się to zrobić, ponieważ wycinek bajtu użyty do zbudowania zawartości ciągu nie jest odsłonięty, więc jest zagwarantowane, że nikt nie będzie mógł go przypadkowo lub złośliwie zmodyfikować, aby zmienić wytworzony ciąg „niezmienny”.

Więc naszym następnym pomysłem jest, aby nie budować losowego ciągu w wycinku, ale za pomocą a strings.Builder, więc kiedy skończymy, możemy uzyskać i zwrócić wynik bez konieczności wykonywania jego kopii. Może to pomóc pod względem prędkości, a na pewno pomoże w zakresie wykorzystania pamięci i przydziałów.

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

Zauważ, że po utworzeniu nowego strings.Buidlerwywołaliśmy jego Builder.Grow()metodę, upewniając się, że alokuje on wystarczająco duży wewnętrzny wycinek (aby uniknąć przesunięć przy dodawaniu losowych liter).

8. „Naśladowanie” strings.Builderz pakietemunsafe

strings.Builderbuduje ciąg w wewnętrznej []byte, tak samo jak my sami. Zasadniczo więc robienie tego za pomocą a strings.Builderma pewien narzut, jedyne, na co przeszliśmy, strings.Builderto unikanie ostatecznego kopiowania wycinka.

strings.Builderunika końcowej kopii za pomocą pakietu unsafe:

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

Chodzi o to, że możemy to również zrobić sami. Pomysł polega na tym, aby powrócić do budowania losowego ciągu znaków w []byte, ale kiedy skończymy, nie przekształcaj go stringna powrót, ale wykonaj niebezpieczną konwersję: uzyskaj stringktóry wskazuje na nasz kawałek bajtu jako dane ciągu .

Oto jak można to zrobić:

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

Go 1.7 dodaje się rand.Read()funkcję i Rand.Read()metody. Powinniśmy mieć pokusę, aby użyć ich do odczytania tyle bajtów, ile potrzebujemy w jednym kroku, aby osiągnąć lepszą wydajność.

Jest z tym jeden „problem”: ile bajtów potrzebujemy? Można powiedzieć: tyle ile liter wyjściowych. Uważamy, że jest to górna ocena, ponieważ indeks literowy używa mniej niż 8 bitów (1 bajt). Ale w tym momencie już idziemy gorzej (ponieważ zdobywanie losowych bitów jest „trudną częścią”) i otrzymujemy więcej niż potrzeba.

Zauważ też, że aby utrzymać równy rozkład wszystkich indeksów literowych, mogą istnieć jakieś „śmieciowe” losowe dane, których nie będziemy w stanie wykorzystać, więc skończyłoby się to pomijaniem niektórych danych, a tym samym skończyłoby się to krótko, gdy przejdziemy przez wszystkie kawałek bajtu. Musielibyśmy uzyskać więcej losowych bajtów „rekurencyjnie”. A teraz tracimy nawet randprzewagę „pojedynczego połączenia do pakietu” ...

Możemy „nieco” zoptymalizować wykorzystanie losowych danych, które pozyskujemy math.Rand(). Możemy oszacować, ile bajtów (bitów) będziemy potrzebować. Jedna litera wymaga letterIdxBitsbitów i potrzebujemy nliter, więc potrzebujemy n * letterIdxBits / 8.0zaokrąglania bajtów w górę. Możemy obliczyć prawdopodobieństwo, że losowy indeks nie będzie użyteczny (patrz wyżej), więc możemy poprosić o więcej, które „bardziej prawdopodobne” będą wystarczające (jeśli okaże się, że tak nie jest, powtarzamy ten proces). Możemy przetworzyć wycinek bajtu na przykład jako „strumień bitów”, dla którego mamy niezłą bibliotekę stron trzecich: github.com/icza/bitio(ujawnienie: jestem autorem).

Ale kod testu porównawczego wciąż pokazuje, że nie wygrywamy. Dlaczego tak jest

Odpowiedź na ostatnie pytanie brzmi, ponieważ rand.Read()używa pętli i dzwoni Source.Int63()do momentu wypełnienia przekazanego wycinka. Dokładnie to, co RandStringBytesMaskImprSrc()robi rozwiązanie, bez pośredniego bufora i bez dodatkowej złożoności. Dlatego RandStringBytesMaskImprSrc()pozostaje na tronie. Tak, RandStringBytesMaskImprSrc()w rand.Sourceprzeciwieństwie do niezsynchronizowanego rand.Read(). Ale rozumowanie wciąż obowiązuje; i co zostanie udowodnione, jeśli użyjemy Rand.Read()zamiast rand.Read()(ten pierwszy jest również niezsynchronizowany).

II. Reper

W porządku, czas na analizę porównawczą różnych rozwiązań.

Moment prawdy:

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

Po prostu zmieniając runy na bajty, natychmiast uzyskujemy 24% wzrost wydajności, a zapotrzebowanie na pamięć spada do jednej trzeciej .

Pozbycie się rand.Intn()i użycie rand.Int63()zamiast tego daje kolejne 20% doładowania.

Maskowanie (i powtarzanie w przypadku dużych indeksów) nieco spowalnia (z powodu wywołań powtórzeń): -22% ...

Ale kiedy wykorzystamy wszystkie (lub większość) z 63 losowych bitów (10 wskaźników z jednego rand.Int63()połączenia): przyspieszy to duży czas: 3 razy .

Jeśli zdecydujemy się na (niewykonanie, nowe) rand.Sourcezamiast rand.Rand, ponownie zyskujemy 21%.

Jeśli korzystamy strings.Builder, możemy zyskać niewielką 3,5% w prędkości , ale również osiągnąć 50% redukcję zużycia pamięci i alokacji! To miłe!

Wreszcie, jeśli ośmielimy się użyć pakietu unsafezamiast strings.Builder, ponownie zyskamy niezłe 14% .

Porównanie ostatecznego rozwiązania do pierwotnego: RandStringBytesMaskImprSrcUnsafe()jest 6,3 razy szybsze niż RandStringRunes(), wykorzystuje jedną szóstą pamięć i połowę mniej alokacji . Misja zakończona sukcesem.


8
@RobbieV Tak, ponieważ używany rand.Sourcejest współużytkowany . Lepszym obejściem byłoby przekazanie rand.Sourcedo RandStringBytesMaskImprSrc()funkcji, w ten sposób nie jest wymagane blokowanie, a zatem nie ma wpływu na wydajność / wydajność. Każdy goroutine może mieć swój własny Source.
icza

113
@icza, to jedna z najlepszych odpowiedzi, jakie widziałem od dawna na SO!
astropaniczny

1
@MikeAtlas: Należy unikać używania, defergdy jest oczywiste, że nie jest to potrzebne. Zobacz grokbase.com/t/gg/golang-nuts/158zz5p42w/…
Zan Lynx

1
@ZanLynx thx za wskazówkę; chociaż deferIMO do odblokowania bezpośrednio przed lub po wywołaniu zamka jest w większości bardzo dobrym pomysłem; gwarantujesz, że zarówno nie zapomnisz odblokować, ale także odblokować nawet w nieśmiercionośnej panicznej środkowej funkcji.
Mike Atlas,

1
@RobbieV wygląda na to, że ten kod jest bezpieczny dla wątków / goroutine, ponieważ podstawowe udostępnione źródło jest już LockedSource, które implementuje mutex ( golang.org/src/math/rand/rand.go:259 ).
adityajones,

130

Możesz po prostu napisać dla niego kod. Ten kod może być nieco prostszy, jeśli chcesz polegać na tym, że wszystkie litery są pojedynczymi bajtami, gdy są zakodowane w 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
Nie zapomnij o rand.Seed (), w przeciwnym razie otrzymasz ten sam ciąg przy pierwszym uruchomieniu ... rand.Seed (time.Now (). UTC (). UnixNano ())
Evan Lin

2
Dodanie Evana jest poprawne, jednak istnieją inne podobne opcje: rand.Seed(time.Now().Unix())lubrand.Seed(time.Now().UnixNano())
openwonk

7
W przypadku trudnego do odgadnięcia sekretu - hasła, klucza kryptograficznego itp. - nigdy nie używaj math/rand; crypto/randzamiast tego użyj (jak opcja 1 Not_A_Golfer 1).
twotwotwo

1
@EvanLin Czy to nie zgadnie? Jeśli będę musiał uruchomić generator, atakujący może odgadnąć czas, w którym go uruchamiam i przewidzieć to samo, co generuję.
Matej

4
Zauważ, że jeśli wypróbujesz powyższy program z ziarnem, na placu zabaw go zwróci cały czas ten sam wynik. Próbowałem tego na placu zabaw i po jakimś czasie zdałem sobie z tego sprawę. Dla mnie to działało dobrze. Mam nadzieję, że oszczędzi to komuś czas :)
Gaurav Sinha,

18

Użyj pakietu uniuri , który generuje kryptograficznie bezpieczne jednolite (bezstronne) ciągi.

Oświadczenie: Jestem autorem pakietu


1
Poza tym: autor, dchest, jest doskonałym programistą i stworzył wiele małych, przydatnych pakietów takich jak ten.
Roshambo,

16

Dwie możliwe opcje (może być oczywiście więcej):

  1. Możesz użyć crypto/randpakietu, który obsługuje odczytywanie tablic bajtów losowych (z / dev / urandom) i jest ukierunkowany na losowe generowanie kryptograficzne. patrz http://golang.org/pkg/crypto/rand/#example_Read . Może to być jednak wolniejsze niż normalne generowanie liczb pseudolosowych.

  2. Weź losową liczbę i hasz ją za pomocą md5 lub czegoś takiego.


4

Po icza'scudownie wyjaśnionym rozwiązaniu, oto jego modyfikacja, która używa crypto/randzamiast 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
}

Jeśli potrzebujesz bardziej ogólnego rozwiązania, które pozwala przekazać fragment bajtu znaków w celu utworzenia ciągu znaków, możesz spróbować użyć tego:

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

Jeśli chcesz przekazać własne źródło losowości, zmodyfikowanie powyższego w celu zaakceptowania io.Readerzamiast użycia byłoby banalne crypto/rand.


2

Jeśli chcesz kryptograficznie zabezpieczonych liczb losowych, a dokładny zestaw znaków jest elastyczny (powiedzmy, base64 jest w porządku), możesz dokładnie obliczyć wymaganą długość losowych znaków z pożądanego rozmiaru wyjściowego.

Podstawowy tekst 64 jest o 1/3 dłuższy niż podstawowy 256. (2 ^ 8 vs 2 ^ 6; stosunek 8 bitów / 6 bitów = 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
}

Uwaga: możesz również użyć RawStdEncoding, jeśli wolisz znaki + i / zamiast - i _

Jeśli chcesz hex, podstawa 16 jest 2x dłuższa niż podstawa 256. (2 ^ 8 vs 2 ^ 4; stosunek 8 bitów / 4 bity = 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
}

Możesz jednak rozszerzyć to na dowolny dowolny zestaw znaków, jeśli masz koder base256 na baseN dla swojego zestawu znaków. Możesz wykonać takie same obliczenia rozmiaru, ile bitów jest potrzebnych do przedstawienia zestawu znaków. Obliczenie współczynnika dla dowolnego dowolnego zestawu znaków wynosi:) ratio = 8 / log2(len(charset)).

Chociaż oba te rozwiązania są bezpieczne, proste, powinny być szybkie i nie marnuj puli entropii kryptograficznej.

Oto plac zabaw pokazujący, że działa dla każdego rozmiaru. https://play.golang.org/p/i61WUVR8_3Z


warto wspomnieć, że Go Playground zawsze zwraca tę samą liczbę losową, więc nie zobaczysz w niej różnych losowych ciągów przy różnych wykonaniach tego kodu
TPPZ

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

Dlaczego generuje n * 2 []byte?
M. Rostami,

1

Oto moja droga) Użyj matematyki rand lub crypto rand, jak chcesz.

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

Jeśli chcesz dodać kilka znaków do puli dozwolonych znaków, możesz sprawić, że kod będzie działał z dowolną liczbą losowych bajtów za pośrednictwem czytnika io.Reader. Tutaj korzystamy 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
}

dlaczego jest to random % 64konieczne
Sung Cho

2
Ponieważ len(encodeURL) == 64. Jeśli random % 64nie zostało to zrobione, randomPosmoże być> = 64 i spowodować panikę poza zakresem w czasie wykonywania.
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))
}

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.