加密与安全:让你的 Go 程序固若金汤

学习 Go 语言的密码学和安全编程:哈希、加密、签名、TLS 和常见安全实践

加密与安全:让你的 Go 程序固若金汤

安全不是事后补救,而是从一开始就应该考虑的事情。

当你存储用户密码时,你有没有想过:如果数据库泄露了,用户的密码会不会被直接看到?当你传输敏感数据时,你有没有想过:中间人会不会窃听你的通信?当用户登录时,你有没有想过:怎么确保这个请求真的来自他本人?

这些问题都需要密码学来解决。好消息是,Go 的标准库提供了强大的 crypto 系列包,覆盖了从哈希到加密、从签名到 TLS 的各种场景。

今天我们就来学习 Go 的安全编程。

哈希函数

哈希函数把任意长度的数据映射为固定长度的"指纹"。好的哈希函数有以下特点:

  • 确定性:相同输入总是产生相同输出
  • 不可逆:无法从哈希值恢复原始数据
  • 雪崩效应:输入的微小变化导致输出的巨大变化
  • 抗碰撞:很难找到两个不同输入产生相同哈希值

MD5 和 SHA-1(不推荐)

package main

import (
	"crypto/md5"
	"crypto/sha1"
	"fmt"
)

func main() {
	data := []byte("Hello, World!")
	
	// MD5(128位,已不安全)
	md5Hash := md5.Sum(data)
	fmt.Printf("MD5:    %x\n", md5Hash)
	
	// SHA-1(160位,已不安全)
	sha1Hash := sha1.Sum(data)
	fmt.Printf("SHA-1:  %x\n", sha1Hash)
}

⚠️ 重要:MD5 和 SHA-1 已经被证明存在碰撞漏洞,不要用于安全敏感的场景。它们只能用于非安全用途,比如文件校验和。

SHA-256 和 SHA-512(推荐)

package main

import (
	"crypto/sha256"
	"crypto/sha512"
	"fmt"
)

func main() {
	data := []byte("Hello, World!")
	
	// SHA-256(256位)
	hash256 := sha256.Sum256(data)
	fmt.Printf("SHA-256: %x\n", hash256)
	
	// SHA-512(512位)
	hash512 := sha512.Sum512(data)
	fmt.Printf("SHA-512: %x\n", hash512)
}

文件完整性校验

package main

import (
	"crypto/sha256"
	"fmt"
	"io"
	"os"
)

func hashFile(path string) (string, error) {
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()
	
	h := sha256.New()
	if _, err := io.Copy(h, f); err != nil {
		return "", err
	}
	
	return fmt.Sprintf("%x", h.Sum(nil)), nil
}

func main() {
	hash, err := hashFile("example.txt")
	if err != nil {
		fmt.Println("错误:", err)
		return
	}
	fmt.Println("SHA-256:", hash)
}

密码存储:bcrypt

永远不要用 MD5 或 SHA-256 直接存储密码! 因为:

  1. 哈希速度太快,攻击者可以暴力破解
  2. 没有加盐,相同密码的哈希值一样

bcrypt 是专门为密码设计的哈希算法,它内置了盐值和计算成本参数:

package main

import (
	"fmt"
	
	"golang.org/x/crypto/bcrypt"
)

func hashPassword(password string) (string, error) {
	// cost=10 表示 2^10 次迭代(默认值)
	bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	return string(bytes), err
}

func checkPassword(password, hash string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
	return err == nil
}

func main() {
	password := "mySuperSecretPassword123"
	
	// 哈希密码
	hash, err := hashPassword(password)
	if err != nil {
		fmt.Println("哈希失败:", err)
		return
	}
	fmt.Println("哈希值:", hash)
	// $2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
	
	// 验证密码
	fmt.Println("正确密码:", checkPassword(password, hash))      // true
	fmt.Println("错误密码:", checkPassword("wrongPassword", hash)) // false
	
	// 即使同一个密码,每次哈希的结果都不同(因为盐值不同)
	hash2, _ := hashPassword(password)
	fmt.Println("哈希值2:", hash2)  // 和第一次不同!
	fmt.Println("验证仍然通过:", checkPassword(password, hash2))  // true
}

