Files
FlowStack/backend/internal/crypto/crypto.go
T
Logan.Li 04d1342b27 feat: FlowStack 发布系统首个可运行版本 (gin + React)
- Go gin 后端:流水线/节点/连线/运行/日志/GitOps/插件 REST API,
  SQLite(GORM) 自动建表 + seed,模拟执行引擎 + SSE 实时日志推送,
  前端产物 go:embed 嵌入单二进制,一个端口同时服务 API 与页面
- React 19 前端:画布式流水线编辑器(React Flow + Tekton 任务库)、
  GitOps 部署、批量发布、发布记录等 14 个模块页面,
  tRPC 全部替换为 REST(TanStack Query)
- 首次启动生成随机密码管理员账号(日志 + data/initial-admin-password.txt)
- run.sh 一键构建启动(默认端口 9527,支持 start/stop/restart/build/dev)
2026-10-27 02:44:10 +08:00

52 lines
1.5 KiB
Go

// Package crypto 提供口令哈希原语(argon2id,与 opsx-loki 同参数)。
package crypto
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"math/big"
"golang.org/x/crypto/argon2"
)
// HashPassword 生成 argon2id 口令哈希(salt+hash base64 编码)
func HashPassword(password string) (string, error) {
salt := make([]byte, 16)
if _, err := rand.Read(salt); err != nil {
return "", err
}
hash := argon2.IDKey([]byte(password), salt, 3, 64*1024, 4, 32)
combined := append(salt, hash...)
return base64.StdEncoding.EncodeToString(combined), nil
}
// VerifyPassword 常数时间比对口令哈希
func VerifyPassword(password, hashStr string) bool {
combined, err := base64.StdEncoding.DecodeString(hashStr)
if err != nil || len(combined) < 16 {
return false
}
salt := combined[:16]
hash := combined[16:]
newHash := argon2.IDKey([]byte(password), salt, 3, 64*1024, 4, 32)
return subtle.ConstantTimeCompare(hash, newHash) == 1
}
// passwordAlphabet 去掉易混淆字符(0O1lI)的密码字母表
const passwordAlphabet = "23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ"
// RandomPassword 生成 n 位随机密码(crypto/rand,无易混淆字符)
func RandomPassword(n int) (string, error) {
max := big.NewInt(int64(len(passwordAlphabet)))
buf := make([]byte, n)
for i := range buf {
idx, err := rand.Int(rand.Reader, max)
if err != nil {
return "", err
}
buf[i] = passwordAlphabet[idx.Int64()]
}
return string(buf), nil
}