-
-
Save AfroThundr3007730/60cb4d3cc7f24e9e7d3ace6ed11f1479 to your computer and use it in GitHub Desktop.
# Original version | |
function New-SecurePassword { | |
<# .SYNOPSIS | |
Generates high entropy passwords of configurable length #> | |
[Alias('genpw')] | |
Param( | |
# Length of passwords to genereate | |
[int]$Length = 20, | |
# How many passwords to generate | |
[int]$Count = 5, | |
# Quiet mode, only emit passwords | |
[switch]$Quiet | |
) | |
if (!$Quiet) { | |
Write-Output ("Generating {0} passwords of length {1}`n" -f $Count, $Length) | |
} | |
foreach ($_ in (1..$Count)) { | |
(& { foreach ($_ in (1..$Length)) { | |
do { $b = [Security.Cryptography.RandomNumberGenerator]::GetBytes(1) } | |
until ($b -ge 33 -and $b -le 126); [char[]]$b | |
} }) -join '' | |
} | |
if (!$Quiet) { | |
Write-Output ("`nEffective entropy of each: {0:n}" -f ` | |
([math]::log([math]::pow(94, $Length)) / [math]::log(2))) | |
} | |
} | |
# PSCore 6 or later (3-4x faster) | |
function New-SecurePassword { | |
<# .SYNOPSIS | |
Generates high entropy passwords of configurable length #> | |
[Alias('genpw')] | |
Param( | |
# Length of passwords to genereate | |
[int]$Length = 20, | |
# How many passwords to generate | |
[int]$Count = 5, | |
# Quiet mode, only emit passwords | |
[switch]$Quiet | |
) | |
if (!$Quiet) { | |
Write-Output ("Generating {0} passwords of length {1}`n" -f $Count, $Length) | |
} | |
foreach ($_ in (1..$Count)) { | |
[char[]](Get-Random -Min 33 -Max 126 -Count $Length) -join '' | |
} | |
if (!$Quiet) { | |
Write-Output ("`nEffective entropy of each: {0:n}" -f ` | |
[math]::log2([math]::pow(94, $Length))) | |
} | |
} |
#!/bin/bash | |
function genpw() { | |
local length=20 count=5 l c | |
while [[ -n $1 ]]; do | |
[[ $1 =~ -l|--length ]] && { shift; length=$1; shift; } | |
[[ $1 =~ -c|--count ]] && { shift; count=$1; shift; } | |
[[ $1 =~ -q|--quiet ]] && { local quiet=true; shift; } | |
done | |
[[ -n $quiet ]] || printf 'Generating %d passwords of length %d\n\n' "$count" "$length" | |
printf '%b\n' "$( | |
tr -dc '[:graph:]' < /dev/urandom | fold -w "$length" | head -n "$count" | |
)"; | |
[[ -n $quiet ]] || | |
printf '\nEffective entropy of each: %.2f\n' "$(bc -l <<< "l(94^$length)/l(2)")" | |
} |
Observations on the effects of transforming values in a uniform distribution (attempting to optimize out the Get-Random
call).
Expand for code
Removes usage of Get-Random
but introduces a 3:2 distribution bias on chars 0-67 (255%94):
function New-SecurePassword {
<# .SYNOPSIS
Generates high entropy passwords of configurable length #>
[Alias('genpw')]
Param(
# Length of passwords to genereate
[int]$Length = 20,
# How many passwords to generate
[int]$Count = 5,
# Quiet mode, only emit passwords
[switch]$Quiet
)
if (!$Quiet) {
Write-Output ("Generating {0} passwords of length {1}`n" -f $Count, $Length)
}
foreach ($_ in (1..$Count)) {
[char[]]([Security.Cryptography.RandomNumberGenerator]::GetBytes($Length) |
& { process { $_ % 94 + 33 } }) -join '' # Distribution bias 3:2 on chars 0 to 67
}
if (!$Quiet) {
Write-Output ("`nEffective entropy of each: {0:n}" -f `
([math]::log([math]::pow(94, $Length)) / [math]::log(2)))
}
}
Can be tested with:
# 10000 char sample
$a = genpw 10000 1 -q
$count = @{}
[char[]]$a | & { process { $count[$_]++ } }
$count.keys | Sort-Object | & { process { Write-Host $_ = $count[$_] } }
# 0 to 255 sample
$b = [char[]](0..255 | & { process { $_ % 94 + 33 } }) -join ''
$count = @{}
[char[]]$b | & { process { $count[$_]++ } }
$count.keys | Sort-Object | & { process { Write-Host $_ = $count[$_] } }
Transforming the output disturbs the uniform distribution. To preserve it, we must keep sampling until the selection criteria are met.
function New-SecurePassword {
<# .SYNOPSIS
Generates high entropy passwords of configurable length #>
[Alias('genpw')]
Param(
# Length of passwords to genereate
[int]$Length = 20,
# How many passwords to generate
[int]$Count = 5,
# Quiet mode, only emit passwords
[switch]$Quiet
)
if (!$Quiet) {
Write-Output ("Generating {0} passwords of length {1}`n" -f $Count, $Length)
}
foreach ($_ in (1..$Count)) {
(& { foreach ($_ in (1..$Length)) {
do { $b = [Security.Cryptography.RandomNumberGenerator]::GetBytes(1) }
until ($b -ge 33 -and $b -le 126); [char[]]$b
} }) -join ''
}
if (!$Quiet) {
Write-Output ("`nEffective entropy of each: {0:n}" -f `
([math]::log([math]::pow(94, $Length)) / [math]::log(2)))
}
}
The revised version no longer has a bias:
# 10000 char sample
$a = genpw 10000 1 -q
$count = @{}
[char[]]$a | & { process { $count[$_]++ } }
$count.keys | Sort-Object | & { process { Write-Host $_ = $count[$_] } }
# 10000 char Get-Random
$b = [char[]](Get-Random -Min 33 -Max 126 -Count 10000) -join ''
$count = @{}
[char[]]$b | & { process { $count[$_]++ } }
$count.keys | Sort-Object | & { process { Write-Host $_ = $count[$_] } }
The final version tends to iterate ~2.5 times $Length
for each password. It does however, ensure the uniform distribution is preserved. This exercise would be wholly unnecessary if Get-Random
supported the Count
parameter in earlier PowerShell versions, as it's actually faster. Invoking it for each character (the old behavior), however, is much slower.
Now part of my HelperFunctions module.
Some observations on bash loop performance, builtins vs binaries, and the impact of subshells.
Expand for code
It seems that minimizing the use of subshells results in faster execution. For higher counts, the loop can be made faster by using external binaries, but I elected to use bash builtins, as the performance hit is minimal. This varies depending on the number of strings generated, as higher counts offset the overhead of calling external binaries.
For lower counts, they aren't really worth it.Per the below comment, the modulus operation introduces a bias, and the external method is performant enough anyway.