MD5的基本实现
1. 标准库调用
Go语言通过crypto/md5
包提供MD5算法的实现。核心步骤包括:
- 创建哈希对象:使用
md5.New()
生成一个实现了hash.Hash
接口的实例。 - 写入数据:通过
Write()
方法或io.WriteString()
将数据写入哈希对象。 - 生成哈希值:调用
Sum(nil)
获取哈希结果,并通过encoding/hex
包转换为十六进制字符串。
示例代码:
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"io"
)
func main() {
h := md5.New()
io.WriteString(h, "Hello, world!")
hash := h.Sum(nil)
fmt.Println(hex.EncodeToString(hash)) // 输出:6cd3556deb0da54bca060b4c39479839
}
此方法支持分块写入数据,适用于大文件处理
2. 简化方法
对于一次性计算,可直接使用md5.Sum()
函数:
data := []byte("Hello, world!")
hash := md5.Sum(data)
fmt.Printf("%x\n", hash) // 输出:6cd3556deb0da54bca060b4c39479839
此方法直接返回固定长度的哈希数组(16字节),需手动转换为字符串
AES对称加解密
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"io"
)
func main() {
// 示例密钥(AES-256需要32字节密钥)
key := []byte("this-is-a-32-byte-key-1234567890")
// 原始数据
plaintext := []byte("今天是2025年4月12日,星期六,农历三月十五")
fmt.Printf("原始数据: %s\n", plaintext)
fmt.Printf("密钥: %s\n", key)
// 加密
ciphertext, err := AESEncrypt(key, plaintext)
if err != nil {
panic(err)
}
eb64 := base64.StdEncoding.EncodeToString(ciphertext)
fmt.Printf("加密结果(Base64): %s\n", eb64)
db64, _ := base64.StdEncoding.DecodeString(eb64)
// 解密
decrypted, err := AESDecrypt(key, db64)
if err != nil {
panic(err)
}
fmt.Printf("解密结果: %s\n", decrypted)
}
// AESEncrypt 使用AES-256 CBC模式加密数据
func AESEncrypt(key, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// PKCS7填充
plaintext = PKCS7Pad(plaintext, aes.BlockSize)
// 生成随机IV
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
// 加密
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
return ciphertext, nil
}
// AESDecrypt 使用AES-256 CBC模式解密数据
func AESDecrypt(key, ciphertext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(ciphertext) < aes.BlockSize {
return nil, errors.New("ciphertext too short")
}
// 提取IV
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
// 解密
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(ciphertext, ciphertext)
// 去除PKCS7填充
return PKCS7Unpad(ciphertext)
}
// PKCS7Pad 实现PKCS7填充
func PKCS7Pad(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padText...)
}
// PKCS7Unpad 去除PKCS7填充
func PKCS7Unpad(data []byte) ([]byte, error) {
length := len(data)
if length == 0 {
return nil, errors.New("empty data")
}
padding := int(data[length-1])
if padding < 1 || padding > aes.BlockSize {
return nil, errors.New("invalid padding")
}
if length < padding {
return nil, errors.New("data shorter than padding")
}
// 检查填充是否有效
for i := 0; i < padding; i++ {
if data[length-padding+i] != byte(padding) {
return nil, errors.New("invalid padding")
}
}
return data[:length-padding], nil
}
关键点说明
- 密钥长度:
- AES-256需要32字节(256位)的密钥
- 示例中使用了简单的字符串密钥,实际应用中应从安全源获取密钥
- 初始化向量(IV):
- 每次加密都生成随机IV,确保相同明文加密结果不同
- IV不需要保密,但必须不可预测
- 填充方案:
- 实现了PKCS7填充,确保数据长度是块大小的倍数
- 解密后自动去除填充
- 安全注意事项:
- 使用
crypto/rand
生成随机数 - 解密时严格验证填充有效性
- 实际应用中应考虑添加消息认证码(MAC)防止篡改
- 使用
Go语言JWT实现
JWT(JSON Web Token)是一种基于JSON的开放标准(RFC 7519),用于在网络应用间安全地传输信息。其核心是通过签名和声明机制实现无状态身份验证,广泛应用于分布式系统、微服务架构和跨域认证场景
JWT结构组成
Header:算法类型和token类型
{
"alg": "HS256",
"typ": "JWT"
}
Payload(载荷)
- 作用:携带声明(Claims),包含用户身份信息或其他业务数据。
- 声明分类:
- Registered Claims(标准声明):如
iss
(签发者)、exp
(过期时间)、sub
(主题) - Public Claims(自定义声明):需避免与标准声明冲突,如用户角色
role
。 - Private Claims(私有声明):业务自定义字段,如用户ID
user_id
。
- Registered Claims(标准声明):如
-
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022, "exp": 1744876800 // 2025年4月12日0时的Unix时间戳 }
Registered Claims 标准声明列表
声明名称 全称 类型 必填 描述 iss
Issuer String 否 签发者标识,表示生成JWT的实体(如认证服务器地址) sub
Subject String 否 主题标识,表示JWT的核心主体(如用户ID或唯一标识) aud
Audience String 否 接收方标识,指定JWT的预期接收者(如客户端应用ID) exp
Expiration Time Number 否 过期时间,Unix时间戳,表示JWT失效时间(必须大于 iat
)nbf
Not Before Number 否 生效时间,Unix时间戳,表示JWT在此时间前不可用 iat
Issued At Number 否 签发时间,Unix时间戳,记录JWT生成时间 jti
JWT ID String 否 唯一标识符,用于防止重放攻击(建议全局唯一)
Signature:对前两部分的签名
- 作用:验证JWT的完整性和真实性,防止篡改。
- 生成方式:对
Header
和Payload
的Base64编码字符串拼接后,使用密钥和算法生成签名。
完整实现示例
安装依赖
go get github.com/golang-jwt/jwt/v5
代码实现
package main
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
// 自定义Claims结构体
type CustomClaims struct {
UserID string `json:"user_id"`
Username string `json:"username"`
jwt.RegisteredClaims // 内置标准声明(exp, iat, nbf等)
}
var secretKey = []byte("your-256-bit-secret-2025-04-12")
func main() {
// 生成Token
tokenString, err := GenerateToken("u10001", "张三")
if err != nil {
panic(err)
}
fmt.Printf("生成的Token: %s\n", tokenString)
// 验证Token
claims, err := ParseToken(tokenString)
if err != nil {
panic(err)
}
fmt.Printf("解析结果: %+v\n", claims)
}
// GenerateToken 生成JWT Token
func GenerateToken(userID, username string) (string, error) {
expiration := time.Now().Add(24 * time.Hour)
claims := CustomClaims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expiration),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(secretKey)
}
// ParseToken 解析验证JWT Token
func ParseToken(tokenString string) (*CustomClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return secretKey, nil
})
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
return claims, nil
}
return nil, err
}