credential_test.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package credential
  2. import (
  3. "context"
  4. "crypto/md5"
  5. "crypto/sha1"
  6. "encoding/hex"
  7. "strings"
  8. "testing"
  9. )
  10. func md5hex(s string) string { sum := md5.Sum([]byte(s)); return hex.EncodeToString(sum[:]) }
  11. func sha1hex(s string) string { sum := sha1.Sum([]byte(s)); return hex.EncodeToString(sum[:]) }
  12. // TestHashArgon2RoundTrip 验证 argon2 哈希往返(新注册/改密/惰性升级入口)。
  13. func TestHashArgon2RoundTrip(t *testing.T) {
  14. enc, err := HashArgon2("X#9f2k")
  15. if err != nil {
  16. t.Fatalf("HashArgon2 失败: %v", err)
  17. }
  18. if !strings.HasPrefix(enc, "argon2id$v=19$") {
  19. t.Errorf("argon2 编码串应前缀 argon2id$v=19$,得 %q", enc)
  20. }
  21. if !VerifyArgon2(enc, "X#9f2k") {
  22. t.Errorf("正确密码应匹配")
  23. }
  24. if VerifyArgon2(enc, "wrong") {
  25. t.Errorf("错误密码应不匹配")
  26. }
  27. }
  28. // TestCheckMD5Salt 验证教务旧算法 md5(password+salt) 路由 + 惰性升级标记。
  29. func TestCheckMD5Salt(t *testing.T) {
  30. const pw, salt = "P@ssw0rd", "aBcDeF"
  31. stored := md5hex(pw + salt)
  32. res, err := Check(context.Background(), pw, stored, salt, HashVersionMD5Salt)
  33. if err != nil {
  34. t.Fatalf("Check 意外错误: %v", err)
  35. }
  36. if !res.Match || !res.NeedsUpgrade {
  37. t.Errorf("md5_salt 命中应 Match=true 且 NeedsUpgrade=true,得 %+v", res)
  38. }
  39. // 错误密码
  40. res2, _ := Check(context.Background(), "nope", stored, salt, HashVersionMD5Salt)
  41. if res2.Match {
  42. t.Errorf("md5 错误密码应不匹配")
  43. }
  44. }
  45. // TestCheckShASalt 验证教学旧算法 sha1(password+salt) 路由。
  46. func TestCheckShASalt(t *testing.T) {
  47. const pw, salt = "teach123", "sAlT12"
  48. stored := sha1hex(pw + salt)
  49. res, err := Check(context.Background(), pw, stored, salt, HashVersionShASalt)
  50. if err != nil {
  51. t.Fatalf("Check 意外错误: %v", err)
  52. }
  53. if !res.Match || !res.NeedsUpgrade {
  54. t.Errorf("sha_salt 命中应 Match=true 且 NeedsUpgrade=true,得 %+v", res)
  55. }
  56. }
  57. // TestCheckArgon2 验证目标算法 argon2 路由(命中不触发升级)。
  58. func TestCheckArgon2(t *testing.T) {
  59. enc, _ := HashArgon2("argon-pw")
  60. res, err := Check(context.Background(), "argon-pw", enc, "", HashVersionArgon2)
  61. if err != nil {
  62. t.Fatalf("Check 意外错误: %v", err)
  63. }
  64. if !res.Match || res.NeedsUpgrade {
  65. t.Errorf("argon2 命中应 Match=true 且 NeedsUpgrade=false(已是终态),得 %+v", res)
  66. }
  67. }
  68. // TestCheckHexCaseFold 验证旧 hex 大写 storedHash 折叠比较(历史脚本大小写差异,数据保真例外)。
  69. func TestCheckHexCaseFold(t *testing.T) {
  70. const pw, salt = "fold123", "zz"
  71. storedUpper := strings.ToUpper(md5hex(pw + salt))
  72. res, err := Check(context.Background(), pw, storedUpper, salt, HashVersionMD5Salt)
  73. if err != nil {
  74. t.Fatalf("Check 意外错误: %v", err)
  75. }
  76. if !res.Match {
  77. t.Errorf("大写 hex storedHash 应折叠命中,得 Match=%v", res.Match)
  78. }
  79. }
  80. // TestCheckUnknownVersion 验证未知 hash_version 拒绝(非密码错误,走告警分支)。
  81. func TestCheckUnknownVersion(t *testing.T) {
  82. res, err := Check(context.Background(), "pw", "abc", "", "weird_version")
  83. if err == nil {
  84. t.Fatalf("未知 version 应报错")
  85. }
  86. if !strings.Contains(err.Error(), "weird_version") {
  87. t.Errorf("错误应含实际 version 值,得 %q", err.Error())
  88. }
  89. if res.Match {
  90. t.Errorf("未知 version 不应 Match")
  91. }
  92. _ = ErrUnknownVersion
  93. }
  94. // TestCheckEmptyHash 验证空密码哈希(账号无凭证,需运维重建)。
  95. func TestCheckEmptyHash(t *testing.T) {
  96. _, err := Check(context.Background(), "pw", "", "salt", HashVersionMD5Salt)
  97. if err == nil {
  98. t.Fatalf("空 storedHash 应报错")
  99. }
  100. if !strings.Contains(err.Error(), "无密码哈希") {
  101. t.Errorf("错误应提示无密码哈希,得 %q", err.Error())
  102. }
  103. }