Cryptographic Hashing and AES Encryption
Combines authentication-safe hashing with symmetric encryption so credentials and payloads are protected end to end.
main.go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io"
"log"
"golang.org/x/crypto/bcrypt"
)
// HashPassword securely hashes a password using bcrypt (industry standard)
func HashPassword(password string) (string, error) {
// Generate a bcrypt hash with cost factor 12 (balance of security/speed)
hashBytes, err := bcrypt.GenerateFromPassword([]byte(password), 12)
if err != nil {
return "", fmt.Errorf("failed to hash password: %w", err)
}
return string(hashBytes), nil
}
// VerifyPassword checks if a password matches a bcrypt hash
func VerifyPassword(password, hash string) bool {
// Compare password with hash (constant time comparison to prevent timing attacks)
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
// GenerateRandomSalt creates a cryptographically secure random salt
func GenerateRandomSalt(size int) ([]byte, error) {
salt := make([]byte, size)
_, err := rand.Read(salt)
if err != nil {
return nil, fmt.Errorf("failed to generate salt: %w", err)
}
return salt, nil
}
// SHA256HashWithSalt creates a SHA-256 hash of data with a random salt
func SHA256HashWithSalt(data []byte) (hash string, salt string, err error) {
// Generate 16-byte salt (128 bits)
saltBytes, err := GenerateRandomSalt(16)
if err != nil {
return "", "", err
}
// Combine data and salt
combined := append(data, saltBytes...)
// Compute SHA-256 hash
hashBytes := sha256.Sum256(combined)
// Encode hash and salt to base64 for easy storage/transmission
return base64.StdEncoding.EncodeToString(hashBytes[:]),
base64.StdEncoding.EncodeToString(saltBytes),
nil
}
// VerifySHA256HashWithSalt verifies data against a hash and salt
func VerifySHA256HashWithSalt(data []byte, hash, salt string) (bool, error) {
// Decode salt from base64
saltBytes, err := base64.StdEncoding.DecodeString(salt)
if err != nil {
return false, fmt.Errorf("failed to decode salt: %w", err)
}
// Combine data and salt
combined := append(data, saltBytes...)
// Compute hash
hashBytes := sha256.Sum256(combined)
computedHash := base64.StdEncoding.EncodeToString(hashBytes[:])
// Compare hashes (constant time comparison would be better for production)
return computedHash == hash, nil
}
// GenerateAESKey creates a cryptographically secure 256-bit AES key
func GenerateAESKey() ([]byte, error) {
key := make([]byte, 32) // 32 bytes = 256 bits
_, err := rand.Read(key)
if err != nil {
return nil, fmt.Errorf("failed to generate AES key: %w", err)
}
return key, nil
}
// AESEncrypt encrypts data using AES-GCM (authenticated encryption)
func AESEncrypt(key, plaintext []byte) (string, error) {
// Create AES cipher block
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("failed to create AES cipher: %w", err)
}
// Create GCM mode (authenticated encryption)
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("failed to create GCM mode: %w", err)
}
// Generate nonce (12 bytes recommended for GCM)
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", fmt.Errorf("failed to generate nonce: %w", err)
}
// Encrypt data (GCM automatically adds authentication tag)
ciphertext := gcm.Seal(nil, nonce, plaintext, nil)
// Combine nonce and ciphertext (nonce is not secret, just needs to be unique)
fullCiphertext := append(nonce, ciphertext...)
// Encode to base64 for easy storage/transmission
return base64.StdEncoding.EncodeToString(fullCiphertext), nil
}
// AESDecrypt decrypts data encrypted with AESEncrypt
func AESDecrypt(key []byte, ciphertextBase64 string) ([]byte, error) {
// Decode from base64
fullCiphertext, err := base64.StdEncoding.DecodeString(ciphertextBase64)
if err != nil {
return nil, fmt.Errorf("failed to decode ciphertext: %w", err)
}
// Create AES cipher block
block, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("failed to create AES cipher: %w", err)
}
// Create GCM mode
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("failed to create GCM mode: %w", err)
}
// Extract nonce from the beginning of the ciphertext
nonceSize := gcm.NonceSize()
if len(fullCiphertext) < nonceSize {
return nil, errors.New("ciphertext too short")
}
nonce := fullCiphertext[:nonceSize]
ciphertext := fullCiphertext[nonceSize:]
// Decrypt and verify authentication tag
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf("decryption failed (invalid key or tampered data): %w", err)
}
return plaintext, nil
}
func main() {
// Example 1: Bcrypt password hashing
password := "my_secure_password123"
hash, err := HashPassword(password)
if err != nil {
log.Fatalf("Failed to hash password: %v", err)
}
fmt.Println("Bcrypt Hash:", hash)
// Verify password
isValid := VerifyPassword(password, hash)
fmt.Println("Password valid:", isValid)
isValid = VerifyPassword("wrong_password", hash)
fmt.Println("Wrong password valid:", isValid)
fmt.Println("---")
// Example 2: SHA-256 with salt
data := []byte("sensitive_data_123")
hash256, salt, err := SHA256HashWithSalt(data)
if err != nil {
log.Fatalf("Failed to hash data: %v", err)
}
fmt.Println("SHA-256 Hash:", hash256)
fmt.Println("Salt:", salt)
// Verify hash
isValidHash, err := VerifySHA256HashWithSalt(data, hash256, salt)
if err != nil {
log.Fatalf("Failed to verify hash: %v", err)
}
fmt.Println("Hash valid:", isValidHash)
fmt.Println("---")
// Example 3: AES-GCM encryption/decryption
key, err := GenerateAESKey()
if err != nil {
log.Fatalf("Failed to generate AES key: %v", err)
}
fmt.Println("AES Key (base64):", base64.StdEncoding.EncodeToString(key))
secretData := []byte("my super secret message")
encrypted, err := AESEncrypt(key, secretData)
if err != nil {
log.Fatalf("Failed to encrypt data: %v", err)
}
fmt.Println("Encrypted Data:", encrypted)
decrypted, err := AESDecrypt(key, encrypted)
if err != nil {
log.Fatalf("Failed to decrypt data: %v", err)
}
fmt.Println("Decrypted Data:", string(decrypted))
// Test tampered data (should fail)
tamperedEncrypted := encrypted + "tampered"
_, err = AESDecrypt(key, tamperedEncrypted)
fmt.Println("Decrypt tampered data error:", err != nil)
}How It Works
Combines authentication-safe hashing with symmetric encryption so credentials and payloads are protected end to end.
Hashes passwords with bcrypt using a configurable cost; builds a SHA-256 hash using a random salt; generates a random 32-byte key and 12-byte nonce for AES-GCM, then seals and opens ciphertext while concatenating nonce and ciphertext for transport; sample code prints digests and decrypts back to plaintext.
Key Concepts
- 1bcrypt handles password storage with adaptive work factors.
- 2crypto/rand supplies unpredictable salt, keys, and nonces.
- 3AES-GCM provides authenticated encryption to detect tampering.
- 4Helper encodes and decodes binary blobs as hex or base64 for storage.
When to Use This Pattern
- Storing user credentials securely in web services.
- Encrypting configuration secrets or tokens at rest.
- Protecting data exchanged between services without TLS termination.
- Teaching safe defaults for Go cryptography primitives.
Best Practices
- Never reuse nonces with the same AES-GCM key.
- Keep keys and salts outside source control such as environment variables or KMS.
- Tune bcrypt cost to balance security and login latency.
- Validate decode errors and fail closed rather than returning plaintext.
Go Version1.18+
Difficultyadvanced
Production ReadyYes
Lines of Code219