How to Generate and Validate API Keys in Go

Generate API keys using `crypto/rand` to create cryptographically secure random bytes, then encode them as Base64 or Hex strings for storage.

Generate API keys using crypto/rand to create cryptographically secure random bytes, then encode them as Base64 or Hex strings for storage. Validation requires comparing the incoming key against a stored hash (using bcrypt or argon2) rather than storing the raw key, ensuring that even if your database is compromised, the actual keys remain protected.

Here is a practical implementation using crypto/rand for generation and bcrypt for secure validation:

package main

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"log"

	"golang.org/x/crypto/bcrypt"
)

// GenerateAPIKey creates a 32-byte random key and returns it as a Base64 string.
func GenerateAPIKey() (string, error) {
	bytes := make([]byte, 32)
	if _, err := rand.Read(bytes); err != nil {
		return "", err
	}
	// Use Base64 for a compact, URL-safe representation
	return base64.StdEncoding.EncodeToString(bytes), nil
}

// HashAPIKey hashes the raw key for secure storage.
func HashAPIKey(key string) ([]byte, error) {
	// bcrypt handles salting internally
	return bcrypt.GenerateFromPassword([]byte(key), bcrypt.DefaultCost)
}

// ValidateAPIKey checks the raw key against the stored hash.
func ValidateAPIKey(key string, storedHash []byte) bool {
	err := bcrypt.CompareHashAndPassword(storedHash, []byte(key))
	return err == nil
}

func main() {
	// 1. Generate a new key
	rawKey, err := GenerateAPIKey()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Generated Key: %s\n", rawKey)

	// 2. Hash it for storage (simulate DB save)
	hashedKey, err := HashAPIKey(rawKey)
	if err != nil {
		log.Fatal(err)
	}

	// 3. Validate a request (simulate incoming API call)
	isValid := ValidateAPIKey(rawKey, hashedKey)
	fmt.Printf("Validation Result: %v\n", isValid)

	// 4. Validate a wrong key
	wrongKey := "invalid-key-string"
	isValidWrong := ValidateAPIKey(wrongKey, hashedKey)
	fmt.Printf("Wrong Key Validation: %v\n", isValidWrong)
}

In production, never store the raw API key in your database. Always store the bcrypt hash. When a client sends a key in the Authorization header, retrieve the corresponding hash from your database and run ValidateAPIKey. This approach prevents rainbow table attacks and ensures constant-time comparison to mitigate timing attacks. If you need to revoke keys, simply delete the hash record or add a revoked_at timestamp to your database schema.