安装:

go get golang.org/x/crypto/bcrypt

对称加密:AES

对称加密使用同一个密钥来加密和解密数据。AES(Advanced Encryption Standard)是目前最广泛使用的对称加密算法。

package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
)

func encrypt(plaintext []byte, key []byte) (string, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", err
	}
	
	// 使用 GCM 模式(认证加密)
	aesGCM, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}
	
	// 生成随机 nonce
	nonce := make([]byte, aesGCM.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return "", err
	}
	
	// 加密(nonce 会附加在密文前面)
	ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil)
	return hex.EncodeToString(ciphertext), nil
}

func decrypt(ciphertextHex string, key []byte) ([]byte, error) {
	ciphertext, err := hex.DecodeString(ciphertextHex)
	if err != nil {
		return nil, err
	}
	
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	
	aesGCM, err := cipher.NewGCM(block)
	if err != nil {
		return nil, err
	}
	
	nonceSize := aesGCM.NonceSize()
	if len(ciphertext) < nonceSize {
		return nil, fmt.Errorf("密文太短")
	}
	
	nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
	return aesGCM.Open(nil, nonce, ciphertext, nil)
}

func main() {
	// 密钥必须是 16、24 或 32 字节(对应 AES-128、AES-192、AES-256)
	key := []byte("my-32-byte-secret-key-1234567890")
	
	plaintext := "这是一条秘密消息"
	
	// 加密
	encrypted, err := encrypt([]byte(plaintext), key)
	if err != nil {
		fmt.Println("加密失败:", err)
		return
	}
	fmt.Println("密文:", encrypted)
	
	// 解密
	decrypted, err := decrypt(encrypted, key)
	if err != nil {
		fmt.Println("解密失败:", err)
		return
	}
	fmt.Println("明文:", string(decrypted))
}

⚠️ 安全提示

  1. 密钥不要硬编码在代码中
  2. 使用 crypto/rand 生成随机密钥,不要用 math/rand
  3. 使用 GCM 模式(认证加密),不要用 ECB 模式

非对称加密:RSA

非对称加密使用一对密钥:公钥(加密)和私钥(解密)。

package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"fmt"
)

func main() {
	// 生成 RSA 密钥对
	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		fmt.Println("生成密钥失败:", err)
		return
	}
	publicKey := &privateKey.PublicKey
	
	// 加密
	plaintext := []byte("这是用 RSA 加密的消息")
	ciphertext, err := rsa.EncryptOAEP(
		sha256.New(),
		rand.Reader,
		publicKey,
		plaintext,
		nil,
	)
	if err != nil {
		fmt.Println("加密失败:", err)
		return
	}
	fmt.Printf("密文: %x...\n", ciphertext[:20])
	
	// 解密
	decrypted, err := rsa.DecryptOAEP(
		sha256.New(),
		rand.Reader,
		privateKey,
		ciphertext,
		nil,
	)
	if err != nil {
		fmt.Println("解密失败:", err)
		return
	}
	fmt.Println("明文:", string(decrypted))
}

数字签名

RSA 还可以用于数字签名——用私钥签名,用公钥验证:

package main

import (
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"fmt"
)

func sign(data []byte, privateKey *rsa.PrivateKey) ([]byte, error) {
	hash := sha256.Sum256(data)
	return rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hash[:])
}

func verify(data []byte, signature []byte, publicKey *rsa.PublicKey) bool {
	hash := sha256.Sum256(data)
	err := rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hash[:], signature)
	return err == nil
}

