DicewarePassword.psm1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
[System.Security.Cryptography.RandomNumberGenerator]$rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create()

function Get-SecureRandom {

<#
    .SYNOPSIS
        Gets a cryptographically secure random number.
    .DESCRIPTION
        The Get-SecureRandom cmdlet gets a randomly selected number using System.Security.Cryptography.RNGCryptoServiceProvider.
 
        Get-SecureRandom behaves similarly Get-Random, except that it doesn't accept a user-provided seed, choose items from a list, or produce anything other than 32-bit integers.
    .PARAMETER Maximum
        The maximum value for the generated random number. This must be stricty greater than Minimum.
    .PARAMETER Minimum
        The minimum value for the generated random number. This must be strictly less than Maximum.
    .EXAMPLE
        Get-SecureRandom -Maximum 100 -Minimum 1
        99
    .EXAMPLE
        Get-SecureRandom
        642640509
    .OUTPUTS
        System.Int32
    .NOTES
        Author: Alex Godofsky
        Contact: https://github.com/AlexGodofsky
        Version: 1.0
        Last Updated: 20160621
        Last Updated By: Alex Godofsky
        Last Update Notes:
        - Created
#>


    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory = $false)]
        [System.Int32]$Maximum = [System.Int32]::MaxValue,

        [Parameter(Position = 1, Mandatory = $false)]
        [System.Int32]$Minimum = 0
    )

    PROCESS {

        if ($Maximum -le $Minimum) {

            Throw New-Object System.ArgumentException "The Minimum value ({$Minimum}) cannot be greater than or equal to the Maximum value ({$Maximum})."

        } # end if

        [System.UInt64]$delta = $Maximum - $Minimum + 1
        [System.UInt64]$upper = $delta * [System.Math]::Floor(([System.UInt32]::MaxValue + 1) / $delta)
        $bytes = New-Object Byte[] 4
        [System.UInt32]$value = 0

        do { # this loop avoids a bias when the range of values doesn't cleanly divide 2<<32

            $script:rng.GetBytes($bytes)
            $value = [System.BitConverter]::ToUInt32($bytes, 0)

        } while ($value -gt $upper) # end do while loop

        return [System.Int32](($value % $delta) + $Minimum)

    } # end PROCESS block

} # end function Get-SecureRandom


function Invoke-DiceRoll {

<#
    .SYNOPSIS
        Simulates a dice roll and returns a number based on the result of rolling the desired number of dice.
    .DESCRIPTION
        Simulates a dice roll and returns a number based on the result of rolling the desired number of dice.
 
        The default dice count is 5, which was driven by the Diceware Passphrase minimum word length recommendation. See the help for the 'New-DicewarePassword' cmdlet/function for more information.
    .PARAMETER DiceCounty
        The number of dice to roll
    .EXAMPLE
        Invoke-DiceRoll -DiceCount 8
        51334144
    .EXAMPLE
        Invoke-DiceRoll
        31544
    .INPUTS
        System.Int32
    .OUTPUTS
        System.String
    .NOTES
        Author: Kevin Kirkpatrick
        Contact: https://github.com/vScripter
        Version: 1.1
        Last Updated: 20160621
        Last Updated By: Alex Godofsky
        Last Update Notes:
        - Uses a cryptographic RNG instead of Get-Random
#>


    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory = $false)]
        [ValidateRange(1, 8)]
        [System.Int32]$DiceCount = 5
    )

    BEGIN {

        #Requires -Version 3

    } # end BEGIN block

    PROCESS {

        $i = 0

        Write-Verbose -Message "[Invoke-DiceRoll] Rolling dice; generating a {$DiceCount} digit random number."
        try {

            $numberResult = $null

            for (; $i -lt $DiceCount; $i++) {

                $number = $null
                $number = "$(Get-SecureRandom -Minimum 1 -Maximum 6)"
                $numberResult += $number

            } # end for loop

            $numberResult

        } catch {

            Write-Warning -Message "[Invoke-DiceRoll][ERROR]Error generating random number. $_ "

        } # end try/catch

    } # end PROCESS block

    END {

        Write-Verbose -Message "[Invoke-DiceRoll] Processing Complete"

    } # end END block

} # end function Invoke-DiceRoll


