package main import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "errors" "fmt" "os" ) // tokenCodec 使用 AES-256-GCM 把文件相对路径加密成不可读、不可篡改的分享 token。 type tokenCodec struct { gcm cipher.AEAD } func newTokenCodec(key []byte) (*tokenCodec, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } gcm, err := cipher.NewGCM(block) if err != nil { return nil, err } return &tokenCodec{gcm: gcm}, nil } // Seal 加密明文(相对路径),返回 base64url(无填充)字符串。 // 每次使用随机 nonce,因此同一明文的 token 每次不同,但均可被 Open 解密。 func (c *tokenCodec) Seal(plaintext string) string { nonce := make([]byte, c.gcm.NonceSize()) if _, err := rand.Read(nonce); err != nil { panic(err) // crypto/rand 失败属于系统级异常 } ciphertext := c.gcm.Seal(nonce, nonce, []byte(plaintext), nil) return base64.RawURLEncoding.EncodeToString(ciphertext) } // Open 解密并校验 token。token 被篡改或非本服务签发时返回错误。 func (c *tokenCodec) Open(token string) (string, error) { raw, err := base64.RawURLEncoding.DecodeString(token) if err != nil { return "", errors.New("token 编码非法") } ns := c.gcm.NonceSize() if len(raw) < ns { return "", errors.New("token 长度非法") } nonce, ciphertext := raw[:ns], raw[ns:] plaintext, err := c.gcm.Open(nil, nonce, ciphertext, nil) if err != nil { return "", errors.New("token 校验失败") } return string(plaintext), nil } // loadOrCreateSecret 读取 32 字节密钥;不存在则随机生成并持久化(base64 编码)。 // 密钥文件权限尽量收紧为 0600(Windows 上仅能尽力而为)。 func loadOrCreateSecret(path string) ([]byte, error) { if data, err := os.ReadFile(path); err == nil { key := make([]byte, 32) n, err := base64.StdEncoding.Decode(key, data) if err == nil && n == 32 { return key, nil } // 兼容直接存放的原始 32 字节 if len(data) == 32 { return data, nil } return nil, fmt.Errorf("密钥文件格式无效: %s", path) } else if !os.IsNotExist(err) { return nil, err } key := make([]byte, 32) if _, err := rand.Read(key); err != nil { return nil, err } encoded := []byte(base64.StdEncoding.EncodeToString(key)) if err := os.WriteFile(path, encoded, 0o600); err != nil { return nil, err } return key, nil }