func main() {
	// 生成密钥对
	privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
	publicKey := &privateKey.PublicKey
	
	data := []byte("这是一份重要的合同")
	
	// 签名
	signature, err := sign(data, privateKey)
	if err != nil {
		fmt.Println("签名失败:", err)
		return
	}
	fmt.Printf("签名: %x...\n", signature[:20])
	
	// 验证
	fmt.Println("签名有效:", verify(data, signature, publicKey))     // true
	
	// 篡改数据后验证
	data[0] = 'X'
	fmt.Println("篡改后:", verify(data, signature, publicKey))      // false
}

HMAC:消息认证码

HMAC(Hash-based Message Authentication Code)用于验证消息的完整性和来源:

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
)

func generateHMAC(message, key []byte) string {
	h := hmac.New(sha256.New, key)
	h.Write(message)
	return hex.EncodeToString(h.Sum(nil))
}

func verifyHMAC(message, key []byte, expectedMAC string) bool {
	actualMAC := generateHMAC(message, key)
	return hmac.Equal([]byte(actualMAC), []byte(expectedMAC))
}

func main() {
	key := []byte("my-secret-key")
	message := []byte("转账 1000 元给张三")
	
	// 生成 HMAC
	mac := generateHMAC(message, key)
	fmt.Println("HMAC:", mac)
	
	// 验证
	fmt.Println("验证:", verifyHMAC(message, key, mac))        // true
	
	// 篡改消息
	message[0] = 'X'
	fmt.Println("篡改后:", verifyHMAC(message, key, mac))      // false
}

TLS/HTTPS

创建 HTTPS 服务器

package main

import (
	"crypto/tls"
	"fmt"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, HTTPS!")
	})
	
	// 配置 TLS
	server := &http.Server{
		Addr:    ":443",
		Handler: mux,
		TLSConfig: &tls.Config{
			MinVersion: tls.VersionTLS12,  // 最低 TLS 1.2
			CipherSuites: []uint16{
				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
			},
		},
	}
	
	// 启动 HTTPS 服务器
	err := server.ListenAndServeTLS("cert.pem", "key.pem")
	if err != nil {
		fmt.Println("启动失败:", err)
	}
}

生成自签名证书

go run $(go env GOROOT)/src/crypto/tls/generate_cert.go --host=localhost

HTTPS 客户端

package main

import (
	"crypto/tls"
	"fmt"
	"io"
	"net/http"
)

func main() {
	// 跳过证书验证(仅开发环境使用!)
	client := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},
	}
	
	resp, err := client.Get("https://localhost:443")
	if err != nil {
		fmt.Println("请求失败:", err)
		return
	}
	defer resp.Body.Close()
	
	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

安全最佳实践

1. 使用 crypto/rand 生成随机数

// ❌ 不要用 math/rand(可预测)
import "math/rand"
token := rand.Int()

// ✅ 用 crypto/rand(安全的随机数)
import "crypto/rand"
token := make([]byte, 32)
rand.Read(token)

2. 防范 SQL 注入

// ❌ 字符串拼接(有 SQL 注入风险)
query := fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", name)

// ✅ 使用参数化查询
db.Query("SELECT * FROM users WHERE name = ?", name)

3. 防范 XSS 攻击

// ❌ 直接输出用户输入
fmt.Fprintf(w, "<h1>%s</h1>", userInput)

// ✅ 使用 html/template 自动转义
import "html/template"
tmpl, _ := template.New("page").Parse("<h1>{{.}}</h1>")
tmpl.Execute(w, userInput)

4. 防范 CSRF 攻击

// 使用 CSRF token
func generateCSRFToken() string {
	token := make([]byte, 32)
	rand.Read(token)
	return hex.EncodeToString(token)
}

// 在表单中包含 CSRF token
// <input type="hidden" name="csrf_token" value="{{.CSRFToken}}">

