因此,在 IT 领域作业,我们几乎一直都需要生成安全密码,某些组织除了所需的字符类数量和长度要求之外,还增加了更严格的要求。我作业的一个这样的组织也限制了可以连续出现的单个字符类(小写、大写、特殊字符、数字)的字符数。我已经构建了一个确实有助于实作这一点的函式,但是,它本质上只是暴力破解密码,这非常糟糕。你会如何从计算机科学的角度来解决这个特定的问题,知道速度是关键,同时保持随机性。
我觉得应该有某种我可以实作的 shuffle 技术,但是我想出的每个解决方案要么太慢,要么降低了字符串的随机性。
Function New-Password {
PARAM(
[Int]$PasswordLength = 64,
[Int]$MinUpperCase = 5,
[Int]$MinLowerCase = 5,
[Int]$MinSpecialCharacters = 5,
[Int]$MinNumbers = 5,
[Int]$ConsecutiveCharClass = 0,
[Int]$ConsecutiveCharCheckCount = 1000,
[String]$LowerCase = 'abcdefghiklmnoprstuvwxyz',
[String]$UpperCase = 'ABCDEFGHKLMNOPRSTUVWXYZ',
[String]$Numbers = '1234567890',
[String]$SpecialCharacters = '!"$%&/()=?}][{@#* ',
[String]$PasswordProfile = '',
#Advanced Options
[Bool]$EnhancedEntrophy = $True
)
If ([String]::IsNullOrEmpty($PasswordProfile) -eq $False) {
#You can define custom password profiles here for easy reference later on.
New-Variable -Force -Name:'PasswordProfiles' -Value:@{
'iDrac' = [PSCustomObject]@{PasswordLength=20;SpecialCharacters=" &?>-}|.!(',_[`"@#)*;$]/§%=<:{@";}
}
If ($PasswordProfile -in $PasswordProfiles.Keys) {
$PasswordProfiles[$PasswordProfile] |Get-Member -MemberType NoteProperty |ForEach-Object {
Set-Variable -Name $_.name -Value $PasswordProfiles[$PasswordProfile].($_.name)
}
}
}
New-Variable -Force -Name:'PassBldr' -Value @{}
New-Variable -Force -Name:'CharacterClass' -Value:([String]::Empty)
ForEach ($CharacterClass in @("UpperCase","LowerCase","SpecialCharacters","Numbers")) {
$Characters = (Get-Variable -Name:$CharacterClass -ValueOnly)
If ($Characters.Length -gt 0) {
$PassBldr[$CharacterClass] = [PSCustomObject]@{
Min = (Get-Variable -Name:"min$CharacterClass" -ValueOnly);
Characters = $Characters
Length = $Characters.length
}
}
}
#Sanity Check(s)
$MinimumChars = $MinUpperCase $MinLowerCase $MinSpecialCharacters $MinNumbers
If ($MinimumChars -gt $PasswordLength) {
Write-Error -Message:"Specified number of minimum characters ($MinimumChars) is greater than password length ($PasswordLength)."
Return
}
#New-Variable -Force -Name:'Random' -Value:(New-Object -TypeName:'System.Random')
New-Variable -Force -Name:'Randomizer' -Value:$Null
New-Variable -Force -Name:'Random' -Value:([ScriptBlock]::Create({
Param([Int]$Max=[Int32]::MaxValue,[Int32]$Min=1)
if ($Min -gt $Max) {
Write-Warning "[$($myinvocation.ScriptLineNumber)] Min ($Min) must be less than Max ($Max)."
return -1
}
if ($EnhancedEntrophy) {
if ($Randomizer -eq $Null) {
Set-Variable -Name:'Randomizer' -Value:(New-Object -TypeName:'System.Security.Cryptography.RNGCryptoServiceProvider') -Scope:1
}
#initialize everything
$Difference=$Max-$Min
[Byte[]] $bytes = 1..4 #4 byte array for int32/uint32
#generate the number
$Randomizer.getbytes($bytes)
$Number = [System.BitConverter]::ToUInt32(($bytes),0)
return ([Int32]($Number % $Difference $Min))
} Else {
if ($Randomizer -eq $Null) {
Set-Variable -Name:'Randomizer' -Value:(New-Object -TypeName:'System.Random') -Scope:1
}
return ([Int]$Randomizer.Next($Min,$Max))
}
}))
$GetString = [ScriptBlock]::Create({
Param([Int]$Length,[String]$Characters)
Return ([String]$Characters[(1..$Length |ForEach-Object {& $Random $Characters.length})] -replace " ","")
})
$CreatePassword = [scriptblock]::Create({
New-Variable -Name Password -Value ([System.Text.StringBuilder]::new()) -Force
#Meet the minimum requirements for each character class
ForEach ($CharacterClass in $PassBldr.Values) {
If ($CharacterClass.Min -gt 0) {
$Null = $Password.Append([string](Invoke-Command $GetString -ArgumentList $CharacterClass.Min,$CharacterClass.Characters))
}
}
#Now meet the minimum length requirements.
If ([Int]($PasswordLength-$Password.length) -gt 0) {
$Null = $Password.Append((Invoke-Command $GetString -ArgumentList ($PasswordLength-$Password.length),($PassBldr.Values.Characters -join "")))
}
return (([Char[]]$Password.ToString() | Get-Random -Count $Password.Length) -join "")
})
Switch ([Int]$ConsecutiveCharClass) {
'0' { New-Variable -Name NewPassword -Value (& $CreatePassword) -Force }
{$_ -gt 0} {
New-Variable -Name CheckPass -Value $False -Force
New-Variable -Name CheckCount -Value ([Int]0) -Force
For ($I=0; $I -le $ConsecutiveCharCheckCount -and $CheckPass -eq $False; $I ) {
New-Variable -Name NewPassword -Value (& $CreatePassword) -Force
$TestPassed = 0
ForEach ($CharClass in $PassBldr.Values) {
IF ([Regex]::IsMatch([Regex]::Escape($NewPassword),"[$([Regex]::Escape($CharClass.Characters))]{$ConsecutiveCharClass}") -eq $False) {
$TestPassed
}
}
if ($TestPassed -eq $CheckClasses.Count) {
$CheckPass = $True
}
}
}
Default {Write-Warning -Message "This shouldn't be possible, how did you get here?!"}
}
Return $NewPassword
}
uj5u.com热心网友回复:
在进一步讨论之前,我应该注意这些属性(符合所描述的策略与保持随机性/熵)是相互排斥的 - 您不能通过仔细“纠正”来自 PRNG 的输出分布来“保持随机性”。
我会将问题分成两个独立的功能:
Test-PasswordCharSequence
- 快速验证给定的密码字符串是否符合策略Shuffle-PasswordCharSequence
- 随机打乱任何给定密码中的字符一次
原子化这些核心操作应该更容易调整/重构。
对于验证函式,使用正则表达式可能很诱人——但我建议简单地遍历字符串并跟踪同一类的连续字符。
function Test-PasswordCharSequence {
param(
[string]
$String,
[System.Collections.IDictionary]
$CharacterMap,
[int]$Limit = 5
)
# Keep tracking the last seen character class and length of consecutive sequence
$currentClass = ""
$counter = 0
foreach($char in $String.ToCharArray())
{
if($CharacterMap.ContainsKey($char) -and $CharacterMap[$char] -eq $currentClass)
{
$counter
}
else
{
$counter = 1
$currentClass = $CharacterMap[$char]
}
# if we've seen the same class for too many consecutive characters, fail
if($counter -gt $Limit){
return $false
}
}
# No sequence over limit observed
return $true
}
然后我们需要一个函式来洗牌密码。我所知道的最有效的真正随机(同样取决于所使用的 RNG)混洗算法是就地 Fisher-Yates 混洗算法,可以按如下方式实作:
function Shuffle-PasswordCharSequence
{
param(
[Parameter(Mandatory)]
[string]$String
)
$chars = $String.ToCharArray()
$max = $chars.Length
#Fisher-Yates Left to Right
for($i = 0; $i -lt $max - 1; $i )
{
$j = Get-Random -Minimum 0 -Maximum ($max - $i)
$chars[$j],$chars[$i $j] = $chars[$i $j],$chars[$j]
}
return [string]::new($chars)
}
要将这些与您现有的New-Password
功能结合使用:
# define character classes to use
$CharacterClasses = @{
LowerCase = 'abcdefghiklmnoprstuvwxyz'
UpperCase = 'ABCDEFGHKLMNOPRSTUVWXYZ'
Numbers = '1234567890'
SpecialCharacters = '!"$%&/()=?}][{@#* '
}
# generate inverse character map for the validation function
# we use [Dictionary[char,string]] rather than [hashtable] to ensure case-sensitive handling of keys ('b' vs 'B')
$classMap = [System.Collections.Generic.Dictionary[char,string]]::new()
foreach($entry in $CharacterClasses.GetEnumerator())
{
foreach($char in $entry.Value.ToCharArray())
{
$classMap[$char] = $entry.Name
}
}
# generate initial password
$passwordCandidate = New-Password -PasswordLength 127 @CharacterClasses
# validate generated password, shuffle until successful
$shuffleCount = 0
while(!(Test-PasswordCharSequence $passwordCandidate -CharacterMap $classMap)){
$passwordCandidate = Shuffle-PasswordCharSequence $passwordCandidate
$shuffleCount
}
Write-Host "Generated valid password after ${shuffleCount} shuffles"
uj5u.com热心网友回复:
我认为@vonPryz在他的评论中提到的“为什么不逐个字符地构建密码? ”实际上是可能的,并且可能是最快的方法。
关键是您必须分两个阶段创建密码,首先使用字符集(而不是最终字符)构建复杂性串列,然后在下一阶段为该位置的字符集选择相关字符。如果$MaxConsecutiveChar
达到,则从该位置的字符集中选择一个新字符:
Function New-Password {
Param(
[Int]$PasswordLength = 64,
[Int]$MinUpperCase = 5,
[Int]$MinLowerCase = 5,
[Int]$MinSpecialCharacters = 5,
[Int]$MinNumbers = 5,
[Int]$MaxConsecutiveChar = 3,
[String]$LowerCase = 'abcdefghiklmnoprstuvwxyz',
[String]$UpperCase = 'ABCDEFGHKLMNOPRSTUVWXYZ',
[String]$Numbers = '1234567890',
[String]$SpecialCharacters = '!"$%&/()=?}][{@#* '
)
enum CharSet {
LowerCase
UpperCase
SpecialCharacters
Numbers
}
$CharSets = [system.collections.generic.dictionary[CharSet, Char[]]]::new()
$MinSetChars = [system.collections.generic.dictionary[CharSet, Int]]::new()
$MinimumChars = 0
[CharSet].GetEnumNames().ForEach{
$CharSets[$_] = (Get-Variable -ValueOnly -Name $_).ToCharArray()
$MinChar = [Int](Get-Variable -ValueOnly -Name "Min$_")
$MinSetChars[$_] = $MinChar
$MinimumChars = $MinChar
}
If ($MinimumChars -gt $PasswordLength) {
Throw "Specified number of minimum characters ($MinimumChars) is greater than password length ($PasswordLength)."
}
# Build a list of characters sets
$SetList = for ($i = 0; $i -lt $PasswordLength; $i ) { $CharSets.Keys |Get-Random }
# Insert the Min* required characters for the specific sets
# Making sure that the position is not already taken by another Min* characterset
$Used = [System.Collections.Generic.HashSet[int]]::New()
$CharSets.Keys.ForEach{
for ($i = 0; $i -lt $MinSetChars[$_]) {
$At = Get-Random $PasswordLength
if (!$Used.Contains[$At]) {
$SetList[$At] = $_
$Null = $Used.Add($At)
$i
}
}
}
# Elect a character for each set
$LastChar = $Null
$ConsecutiveChars = 1
-Join $SetList.ForEach{
$Char = $CharSets[$_] |Get-Random
# Check the consecutive characters (choose another when required)
While ($ConsecutiveChars -ge $MaxConsecutiveChar -and $Char -eq $LastChar) { $Char = $CharSets[$_] |Get-Random }
$ConsecutiveChars = if ($Char -eq $LastChar) { $ConsecutiveChars 1 } else { 1 }
$LastChar = $Char
$Char
}
}
New-Password
X23@[X0C5%FL3Demyf5?5})f]5Kt#usC#m1 3?T(NOb4DmYsX8FA3pF46OUZeW3V
为了证明-MaxConsecutiveChar
有效并且安静快速:
$Params = @{
MaxConsecutiveChar = 1
LowerCase = 'ab'
UpperCase = 'ab'
Numbers = 'ab'
SpecialCharacters = 'ab'
}
New-Password @Params
babababababababababababababababababababababababababababababababa
0 评论