Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build passwords as a SecureString. #5

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# PSPasswordGenerator change log

## Version 3.1.0 (in development)
Packaging improvements.
- This version is more secure, as the generated password is now built in memory as a `[SecureString]`, and only converted from one when this cmdlet is run with `-AsPlainText`.
- Packaging improvements.

## Version 3.0.0 (March 17, 2022)
- We can now generate human-friendly passwords from wordlists.
Expand Down
4 changes: 2 additions & 2 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# PSPasswordGenerator
History of user-visible changes.
Last update: 2024-01-31
Last update: 2024-02-01

## PSPasswordGenerator version 3.0.1, in development
Packaging improvements.
This version builds the password in-memory as a `[SecureString]`, which means it is never stored insecurely.

## PSPasswordGenerator version 3.0.0, released 3/17/2022
Not dead yet! In this version, the cmdlet's verb has been changed. It is now called `Get-RandomPassword` (but `New-RandomPassword` still works.)
Expand Down
5 changes: 3 additions & 2 deletions PSPasswordGenerator.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
RootModule = 'src/PSPasswordGenerator.psm1'

# Version number of this module.
ModuleVersion = '3.0.1'
ModuleVersion = '3.1.0'

# Supported PSEditions
CompatiblePSEditions = @('Desktop', 'Core')
Expand Down Expand Up @@ -91,7 +91,8 @@ PrivateData = @{
IconUri = 'https://raw.githubusercontent.com/rhymeswithmogul/PSPasswordGenerator/main/icon/PSPasswordGenerator.png'

# ReleaseNotes of this module
ReleaseNotes = 'Packaging improvements.'
ReleaseNotes = '- Strings are now generated securely on supported platforms.
- Packaging improvements.'

# Flag to indicate whether the module requires explicit user acceptance for install/update/save
RequireLicenseAcceptance = $false
Expand Down
66 changes: 37 additions & 29 deletions src/PSPasswordGenerator.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
#>

#Requires -Version 3.0
#Requires -Version 5.1

# .ExternalHelp PSPasswordGenerator-help.xml
Function Get-RandomPassword {
Function Get-RandomPassword
{
[CmdletBinding(DefaultParameterSetName='RandomSecurely')]
[OutputType([String], ParameterSetName='RandomInsecurely')]
[OutputType([String], ParameterSetName='WordsInsecurely')]
Expand Down Expand Up @@ -64,7 +65,7 @@ Function Get-RandomPassword {
Write-Warning 'The -NoSymbols parameter was also specified. No extended ASCII characters will be used.'
}

$ret = ""
$ret = [SecureString]::new()
If ($PSCmdlet.ParameterSetName -Like 'Random*') {
For ($i = 0; $i -lt $Length; $i++) {
Do {
Expand All @@ -86,13 +87,13 @@ Function Get-RandomPassword {
# If the -NoSymbols parameter was specified, this loop will ensure
# that the character is neither a symbol nor in the extended ASCII
# character set.

} While ($i -eq 0 -And $StartWithLetter -And -Not (($x -ge 65 -And $x -le 90) -Or ($x -ge 97 -And $x -le 122)))
# If the -StartWithLetter parameter was specified, this loop will make
# sure that the first character is an upper- or lower-case letter.

Write-Debug "SUCCESS: Adding character: $([char]$x)"
$ret += [char]$x
$ret.AppendChar($x)
}
}

Expand All @@ -103,7 +104,6 @@ Function Get-RandomPassword {
$allWords = Get-Content -LiteralPath $WordList -ErrorAction Stop
$culture = (Get-Culture).TextInfo

$ret = ''
For ($i = 0; $i -lt $Words; $i++) {
# Pick a random word from the list.
$word = Get-Random $allWords
Expand All @@ -114,33 +114,41 @@ Function Get-RandomPassword {
}

# Stick something in between the words.
# Letters are 65-90 (caps) and 97-122 (lower)
$separator = 0
Do {
$ch = (Get-RandomPassword -Length 1 -NoSymbols:$NoSymbols -AsPlainText -UseExtendedAscii:$UseExtendedAscii)
Write-Debug "Trying separator $ch."
$separator = [Convert]::ToByte([Char]$ch)
} While (
($separator -ge 65 -and $separator -le 90) <# No uppercase letters #> `
-or ($separator -ge 97 -and $separator -le 122) <# No lowercase letters #> `
-or ($separator -ge 128 -and $separator -le 165) <# No accented letters #> `
-or ($separator -gt 165 -and -Not $UseExtendedAscii) <# Unwanted extended ASCII #> `
)

Write-Debug "WORD=`"$word`", SEP=`"$([Char]$separator)`""
$ret += $word
$ret += [Char]$separator
}
# Letters are 65-90 (caps) and 97-122 (lower).
# Don't bother finding a separator if this is the final word.
If ($i -eq ($Words - 1))
{
Write-Debug "WORD=`"$word`""
}
Else
{
$separator = 0
Do {
$ch = (Get-RandomPassword -Length 1 -NoSymbols:$NoSymbols -AsPlainText -UseExtendedAscii:$UseExtendedAscii)
Write-Debug "Trying separator $ch."
$separator = [Convert]::ToByte([Char]$ch)
} While (
($separator -ge 65 -and $separator -le 90) <# No uppercase letters #> `
-or ($separator -ge 97 -and $separator -le 122) <# No lowercase letters #> `
-or ($separator -ge 128 -and $separator -le 165) <# No accented letters #> `
-or ($separator -gt 165 -and -Not $UseExtendedAscii) <# Unwanted extended ASCII #> `
)
Write-Debug "WORD=`"$word`", SEP=`"$([Char]$separator)`""

$word += [Char]$separator
}

# Chop off the final separator.
$ret = $ret.Substring(0, $ret.Length - 1)
# SecureStrings can only be appended to one character at a time.
$word.ToCharArray() | ForEach-Object {
Write-Debug "Appending character: $_"
$ret.AppendChar($_)
}
}
}

If ($AsPlainText) {
Return $ret
Return (ConvertFrom-SecureString $ret -AsPlainText)
} Else {
$ss = ConvertTo-SecureString -AsPlainText -Force -String $ret
Remove-Variable -Name 'ret' -ErrorAction SilentlyContinue
Return $ss
Return $ret
}
}