// 验证 CSRF token
func validateCSRF(r *http.Request) bool {
	sessionToken := getSessionCSRFToken(r)
	formToken := r.FormValue("csrf_token")
	return hmac.Equal([]byte(sessionToken), []byte(formToken))
}

5. 设置安全的 HTTP 头

func securityHeaders(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("X-Content-Type-Options", "nosniff")
		w.Header().Set("X-Frame-Options", "DENY")
		w.Header().Set("X-XSS-Protection", "1; mode=block")
		w.Header().Set("Strict-Transport-Security", "max-age=31536000")
		w.Header().Set("Content-Security-Policy", "default-src 'self'")
		
		next.ServeHTTP(w, r)
	})
}

实战:JWT 认证

JSON Web Token(JWT)是一种常用的认证方式:

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strings"
	"time"
)

var jwtSecret = []byte("your-256-bit-secret")

type Claims struct {
	UserID int    `json:"user_id"`
	Name   string `json:"name"`
	Exp    int64  `json:"exp"`
}

func generateJWT(userID int, name string) (string, error) {
	header := base64.RawURLEncoding.EncodeToString(
		[]byte(`{"alg":"HS256","typ":"JWT"}`),
	)
	
	claims := Claims{
		UserID: userID,
		Name:   name,
		Exp:    time.Now().Add(24 * time.Hour).Unix(),
	}
	claimsJSON, _ := json.Marshal(claims)
	payload := base64.RawURLEncoding.EncodeToString(claimsJSON)
	
	// 签名
	mac := hmac.New(sha256.New, jwtSecret)
	mac.Write([]byte(header + "." + payload))
	signature := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
	
	return header + "." + payload + "." + signature, nil
}

func verifyJWT(tokenString string) (*Claims, error) {
	parts := strings.Split(tokenString, ".")
	if len(parts) != 3 {
		return nil, fmt.Errorf("无效的 token 格式")
	}
	
	// 验证签名
	mac := hmac.New(sha256.New, jwtSecret)
	mac.Write([]byte(parts[0] + "." + parts[1]))
	expectedSig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
	
	if !hmac.Equal([]byte(parts[2]), []byte(expectedSig)) {
		return nil, fmt.Errorf("签名验证失败")
	}
	
	// 解析 claims
	claimsJSON, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil {
		return nil, err
	}
	
	var claims Claims
	if err := json.Unmarshal(claimsJSON, &claims); err != nil {
		return nil, err
	}
	
	// 检查过期
	if time.Now().Unix() > claims.Exp {
		return nil, fmt.Errorf("token 已过期")
	}
	
	return &claims, nil
}

func main() {
	// 生成 token
	token, err := generateJWT(42, "张三")
	if err != nil {
		fmt.Println("生成失败:", err)
		return
	}
	fmt.Println("JWT:", token)
	
	// 验证 token
	claims, err := verifyJWT(token)
	if err != nil {
		fmt.Println("验证失败:", err)
		return
	}
	fmt.Printf("用户: ID=%d, Name=%s\n", claims.UserID, claims.Name)
}

小结

今天我们全面学习了 Go 的安全编程:

  1. 哈希函数:SHA-256/512、bcrypt 密码哈希
  2. 对称加密:AES-GCM 认证加密
  3. 非对称加密:RSA 加密和数字签名
  4. HMAC:消息认证码
  5. TLS/HTTPS:安全的网络通信
  6. 安全实践:随机数、SQL 注入、XSS、CSRF 防范
  7. JWT:基于 Token 的认证

安全是一个持续的话题。永远不要自己发明加密算法,使用经过验证的标准库和第三方库。

练习时间

  1. 密码管理器:实现一个密码哈希和验证的工具
  2. 文件加密:实现一个文件加密/解密工具(AES-GCM)
  3. API 签名验证:用 HMAC 实现 API 请求签名
  4. 安全中间件:实现包含 CSRF 保护和安全头的 HTTP 中间件

我们下篇见!👋


参考资料:

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页