| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 |
- 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
- }
|