scripts/New-Password.ps1
3.9 KB| 1 | #requires -Version 5.1 |
| 2 | <# |
| 3 | .SYNOPSIS |
| 4 | 生成密码学安全的随机密码。 |
| 5 | .DESCRIPTION |
| 6 | 使用 System.Security.Cryptography.RandomNumberGenerator 生成无偏随机密码, |
| 7 | 支持自定义长度、字符集、批量生成、排除易混淆字符、保证每类字符至少一个、结果写文件。 |
| 8 | 全程离线,不发起任何网络请求。 |
| 9 | .PARAMETER Length |
| 10 | 密码长度,默认 16,范围 4-128。 |
| 11 | .PARAMETER Count |
| 12 | 生成密码条数,默认 1,范围 1-100。 |
| 13 | .PARAMETER ExcludeAmbiguous |
| 14 | 排除易混淆字符:0 O o 1 l I | ` ' "。 |
| 15 | .PARAMETER NoLowercase |
| 16 | 不使用小写字母。 |
| 17 | .PARAMETER NoUppercase |
| 18 | 不使用大写字母。 |
| 19 | .PARAMETER NoDigits |
| 20 | 不使用数字。 |
| 21 | .PARAMETER NoSymbols |
| 22 | 不使用特殊符号。 |
| 23 | .PARAMETER OutFile |
| 24 | 将结果写入指定文件(UTF-8 无 BOM);不指定则只输出到屏幕。 |
| 25 | .EXAMPLE |
| 26 | .\New-Password.ps1 -Length 20 -Count 3 |
| 27 | .EXAMPLE |
| 28 | .\New-Password.ps1 -NoSymbols -ExcludeAmbiguous |
| 29 | #> |
| 30 | [CmdletBinding()] |
| 31 | param( |
| 32 | [ValidateRange(4, 128)] |
| 33 | [int]$Length = 16, |
| 34 | |
| 35 | [ValidateRange(1, 100)] |
| 36 | [int]$Count = 1, |
| 37 | |
| 38 | [switch]$ExcludeAmbiguous, |
| 39 | [switch]$NoLowercase, |
| 40 | [switch]$NoUppercase, |
| 41 | [switch]$NoDigits, |
| 42 | [switch]$NoSymbols, |
| 43 | |
| 44 | [string]$OutFile |
| 45 | ) |
| 46 | |
| 47 | Set-StrictMode -Version 2.0 |
| 48 | |
| 49 | # 组装字符集 |
| 50 | $sets = New-Object System.Collections.Generic.List[string] |
| 51 | if (-not $NoLowercase) { $sets.Add('abcdefghijklmnopqrstuvwxyz') } |
| 52 | if (-not $NoUppercase) { $sets.Add('ABCDEFGHIJKLMNOPQRSTUVWXYZ') } |
| 53 | if (-not $NoDigits) { $sets.Add('0123456789') } |
| 54 | if (-not $NoSymbols) { $sets.Add('!@#$%^&*()-_=+[]{};:,.?/') } |
| 55 | |
| 56 | if ($sets.Count -eq 0) { |
| 57 | throw '至少保留一个字符集,不能同时关闭全部四类字符。' |
| 58 | } |
| 59 | |
| 60 | if ($ExcludeAmbiguous) { |
| 61 | $ambiguous = [char[]]@('0', 'O', 'o', '1', 'l', 'I', '|', [char]96, [char]39, [char]34) |
| 62 | for ($i = 0; $i -lt $sets.Count; $i++) { |
| 63 | $s = $sets[$i] |
| 64 | foreach ($c in $ambiguous) { $s = $s.Replace([string]$c, '') } |
| 65 | $sets[$i] = $s |
| 66 | } |
| 67 | $sets = [System.Collections.Generic.List[string]]@($sets | Where-Object { $_.Length -gt 0 }) |
| 68 | if ($sets.Count -eq 0) { |
| 69 | throw '排除易混淆字符后没有可用字符,请调整参数。' |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | $allChars = -join $sets |
| 74 | $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() |
| 75 | $buffer = New-Object byte[] 1 |
| 76 | |
| 77 | # 返回 [0, exclusiveMax) 内的无偏随机整数(拒绝采样,exclusiveMax 不超过 256) |
| 78 | function Get-RandomIndex { |
| 79 | param([int]$ExclusiveMax) |
| 80 | $limit = 256 - (256 % $ExclusiveMax) |
| 81 | do { |
| 82 | $rng.GetBytes($buffer) |
| 83 | } while ($buffer[0] -ge $limit) |
| 84 | return [int]($buffer[0] % $ExclusiveMax) |
| 85 | } |
| 86 | |
| 87 | function Get-RandomChar { |
| 88 | param([string]$CharSet) |
| 89 | return $CharSet[(Get-RandomIndex -ExclusiveMax $CharSet.Length)] |
| 90 | } |
| 91 | |
| 92 | if ($Length -lt $sets.Count) { |
| 93 | Write-Warning "长度 $Length 小于启用的字符集数 $($sets.Count),无法保证每类字符至少出现一个。" |
| 94 | } |
| 95 | |
| 96 | $results = New-Object System.Collections.Generic.List[string] |
| 97 | |
| 98 | for ($n = 0; $n -lt $Count; $n++) { |
| 99 | $chars = New-Object System.Collections.Generic.List[char] |
| 100 | |
| 101 | # 每个启用字符集先各取一个,保证类别齐全 |
| 102 | if ($Length -ge $sets.Count) { |
| 103 | foreach ($s in $sets) { |
| 104 | $chars.Add((Get-RandomChar -CharSet $s)) |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | # 剩余位置从合并字符池随机取 |
| 109 | while ($chars.Count -lt $Length) { |
| 110 | $chars.Add((Get-RandomChar -CharSet $allChars)) |
| 111 | } |
| 112 | |
| 113 | # Fisher-Yates 洗牌 |
| 114 | for ($i = $chars.Count - 1; $i -gt 0; $i--) { |
| 115 | $j = Get-RandomIndex -ExclusiveMax ($i + 1) |
| 116 | $tmp = $chars[$i] |
| 117 | $chars[$i] = $chars[$j] |
| 118 | $chars[$j] = $tmp |
| 119 | } |
| 120 | |
| 121 | $results.Add(-join $chars) |
| 122 | } |
| 123 | |
| 124 | $rng.Dispose() |
| 125 | |
| 126 | if ($OutFile) { |
| 127 | [System.IO.File]::WriteAllLines($OutFile, $results, (New-Object System.Text.UTF8Encoding($false))) |
| 128 | Write-Output "已写入 $($results.Count) 个密码到:$OutFile" |
| 129 | } else { |
| 130 | Write-Output $results |
| 131 | } |
文件内容是平台检查时读出的文本,只供查看。平台不会运行包里的任何脚本或程序。