function New-DicewarePassword {

<#
    .SYNOPSIS
        This function will generate/return a Diceware Password.
    .DESCRIPTION
        This function will generate/return a Diceware Password.
 
        Diceware involves, literally, rolling dice and matching the resulting numbers to a list containing 7,776 English words, each identified by a five-digit number.
 
        For more information on what the 'Diceware Passphrase' concept is, see the following link: http://world.std.com/~reinhold/diceware.html
 
        This function was inspired by the concepts and methods of creating Diceware Passphrases. That said, this function cannot guarantee 100% unique unique results for a few reasons:
            1. I had to substite out the double-quote charater (") associated with number/entry 66634 with double-colons (::); I had issues returning a single, double-quote charater, based on how PowerShell handles quoations
            2. Since a computer is handling the 'digital' dice rolls, one could argue that a bug in some system code, or at some other low level of computation, a computer (potentially) follows a discoverable algorithm...that's as far as I'll go, on that.
 
        Overall, this should still be a good tool to generate more complex, and easy to remember, passwords than you may have previously used.
 
        There are several 'Security Levels' that you can choose from, which equates to the total number of words utilized. In the Diceware FAQ, it states that "Six words may be breakable by an organization with a very large budget, such as a large country's
        security agency. Seven words and longer are unbreakable with any known technology, but may be within the range of large organizations by around 2030. Eight words should be completely secure through 2050."
 
        Some additional good resources/articles:
        http://arstechnica.com/business/2015/10/this-11-year-old-is-selling-cryptographically-secure-passwords-for-2-each/
        http://arstechnica.com/information-technology/2014/03/diceware-passwords-now-need-six-random-words-to-thwart-hackers/
    .PARAMETER FetchType
        Specify if you want to import the file from the web or from a local file path
    .PARAMETER URI
        URI of web-hosted file
    .PARAMETER Path
        Full path to file location
    .PARAMETER SecurityLevel
        Desired Security Level.
 
        Average = 5 words
        High = 6 words
        Extreme = 7 words
        UnHackable = 8 words
    .EXAMPLE
        New-DicewarePassword -Verbose
 
        This assumes a URI has been hard coded into the function and will attempt to pull the .CSV Diceware word list from a web-hosted file
    .EXAMPLE
        New-DicewarePassword -FetchType Web -URI 'https://server01/dicewarewordlist.csv'
    .EXAMPLE
        New-DicewarePassword -FetchType Local -Path C:\dicewarewordlist.csv
    .INPUTS
        System.String
    .OUTPUTS
        System.Management.Automation.PSCustomObject
    .NOTES
        Author: Kevin Kirkpatrick
        Contact: https://github.com/vScripter
        Version: 1.1
        Last Updated: 20170511
        Last Updated By: K. Kirkpatrick
        Last Update Notes:
        - Updated to read from local .JSON file, by default
#>


    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param (
        [Parameter(Position = 0, Mandatory = $false)]
        [ValidateSet('Local', 'Web')]
        [System.String]$FetchType = 'Local',

        [Parameter(Position = 1, ParameterSetName = 'Web')]
        [ValidateScript({ (Invoke-WebRequest -Uri $URI).StatusCode -eq 200 })]
        [System.String]$URI = 'https://dl.dropboxusercontent.com/u/22332521/DicewareWordList.csv',

        [Parameter(Position = 2, ParameterSetName = 'Local')]
        [ValidateScript({ Test-Path -LiteralPath $Path -PathType Leaf })]
        [System.String]$Path = "$PSScriptRoot\Inputs\dicewareWordList.json",

        [Parameter(Position = 3)]
        [ValidateSet('Average', 'High', 'Extreme', 'UnHackable')]
        [System.String]$SecurityLevel = 'Average'
    )

    BEGIN {

        #Requires -Version 3

        if ($FetchList -eq 'Web') {

            Write-Verbose -Message "[New-DicewarePassword] Calling REST 'Get' method from URI {$URI}. I will then import and store the Diceware Word List"
            try {

                $wordList = $null
                $wordList = Invoke-RestMethod -Uri $URI -Method Get -ErrorAction Stop | ConvertFrom-Csv -ErrorAction Stop

            } catch {

                Write-Warning -Message "[New-DicewarePassword][ERROR] Importing and storing Diceware Word List from URI {$URI}. $_ "

            } # end try/catch

        } elseif ($FetchType -eq 'Local') {

            Write-Verbose -Message "[New-DicewarePassword] Importing and storing Diceware Word List from Path {$Path}"
            try {

                $wordList = $null
                $wordList = Get-Content -Path $Path -Raw -ErrorAction 'Stop' | ConvertFrom-Json -ErrorAction 'Stop'

            } catch {

                Write-Warning -Message "[New-DicewarePassword][ERROR] Importing and storing Diceware Word List from Path {$Path}. $_ "

            } # end try/catch

        } # end if/elseif


        Write-Verbose -Message "[New-DicewarePassword] Security level of {$SecurityLevel} has been selected. Translating dice roll count."
        try {

            [void][int]$diceRollCount
            $diceRollCount = switch ($SecurityLevel) {
                Average { 5 }
                High { 6 }
                Extreme { 7 }
                UnHackable { 8 }
            } # end switch

            Write-Verbose -Message "[New-DicewarePassword] Dice will be rolled {$diceRollCount} time/s"

        } catch {

            Write-Warning -Message "[New-DicewarePassword][ERROR] Unable to translate dice roll count. $_ "

        } # end try/catch


    } # end BEGIN block

    PROCESS {

        Write-Verbose -Message "[New-DicewarePassword] Generating a {$diceRollCount} word Diceware Password based on Security Preference {$($SecurityLevel)}."
        try {

            $i = 0
            $result1 = $null
            $result2 = $null

            for (; $i -lt $diceRollCount; $i++) {

                $word     = $null
                $diceRoll = $null

                $diceRoll = Invoke-DiceRoll -ErrorAction Stop
                $word     = ($wordList | Where-Object { $_.Number -eq $diceRoll }).Word

                $result1 += " $word"
                $result2 += $word

            } # end for loop

            [PSCustomObject] @{
                PassWord       = $result2.Trim()
                SpacedPassWord = $result1.Trim()
            }


        } catch {

            Write-Warning -Message "[New-DicewarePassword][ERROR] Could not generate Diceware Password. $_ "


        } # end try/catch

    } # end PROCESS block

    END {

        Write-Verbose -Message "[New-DicewarePassword] Processing Complete"

    } # end END block

} # end function New-DicewarePassword


Export-ModuleMember -Function *