credential.go 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. package util
  2. import (
  3. "crypto/rand"
  4. "encoding/hex"
  5. "fmt"
  6. )
  7. func GenerateRandomHex(byteLen int) (string, error) {
  8. b := make([]byte, byteLen)
  9. if _, err := rand.Read(b); err != nil {
  10. return "", fmt.Errorf("generate random bytes failed: %w", err)
  11. }
  12. return hex.EncodeToString(b), nil
  13. }
  14. // generateStrongInitialPassword 为账号生成强密码:大写+小写+数字+少量符号混合,
  15. // 并通过 util.ValidatePassword 断言,确保任何后续合规复核都不会卡住(见审计 L-R10-2)。
  16. // 长度要求 n >= 8;实际生成 n 个字符,混合字符集字面量已刻意去掉易混淆的 I/l/O/0/1。
  17. func GenerateStrongInitialPassword(n int) (string, error) {
  18. if n < 8 {
  19. n = 8
  20. }
  21. const (
  22. upper = "ABCDEFGHJKMNPQRSTUVWXYZ"
  23. lower = "abcdefghjkmnpqrstuvwxyz"
  24. digits = "23456789"
  25. symbols = "!@#$%^&*"
  26. )
  27. alphabet := upper + lower + digits + symbols
  28. // 把每个字符类至少各取 1 个放在随机位置,保证强度检查一定通过;其余位从总字母表随机。
  29. classes := []string{upper, lower, digits, symbols}
  30. pwd := make([]byte, n)
  31. used := 0
  32. for _, cls := range classes {
  33. c, err := randomCharFrom(cls)
  34. if err != nil {
  35. return "", err
  36. }
  37. pwd[used] = c
  38. used++
  39. }
  40. for i := used; i < n; i++ {
  41. c, err := randomCharFrom(alphabet)
  42. if err != nil {
  43. return "", err
  44. }
  45. pwd[i] = c
  46. }
  47. if err := shuffleBytes(pwd); err != nil {
  48. return "", err
  49. }
  50. out := string(pwd)
  51. if msg := ValidatePassword(out); msg != "" {
  52. // 理论上不可达:上面已按字符类强制填充,保留断言避免字符集未来被人修改后静默失效。
  53. return "", fmt.Errorf("generated password failed strength check: %s", msg)
  54. }
  55. return out, nil
  56. }
  57. // randomCharFrom 用 crypto/rand 无偏地从字符集中取一个字节。循环直到命中模长内,避免简单取模偏置。
  58. func randomCharFrom(alphabet string) (byte, error) {
  59. max := len(alphabet)
  60. bucket := 256 - (256 % max)
  61. buf := make([]byte, 1)
  62. for {
  63. if _, err := rand.Read(buf); err != nil {
  64. return 0, err
  65. }
  66. if int(buf[0]) < bucket {
  67. return alphabet[int(buf[0])%max], nil
  68. }
  69. }
  70. }
  71. // shuffleBytes Fisher-Yates 洗牌,随机索引基于 crypto/rand;用于打散 generateStrongInitialPassword
  72. // 里"前 4 个字符恰好是各字符类"的固定前缀,避免格式可预测。
  73. func shuffleBytes(buf []byte) error {
  74. for i := len(buf) - 1; i > 0; i-- {
  75. bucket := byte(i + 1)
  76. b := make([]byte, 1)
  77. if _, err := rand.Read(b); err != nil {
  78. return err
  79. }
  80. j := int(b[0] % bucket)
  81. buf[i], buf[j] = buf[j], buf[i]
  82. }
  83. return nil
  84. }