Cryptography in Go
Introduction
Cryptography is essential for protecting sensitive data. Go’s crypto package provides robust implementations of modern cryptographic algorithms. This guide covers encryption, hashing, digital signatures, and secure key management.
Core Concepts
Types of Cryptography
- Symmetric Encryption: Same key for encryption and decryption (AES)
- Asymmetric Encryption: Different keys for encryption and decryption (RSA)
- Hashing: One-way function producing fixed-size output (SHA-256)
- Digital Signatures: Prove authenticity and non-repudiation
Good: Hashing
SHA-256 Hashing
package main
import (
"crypto/sha256"
"fmt"
"io"
)
// โ
GOOD: Hash sensitive data
func HashPassword(password string) string {
hash := sha256.Sum256([]byte(password))
return fmt.Sprintf("%x", hash)
}
// โ
GOOD: Hash file
func HashFile(filename string) (string, error) {
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close()
hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return "", err
}
return fmt.Sprintf("%x", hash.Sum(nil)), nil
}
func main() {
password := "mypassword"
hashed := HashPassword(password)
fmt.Printf("Hash: %s\n", hashed)
}
Good: Symmetric Encryption
AES Encryption
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
)
// โ
GOOD: AES-256 encryption
func EncryptData(plaintext string, key []byte) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
return fmt.Sprintf("%x", ciphertext), nil
}
// โ
GOOD: AES decryption
func DecryptData(ciphertext string, key []byte) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
data := make([]byte, hex.DecodedLen(len(ciphertext)))
if _, err := hex.Decode(data, []byte(ciphertext)); err != nil {
return "", err
}
nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}
func main() {
key := make([]byte, 32) // 256-bit key
rand.Read(key)
plaintext := "sensitive data"
encrypted, _ := EncryptData(plaintext, key)
decrypted, _ := DecryptData(encrypted, key)
fmt.Printf("Original: %s\n", plaintext)
fmt.Printf("Decrypted: %s\n", decrypted)
}
Good: Asymmetric Encryption
RSA Encryption
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"fmt"
)
// โ
GOOD: Generate RSA key pair
func GenerateRSAKeyPair() (*rsa.PrivateKey, error) {
return rsa.GenerateKey(rand.Reader, 2048)
}
// โ
GOOD: RSA encryption
func EncryptWithRSA(plaintext string, publicKey *rsa.PublicKey) (string, error) {
ciphertext, err := rsa.EncryptOAEP(
sha256.New(),
rand.Reader,
publicKey,
[]byte(plaintext),
nil,
)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", ciphertext), nil
}
// โ
GOOD: RSA decryption
func DecryptWithRSA(ciphertext string, privateKey *rsa.PrivateKey) (string, error) {
data := make([]byte, hex.DecodedLen(len(ciphertext)))
hex.Decode(data, []byte(ciphertext))
plaintext, err := rsa.DecryptOAEP(
sha256.New(),
rand.Reader,
privateKey,
data,
nil,
)
if err != nil {
return "", err
}
return string(plaintext), nil
}
func main() {
privateKey, _ := GenerateRSAKeyPair()
publicKey := &privateKey.PublicKey
plaintext := "secret message"
encrypted, _ := EncryptWithRSA(plaintext, publicKey)
decrypted, _ := DecryptWithRSA(encrypted, privateKey)
fmt.Printf("Original: %s\n", plaintext)
fmt.Printf("Decrypted: %s\n", decrypted)
}
Good: Digital Signatures
RSA Signatures
package main
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"fmt"
)
// โ
GOOD: Sign data with RSA
func SignData(data string, privateKey *rsa.PrivateKey) (string, error) {
hash := sha256.Sum256([]byte(data))
signature, err := rsa.SignPKCS1v15(
rand.Reader,
privateKey,
crypto.SHA256,
hash[:],
)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", signature), nil
}
// โ
GOOD: Verify signature
func VerifySignature(data, signature string, publicKey *rsa.PublicKey) error {
hash := sha256.Sum256([]byte(data))
sig := make([]byte, hex.DecodedLen(len(signature)))
hex.Decode(sig, []byte(signature))
return rsa.VerifyPKCS1v15(
publicKey,
crypto.SHA256,
hash[:],
sig,
)
}
func main() {
privateKey, _ := GenerateRSAKeyPair()
publicKey := &privateKey.PublicKey
data := "important message"
signature, _ := SignData(data, privateKey)
if err := VerifySignature(data, signature, publicKey); err != nil {
fmt.Println("Signature verification failed")
} else {
fmt.Println("Signature verified successfully")
}
}
Best Practices
1. Use Strong Keys
// โ
GOOD: 256-bit key for AES
key := make([]byte, 32)
rand.Read(key)
// โ
GOOD: 2048-bit RSA key
privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
// โ BAD: Weak key
key := []byte("password123")
2. Use Authenticated Encryption
// โ
GOOD: GCM mode provides authentication
gcm, _ := cipher.NewGCM(block)
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
// โ BAD: ECB mode (no authentication)
cipher.NewCBCEncrypter(block, iv)
3. Secure Key Storage
// โ
GOOD: Use environment variables or key management services
key := os.Getenv("ENCRYPTION_KEY")
// โ BAD: Hardcoded keys
key := "hardcoded-secret-key"
Resources
- Go Crypto Package: https://pkg.go.dev/crypto
- OWASP Cryptography: https://owasp.org/www-community/attacks/Cryptanalysis
- Modern Cryptography: https://cryptography.io/
Summary
Go’s crypto package provides secure implementations of modern cryptographic algorithms. Always use authenticated encryption, strong keys, and secure key management practices.
Comments