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