Files
schemas/hash/hash.go
T

75 lines
2.4 KiB
Go
Raw Normal View History

2023-04-27 07:09:10 +02:00
package hash
import (
"crypto/sha256"
"encoding/base64"
"golang.org/x/crypto/bcrypt"
2023-04-27 07:09:10 +02:00
)
// String creates a SHA256 hash of a string (legacy, for non-sensitive data)
2023-04-27 07:09:10 +02:00
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
}