pwquality.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package pwquality
  15. import (
  16. "strconv"
  17. "strings"
  18. "unicode"
  19. "yunion.io/x/pkg/errors"
  20. )
  21. // ErrPasswordTooWeak 表示密码强度不符合要求的统一错误
  22. var ErrPasswordTooWeak = errors.Error("password too weak")
  23. // Config 存储 pwquality 配置
  24. type Config struct {
  25. Minlen int // 最小长度
  26. Dcredit int // 数字字符信用值(负数表示至少需要多少个字符,正数表示每个字符可减少的长度要求)
  27. Ucredit int // 大写字母信用值
  28. Lcredit int // 小写字母信用值
  29. Ocredit int // 特殊字符信用值
  30. Minclass int // 最小字符类数量(数字、大写、小写、特殊)
  31. Maxrepeat int // 最大重复字符数(0 表示不限制)
  32. Maxclassrepeat int // 最大同类字符重复数(0 表示不限制)
  33. Maxsequence int // 最大连续字符序列长度(0 表示不限制)
  34. // Enforcing 是否强制执行密码策略(1=强制执行,0=仅警告,默认1)
  35. // 注意:此参数在 libpwquality 1.2.0+ 版本中支持,较老的系统可能不支持
  36. // 如果系统不支持,配置文件中不会出现此参数,将使用默认值 1(强制执行)
  37. Enforcing int
  38. // EnforceForRoot 是否对 root 用户强制执行密码策略(1=强制执行,0=不强制,默认0)
  39. // 注意:此参数在 libpwquality 1.2.0+ 版本中支持,较老的系统可能不支持
  40. // 在配置文件中,可能以两种形式出现:
  41. // 1. enforce_for_root = 1(key=value 形式)
  42. // 2. enforce_for_root(独立标志形式,无等号,表示启用)
  43. // 如果系统不支持,配置文件中不会出现此参数,将使用默认值 0(不对 root 强制执行)
  44. EnforceForRoot int
  45. Usercheck int // 是否检查密码中包含用户名(1=检查,0=不检查,默认0)
  46. // 以下配置项在 chroot 环境中可能不适用,暂不实现
  47. // Gecoscheck int // 是否检查密码中包含用户的 GECOS 信息
  48. // Dictcheck int // 是否检查密码是否包含字典中的单词(需要字典文件)
  49. // Dictpath string // 字典文件路径
  50. }
  51. // HasAnyPolicy 检查配置是否有任何非默认的密码策略设置
  52. // 用于判断配置是否有效(即是否包含任何密码强度要求)
  53. func (c *Config) HasAnyPolicy() bool {
  54. if c == nil {
  55. return false
  56. }
  57. return c.Minlen > 0 || c.Dcredit != 0 || c.Ucredit != 0 ||
  58. c.Lcredit != 0 || c.Ocredit != 0 || c.Minclass > 0 ||
  59. c.Maxrepeat > 0 || c.Maxclassrepeat > 0 || c.Maxsequence > 0
  60. }
  61. // IsEnforcing 检查密码策略是否强制执行
  62. // 如果 enforcing=0,密码策略不会强制执行(只是警告)
  63. func (c *Config) IsEnforcing() bool {
  64. if c == nil {
  65. return true // 默认强制执行
  66. }
  67. // enforcing=1 表示强制执行,enforcing=0 表示仅警告
  68. // 默认值为 1(强制执行)
  69. return c.Enforcing != 0
  70. }
  71. // IsEnforcingForRoot 检查是否对 root 用户强制执行密码策略
  72. func (c *Config) IsEnforcingForRoot() bool {
  73. if c == nil {
  74. return false // 默认不对 root 强制执行
  75. }
  76. // enforce_for_root=1 表示对 root 强制执行,enforce_for_root=0 表示不强制
  77. return c.EnforceForRoot == 1
  78. }
  79. // ParseConfig 解析 /etc/security/pwquality.conf 配置文件内容
  80. //
  81. // 兼容性说明:
  82. // - enforcing 和 enforce_for_root 参数在 libpwquality 1.2.0+ 版本中支持
  83. // - 较老的系统(如 RHEL 6 之前)可能不支持这些参数
  84. // - enforce_for_root 可能以两种形式出现:
  85. // 1. enforce_for_root = 1(key=value 形式)
  86. // 2. enforce_for_root(独立标志形式,无等号,表示启用)
  87. //
  88. // - 如果配置文件中不存在这些参数,将使用默认值:
  89. // - Enforcing: 1(默认强制执行)
  90. // - EnforceForRoot: 0(默认不对 root 强制执行)
  91. func ParseConfig(content []byte) *Config {
  92. config := &Config{
  93. Minlen: 0, // 默认值
  94. Dcredit: 0, // 默认值
  95. Ucredit: 0, // 默认值
  96. Lcredit: 0, // 默认值
  97. Ocredit: 0, // 默认值
  98. Minclass: 0, // 默认值
  99. Maxrepeat: 0, // 默认值(0 表示不限制)
  100. Maxclassrepeat: 0, // 默认值(0 表示不限制)
  101. Maxsequence: 0, // 默认值(0 表示不限制)
  102. Enforcing: 1, // 默认值(1 表示强制执行)
  103. EnforceForRoot: 0, // 默认值(0 表示不对 root 强制执行)
  104. Usercheck: 0, // 默认值(0 表示不检查用户名)
  105. }
  106. lines := strings.Split(string(content), "\n")
  107. for _, line := range lines {
  108. line = strings.TrimSpace(line)
  109. // 跳过注释和空行
  110. if len(line) == 0 || strings.HasPrefix(line, "#") {
  111. continue
  112. }
  113. // 检查是否是 key = value 格式
  114. if strings.Contains(line, "=") {
  115. // 解析 key = value 格式
  116. parts := strings.SplitN(line, "=", 2)
  117. if len(parts) != 2 {
  118. continue
  119. }
  120. key := strings.TrimSpace(parts[0])
  121. value := strings.TrimSpace(parts[1])
  122. switch key {
  123. case "minlen":
  124. if v, err := strconv.Atoi(value); err == nil {
  125. config.Minlen = v
  126. }
  127. case "dcredit":
  128. if v, err := strconv.Atoi(value); err == nil {
  129. config.Dcredit = v
  130. }
  131. case "ucredit":
  132. if v, err := strconv.Atoi(value); err == nil {
  133. config.Ucredit = v
  134. }
  135. case "lcredit":
  136. if v, err := strconv.Atoi(value); err == nil {
  137. config.Lcredit = v
  138. }
  139. case "ocredit":
  140. if v, err := strconv.Atoi(value); err == nil {
  141. config.Ocredit = v
  142. }
  143. case "minclass":
  144. if v, err := strconv.Atoi(value); err == nil {
  145. config.Minclass = v
  146. }
  147. case "maxrepeat":
  148. if v, err := strconv.Atoi(value); err == nil {
  149. config.Maxrepeat = v
  150. }
  151. case "maxclassrepeat":
  152. if v, err := strconv.Atoi(value); err == nil {
  153. config.Maxclassrepeat = v
  154. }
  155. case "maxsequence":
  156. if v, err := strconv.Atoi(value); err == nil {
  157. config.Maxsequence = v
  158. }
  159. case "enforcing":
  160. if v, err := strconv.Atoi(value); err == nil {
  161. config.Enforcing = v
  162. }
  163. case "enforce_for_root":
  164. if v, err := strconv.Atoi(value); err == nil {
  165. config.EnforceForRoot = v
  166. }
  167. case "usercheck":
  168. if v, err := strconv.Atoi(value); err == nil {
  169. config.Usercheck = v
  170. }
  171. }
  172. } else {
  173. // 处理独立标志(无值的参数)
  174. // 例如:enforce_for_root(表示对 root 强制执行密码策略)
  175. key := strings.TrimSpace(line)
  176. switch key {
  177. case "enforce_for_root":
  178. // 如果以独立标志形式出现,设置为 1(强制执行)
  179. config.EnforceForRoot = 1
  180. }
  181. }
  182. }
  183. return config
  184. }
  185. // Validate 根据 pwquality 配置校验密码强度
  186. // username 为用户名,用于检查密码中是否包含用户名(如果启用了 usercheck)
  187. // 参考 libpwquality 的实现逻辑
  188. func (c *Config) Validate(password string, username string) error {
  189. if !c.HasAnyPolicy() {
  190. return nil
  191. }
  192. // 如果 enforcing=0,密码策略不会强制执行(只是警告),直接返回
  193. //if username != "root" && !c.IsEnforcing() {
  194. // return nil
  195. //}
  196. // 如果用户是 root 且 enforce_for_root=0,不对 root 强制执行密码策略
  197. //if username == "root" && !c.IsEnforcingForRoot() {
  198. // return nil
  199. //}
  200. // 统计各类字符数量
  201. var digits, uppers, lowers, others int
  202. for _, r := range password {
  203. if unicode.IsDigit(r) {
  204. digits++
  205. } else if unicode.IsUpper(r) {
  206. uppers++
  207. } else if unicode.IsLower(r) {
  208. lowers++
  209. } else {
  210. others++
  211. }
  212. }
  213. // 处理 credit 值
  214. // 根据 libpwquality 的文档:
  215. // - 负数(如 -1):表示至少需要多少个字符(常用)
  216. // - 正数(如 1):表示每个字符可以减少多少长度要求(较少用)
  217. // - 0:不要求
  218. if c.Dcredit < 0 {
  219. // 负数:至少需要这么多数字字符
  220. required := -c.Dcredit
  221. if digits < required {
  222. return errors.Wrapf(ErrPasswordTooWeak, "password requires at least %d digit(s), got %d", required, digits)
  223. }
  224. } else if c.Dcredit > 0 {
  225. // 正数:每个数字字符可以减少多少长度要求(较少使用)
  226. // 这里我们简化处理,只检查是否有数字
  227. if digits == 0 {
  228. return errors.Wrapf(ErrPasswordTooWeak, "password should contain at least one digit")
  229. }
  230. }
  231. if c.Ucredit < 0 {
  232. required := -c.Ucredit
  233. if uppers < required {
  234. return errors.Wrapf(ErrPasswordTooWeak, "password requires at least %d uppercase letter(s), got %d", required, uppers)
  235. }
  236. } else if c.Ucredit > 0 {
  237. if uppers == 0 {
  238. return errors.Wrapf(ErrPasswordTooWeak, "password should contain at least one uppercase letter")
  239. }
  240. }
  241. if c.Lcredit < 0 {
  242. required := -c.Lcredit
  243. if lowers < required {
  244. return errors.Wrapf(ErrPasswordTooWeak, "password requires at least %d lowercase letter(s), got %d", required, lowers)
  245. }
  246. } else if c.Lcredit > 0 {
  247. if lowers == 0 {
  248. return errors.Wrapf(ErrPasswordTooWeak, "password should contain at least one lowercase letter")
  249. }
  250. }
  251. if c.Ocredit < 0 {
  252. required := -c.Ocredit
  253. if others < required {
  254. return errors.Wrapf(ErrPasswordTooWeak, "password requires at least %d special character(s), got %d", required, others)
  255. }
  256. } else if c.Ocredit > 0 {
  257. if others == 0 {
  258. return errors.Wrapf(ErrPasswordTooWeak, "password should contain at least one special character")
  259. }
  260. }
  261. // 计算有效长度(考虑 credit 的正数值,用于减少长度要求)
  262. effectiveLength := len(password)
  263. if c.Dcredit > 0 {
  264. effectiveLength += digits * c.Dcredit
  265. }
  266. if c.Ucredit > 0 {
  267. effectiveLength += uppers * c.Ucredit
  268. }
  269. if c.Lcredit > 0 {
  270. effectiveLength += lowers * c.Lcredit
  271. }
  272. if c.Ocredit > 0 {
  273. effectiveLength += others * c.Ocredit
  274. }
  275. // 检查最小长度
  276. if c.Minlen > 0 && effectiveLength < c.Minlen {
  277. return errors.Wrapf(ErrPasswordTooWeak, "effective length %d is less than required %d", effectiveLength, c.Minlen)
  278. }
  279. // 检查最小字符类数量
  280. if c.Minclass > 0 {
  281. classes := 0
  282. if digits > 0 {
  283. classes++
  284. }
  285. if uppers > 0 {
  286. classes++
  287. }
  288. if lowers > 0 {
  289. classes++
  290. }
  291. if others > 0 {
  292. classes++
  293. }
  294. if classes < c.Minclass {
  295. return errors.Wrapf(ErrPasswordTooWeak, "requires at least %d character class(es), got %d", c.Minclass, classes)
  296. }
  297. }
  298. // 检查最大重复字符数
  299. if c.Maxrepeat > 0 {
  300. maxRepeat := 0
  301. currentRepeat := 1
  302. prevChar := rune(0)
  303. for _, r := range password {
  304. if r == prevChar {
  305. currentRepeat++
  306. if currentRepeat > maxRepeat {
  307. maxRepeat = currentRepeat
  308. }
  309. } else {
  310. currentRepeat = 1
  311. }
  312. prevChar = r
  313. }
  314. if maxRepeat > c.Maxrepeat {
  315. return errors.Wrapf(ErrPasswordTooWeak, "password contains more than %d consecutive repeated characters", c.Maxrepeat)
  316. }
  317. }
  318. // 检查最大同类字符重复数
  319. if c.Maxclassrepeat > 0 {
  320. maxClassRepeat := 0
  321. currentClassRepeat := 1
  322. prevClass := -1 // -1: 未设置, 0: 数字, 1: 大写, 2: 小写, 3: 特殊
  323. for _, r := range password {
  324. var currentClass int
  325. if unicode.IsDigit(r) {
  326. currentClass = 0
  327. } else if unicode.IsUpper(r) {
  328. currentClass = 1
  329. } else if unicode.IsLower(r) {
  330. currentClass = 2
  331. } else {
  332. currentClass = 3
  333. }
  334. if currentClass == prevClass {
  335. currentClassRepeat++
  336. if currentClassRepeat > maxClassRepeat {
  337. maxClassRepeat = currentClassRepeat
  338. }
  339. } else {
  340. currentClassRepeat = 1
  341. }
  342. prevClass = currentClass
  343. }
  344. if maxClassRepeat > c.Maxclassrepeat {
  345. return errors.Wrapf(ErrPasswordTooWeak, "password contains more than %d consecutive characters of the same class", c.Maxclassrepeat)
  346. }
  347. }
  348. // 检查最大连续字符序列长度
  349. // maxsequence 检查密码中是否存在超过指定长度的连续字符序列(如 "1234" 或 "abcd")
  350. if c.Maxsequence > 0 {
  351. runes := []rune(password)
  352. for i := 0; i <= len(runes)-c.Maxsequence-1; i++ {
  353. // 检查升序序列(如 "1234", "abcd")
  354. isAscending := true
  355. for j := 1; j <= c.Maxsequence; j++ {
  356. if i+j >= len(runes) || runes[i+j] != runes[i+j-1]+1 {
  357. isAscending = false
  358. break
  359. }
  360. }
  361. // 检查降序序列(如 "4321", "dcba")
  362. isDescending := true
  363. for j := 1; j <= c.Maxsequence; j++ {
  364. if i+j >= len(runes) || runes[i+j] != runes[i+j-1]-1 {
  365. isDescending = false
  366. break
  367. }
  368. }
  369. if isAscending || isDescending {
  370. return errors.Wrapf(ErrPasswordTooWeak, "password contains a sequence of more than %d consecutive characters", c.Maxsequence)
  371. }
  372. }
  373. }
  374. // 检查密码中是否包含用户名
  375. if c.Usercheck > 0 && username != "" {
  376. // 将用户名和密码都转换为小写进行比较(不区分大小写)
  377. lowerUsername := strings.ToLower(username)
  378. lowerPassword := strings.ToLower(password)
  379. // 检查密码中是否包含用户名(包括反向)
  380. if strings.Contains(lowerPassword, lowerUsername) {
  381. return errors.Wrapf(ErrPasswordTooWeak, "password contains the username")
  382. }
  383. // 检查密码中是否包含用户名的反向(用户名长度至少为3才检查反向)
  384. if len(lowerUsername) >= 3 {
  385. reversedUsername := reverseString(lowerUsername)
  386. if strings.Contains(lowerPassword, reversedUsername) {
  387. return errors.Wrapf(ErrPasswordTooWeak, "password contains the reversed username")
  388. }
  389. }
  390. }
  391. return nil
  392. }
  393. // reverseString 反转字符串
  394. func reverseString(s string) string {
  395. runes := []rune(s)
  396. for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
  397. runes[i], runes[j] = runes[j], runes[i]
  398. }
  399. return string(runes)
  400. }
  401. // ParsePAMConfig 解析 PAM 配置文件中的密码强度策略
  402. // 支持 pam_pwquality 和 pam_cracklib 模块
  403. //
  404. // 兼容性说明:
  405. // - enforcing 和 enforce_for_root 参数在 libpwquality 1.2.0+ 版本中支持
  406. // - 较老的系统(如 RHEL 6 之前)可能不支持这些参数
  407. // - 在 PAM 配置中,enforce_for_root 可能以独立标志形式出现(无值),如:
  408. // password requisite pam_pwquality.so minlen=8 enforce_for_root
  409. // 这种情况下,如果解析到 enforce_for_root 标志(无值),将设置为 1
  410. // - 如果系统不支持这些参数,配置文件中不会出现,将使用默认值
  411. func ParsePAMConfig(content []byte, config *Config) *Config {
  412. if config == nil {
  413. config = &Config{
  414. Minlen: 0,
  415. Dcredit: 0,
  416. Ucredit: 0,
  417. Lcredit: 0,
  418. Ocredit: 0,
  419. Minclass: 0,
  420. Maxrepeat: 0,
  421. Maxclassrepeat: 0,
  422. Maxsequence: 0,
  423. Enforcing: 1, // 默认值(1 表示强制执行)
  424. EnforceForRoot: 0, // 默认值(0 表示不对 root 强制执行)
  425. Usercheck: 0, // 默认值(0 表示不检查用户名)
  426. }
  427. }
  428. lines := strings.Split(string(content), "\n")
  429. for _, line := range lines {
  430. line = strings.TrimSpace(line)
  431. // 跳过注释和空行
  432. if len(line) == 0 || strings.HasPrefix(line, "#") {
  433. continue
  434. }
  435. // 查找 password 相关的 PAM 配置行
  436. // 格式: password requisite pam_pwquality.so retry=3 minlen=8 dcredit=-1 ucredit=-1
  437. // 或: password requisite pam_cracklib.so retry=3 minlen=8 dcredit=-1 ucredit=-1
  438. if !strings.Contains(line, "password") {
  439. continue
  440. }
  441. // 检查是否包含 pam_pwquality 或 pam_cracklib
  442. if !strings.Contains(line, "pam_pwquality") && !strings.Contains(line, "pam_cracklib") {
  443. continue
  444. }
  445. // 解析参数,格式为 key=value 或独立标志(如 enforce_for_root)
  446. // 先找到 .so 后面的参数部分
  447. parts := strings.Fields(line)
  448. for _, part := range parts {
  449. part = strings.TrimSpace(part)
  450. if len(part) == 0 {
  451. continue
  452. }
  453. // 检查是否是 key=value 格式
  454. if strings.Contains(part, "=") {
  455. kv := strings.SplitN(part, "=", 2)
  456. if len(kv) != 2 {
  457. continue
  458. }
  459. key := strings.TrimSpace(kv[0])
  460. value := strings.TrimSpace(kv[1])
  461. switch key {
  462. case "minlen":
  463. if v, err := strconv.Atoi(value); err == nil {
  464. config.Minlen = v
  465. }
  466. case "dcredit":
  467. if v, err := strconv.Atoi(value); err == nil {
  468. config.Dcredit = v
  469. }
  470. case "ucredit":
  471. if v, err := strconv.Atoi(value); err == nil {
  472. config.Ucredit = v
  473. }
  474. case "lcredit":
  475. if v, err := strconv.Atoi(value); err == nil {
  476. config.Lcredit = v
  477. }
  478. case "ocredit":
  479. if v, err := strconv.Atoi(value); err == nil {
  480. config.Ocredit = v
  481. }
  482. case "minclass":
  483. if v, err := strconv.Atoi(value); err == nil {
  484. config.Minclass = v
  485. }
  486. case "maxrepeat":
  487. if v, err := strconv.Atoi(value); err == nil {
  488. config.Maxrepeat = v
  489. }
  490. case "maxclassrepeat":
  491. if v, err := strconv.Atoi(value); err == nil {
  492. config.Maxclassrepeat = v
  493. }
  494. case "maxsequence":
  495. if v, err := strconv.Atoi(value); err == nil {
  496. config.Maxsequence = v
  497. }
  498. case "enforcing":
  499. if v, err := strconv.Atoi(value); err == nil {
  500. config.Enforcing = v
  501. }
  502. case "enforce_for_root":
  503. if v, err := strconv.Atoi(value); err == nil {
  504. config.EnforceForRoot = v
  505. }
  506. case "usercheck":
  507. if v, err := strconv.Atoi(value); err == nil {
  508. config.Usercheck = v
  509. }
  510. case "difok": // pam_cracklib 特有:至少需要多少个字符与旧密码不同
  511. // 这个参数不影响密码强度校验,可以忽略
  512. case "retry": // 重试次数,不影响密码强度校验
  513. }
  514. } else {
  515. // 处理独立标志(无值的参数)
  516. // 例如:enforce_for_root(表示对 root 强制执行密码策略)
  517. switch part {
  518. case "enforce_for_root":
  519. // 如果以独立标志形式出现,设置为 1(强制执行)
  520. config.EnforceForRoot = 1
  521. }
  522. }
  523. }
  524. }
  525. return config
  526. }
  527. // GeneratePassword 根据配置生成符合强度要求的密码
  528. // passwordGenerator 是一个函数,接受长度参数并返回密码
  529. // 如果 passwordGenerator 为 nil,将使用默认的最小长度 12
  530. func (c *Config) GeneratePassword(passwordGenerator func(int) string) string {
  531. if c == nil || !c.HasAnyPolicy() {
  532. // 如果没有配置或配置为空,使用默认长度生成
  533. if passwordGenerator != nil {
  534. return passwordGenerator(12)
  535. }
  536. return ""
  537. }
  538. // 计算所需的最小密码长度
  539. minLength := c.Minlen
  540. if minLength == 0 {
  541. minLength = 8 // 默认最小长度
  542. }
  543. // 根据 credit 要求计算额外需要的字符数
  544. requiredChars := 0
  545. if c.Dcredit < 0 {
  546. requiredChars += -c.Dcredit
  547. }
  548. if c.Ucredit < 0 {
  549. requiredChars += -c.Ucredit
  550. }
  551. if c.Lcredit < 0 {
  552. requiredChars += -c.Lcredit
  553. }
  554. if c.Ocredit < 0 {
  555. requiredChars += -c.Ocredit
  556. }
  557. // 确保长度满足所有要求
  558. passwordLength := minLength
  559. if requiredChars > 0 {
  560. // 至少需要 minLength 和 requiredChars 中的较大值
  561. if requiredChars > passwordLength {
  562. passwordLength = requiredChars
  563. }
  564. // 再加上一些缓冲,确保有足够的字符满足 minclass 要求
  565. if c.Minclass > 0 && c.Minclass > 1 {
  566. passwordLength += c.Minclass
  567. }
  568. }
  569. if passwordGenerator == nil {
  570. return ""
  571. }
  572. // 生成密码并验证,直到符合要求
  573. maxAttempts := 64
  574. for i := 12; i < maxAttempts; i += 2 {
  575. password := passwordGenerator(passwordLength)
  576. // GeneratePassword 不提供用户名,所以传空字符串
  577. if c.Validate(password, "") == nil {
  578. return password
  579. }
  580. // 如果不符合要求,增加长度重试
  581. passwordLength++
  582. }
  583. // 如果多次尝试都失败,返回一个较长的密码(应该能满足大部分要求)
  584. return passwordGenerator(passwordLength)
  585. }