package hash import ( "crypto/sha256" "encoding/base64" "golang.org/x/crypto/bcrypt" ) // String creates a SHA256 hash of a string (legacy, for non-sensitive data) func String(s string) string { encoded := sha256.New().Sum([]byte(s)) return base64.StdEncoding.EncodeToString(encoded) } // APIKey hashes an API key using bcrypt for secure storage // Cost of 12 provides a good balance between security and performance func APIKey(key string) (string, error) { hash, err := bcrypt.GenerateFromPassword([]byte(key), 12) if err != nil { return "", err } return string(hash), nil } // CompareAPIKey compares a plaintext API key with a hash // Supports both bcrypt (new) and SHA256 (legacy) hashes for backwards compatibility // Returns true if they match, false otherwise // // Migration Strategy: // Old API keys stored with SHA256 will continue to work. To upgrade them to bcrypt: // 1. Keys are automatically upgraded when users re-authenticate (if implemented) // 2. Or, run a one-time migration using MigrateAPIKeyHash when convenient func CompareAPIKey(hashedKey, plainKey string) bool { // Bcrypt hashes start with $2a$, $2b$, or $2y$ // If the hash starts with $2, it's a bcrypt hash if len(hashedKey) > 2 && hashedKey[0] == '$' && hashedKey[1] == '2' { // New bcrypt hash err := bcrypt.CompareHashAndPassword([]byte(hashedKey), []byte(plainKey)) return err == nil } // Legacy SHA256 hash - compare using the old method legacyHash := String(plainKey) return hashedKey == legacyHash } // IsLegacyHash returns true if the hash is a legacy SHA256 hash (not bcrypt) func IsLegacyHash(hashedKey string) bool { return len(hashedKey) <= 2 || hashedKey[0] != '$' || hashedKey[1] != '2' } // MigrateAPIKeyHash can be used to upgrade a legacy SHA256 hash to bcrypt // This is useful for one-time migrations of existing keys // Returns the new bcrypt hash if the key is legacy, otherwise returns the original func MigrateAPIKeyHash(currentHash, plainKey string) (string, bool, error) { // If already bcrypt, no migration needed if !IsLegacyHash(currentHash) { return currentHash, false, nil } // Verify the legacy hash is correct before migrating if !CompareAPIKey(currentHash, plainKey) { return "", false, nil // Invalid key, don't migrate } // Generate new bcrypt hash newHash, err := APIKey(plainKey) if err != nil { return "", false, err } return newHash, true, nil }