feat: 引入签名认证、加密工具包及大量goctl代码生成模板,并更新API、Admin和Node服务逻辑。

This commit is contained in:
2026-02-27 08:49:37 -08:00
parent 9dacd85a89
commit e1896f677c
166 changed files with 2913 additions and 489 deletions
+85
View File
@@ -0,0 +1,85 @@
package cryptox
import (
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"time"
)
// generateKey: SHA256(secret)[:32]
func generateKey(secret string) []byte {
h := sha256.Sum256([]byte(secret))
return h[:32]
}
// generateIv: SHA256(hex(md5(nonce)) + secret)[:16]
func generateIv(nonce, secret string) []byte {
md5h := md5.Sum([]byte(nonce))
md5hex := hex.EncodeToString(md5h[:])
sha := sha256.Sum256([]byte(md5hex + secret))
return sha[:16]
}
// Encrypt 加密明文,返回 base64密文 和 noncehex(UnixNano)
func Encrypt(plaintext []byte, secret string) (dataB64 string, nonce string, err error) {
nonce = fmt.Sprintf("%x", time.Now().UnixNano())
key := generateKey(secret)
iv := generateIv(nonce, secret)
block, err := aes.NewCipher(key)
if err != nil {
return
}
padded := pkcs7Pad(plaintext, aes.BlockSize)
mode := cipher.NewCBCEncrypter(block, iv)
dst := make([]byte, len(padded))
mode.CryptBlocks(dst, padded)
dataB64 = base64.StdEncoding.EncodeToString(dst)
return
}
// Decrypt 解密,nonce 用于派生 IV
func Decrypt(dataB64, secret, nonce string) ([]byte, error) {
ciphertext, err := base64.StdEncoding.DecodeString(dataB64)
if err != nil {
return nil, err
}
key := generateKey(secret)
iv := generateIv(nonce, secret)
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(ciphertext)%aes.BlockSize != 0 {
return nil, fmt.Errorf("ciphertext length not multiple of block size")
}
mode := cipher.NewCBCDecrypter(block, iv)
dst := make([]byte, len(ciphertext))
mode.CryptBlocks(dst, ciphertext)
return pkcs7Unpad(dst)
}
func pkcs7Pad(data []byte, blockSize int) []byte {
pad := blockSize - len(data)%blockSize
padding := make([]byte, pad)
for i := range padding {
padding[i] = byte(pad)
}
return append(data, padding...)
}
func pkcs7Unpad(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, fmt.Errorf("empty data")
}
pad := int(data[len(data)-1])
if pad == 0 || pad > aes.BlockSize {
return nil, fmt.Errorf("invalid padding size: %d", pad)
}
return data[:len(data)-pad], nil
}
+34
View File
@@ -0,0 +1,34 @@
package cryptox
import (
"testing"
)
func TestEncryptDecryptRoundtrip(t *testing.T) {
secret := "test-secret-key"
plain := []byte(`{"user_id":1,"action":"login"}`)
dataB64, nonce, err := Encrypt(plain, secret)
if err != nil {
t.Fatal(err)
}
got, err := Decrypt(dataB64, secret, nonce)
if err != nil {
t.Fatal(err)
}
if string(got) != string(plain) {
t.Fatalf("roundtrip mismatch: got %s", got)
}
}
func TestDecryptWrongNonce(t *testing.T) {
secret := "test-secret-key"
plain := []byte(`{"foo":"bar"}`)
dataB64, _, err := Encrypt(plain, secret)
if err != nil {
t.Fatal(err)
}
_, err = Decrypt(dataB64, secret, "wrongnonce")
if err == nil {
t.Fatal("expected error with wrong nonce")
}
}
+15
View File
@@ -0,0 +1,15 @@
package cryptox
import "golang.org/x/crypto/bcrypt"
// CheckPasswordHash 检查密码和 Hash 是否匹配
func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
// GeneratePasswordHash 生成密码的 Hash
func GeneratePasswordHash(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}