How to generate a random string in Go without losing your mind?
\n\nHave you ever encountered a situation where you need to quickly create a unique identifier, a temporary password, or an API token? If you write in Go, sooner or later you will face the question: "How to generate a random string of fixed length?". This question has garnered thousands of views on Stack Overflow, and for good reason — a seemingly simple task hides several pitfalls.
\n\nIn this article, we will break down generating a random string in Go from simple solutions to production-ready approaches. You will learn why math/rand can be dangerous, how to use crypto/rand for security, and how to write elegant code without unnecessary dependencies.
Why shouldn't you use math/rand for passwords?
\n\nLet's start with the most popular, but not always correct, solution. Many beginners write something like:
\n\npackage main\n\nimport (\n "math/rand"\n "time"\n)\n\nconst letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"\n\nfunc RandString(n int) string {\n b := make([]byte, n)\n for i := range b {\n b[i] = letters[rand.Intn(len(letters))]\n }\n return string(b)\n}\n\nfunc main() {\n rand.Seed(time.Now().UnixNano())\n println(RandString(10))\n}\n\nThis code works, but it has a serious drawback: math/rand uses a deterministic algorithm. If an attacker discovers the seed (or the generation time), they can reproduce the entire sequence. This is fine for test data or file names, but for secret tokens and passwords — absolutely not.
Using crypto/rand: secure random string generation in Go
\n\nFor a production environment, the correct choice is the crypto/rand package. It uses the operating system's cryptographically secure random number generator. Here is what generating a random string in Go with crypto/rand looks like:
package main\n\nimport (\n "crypto/rand"\n "math/big"\n)\n\nconst charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()"\n\nfunc SecureRandomString(length int) (string, error) {\n result := make([]byte, length)\n for i := range result {\n num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))\n if err != nil {\n return "", err\n }\n result[i] = charset[num.Int64()]\n }\n return string(result), nil\n}\n\nfunc main() {\n str, err := SecureRandomString(16)\n if err != nil {\n panic(err)\n }\n println(str)\n}\n\nNote: we use rand.Int with rand.Reader — this guarantees that each character is chosen randomly and unpredictably. For a random string in Go, this is the gold standard.
Performance optimization: how to speed up generation
\n\nA loop calling rand.Int for each character can be slow when generating hundreds of thousands of strings. The solution is to generate all random bytes at once, and then map them to our alphabet. This trick is often discussed on Stack Overflow in the context of Golang random string:
package main\n\nimport (\n "crypto/rand"\n "encoding/binary"\n)\n\nconst charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"\n\nfunc FastRandomString(length int) string {\n result := make([]byte, length)\n // Generate random 64-bit numbers\n buf := make([]byte, 8*length)\n if _, err := rand.Read(buf); err != nil {\n panic(err)\n }\n for i := 0; i < length; i++ {\n // Take 8 bytes for each character\n val := binary.LittleEndian.Uint64(buf[i*8 : (i+1)*8])\n // Avoid bias using modulo\n result[i] = charset[val%uint64(len(charset))]\n }\n return string(result)\n}\n\nfu