/
New-PBKDF2Key.ps1
162 lines (140 loc) · 6.57 KB
/
New-PBKDF2Key.ps1
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
# Copyright: (c) 2018, Jordan Borean (@jborean93) <jborean93@gmail.com>
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
Function New-PBKDF2Key {
<#
.SYNOPSIS
Uses the PBKDF2 functiion to derive a key used in cryptographic functions.
.DESCRIPTION
This function can be used to generated a cryptographically secure key based
on the PBKDF2 function. This function calls some native Win32 APIs as the
.NET class Rfc2898DeriveBytes that generates these keys does not allow the
hash algorithm to be changed until .NET 4.7.2.
Because we want this script to run on older versions on Windows and Ansible
Vault uses the SHA256 algorithm we have to resort to using the native
function BCryptDeriveKeyPBKDF2.
.PARAMETER Algorithm
[String] Specifies the algorithm to use for the HMAC calculation. This must
be one of the algorithm identifiers specified in
https://msdn.microsoft.com/en-us/library/windows/desktop/aa375534.aspx.
.PARAMETER Password
[SecureString] The password used as the part of the PBKDF2 function.
.PARAMETER Salt
[byte[]] The salt used as part of the PBKDF2 function.
.PARAMETER Length
[UInt32] The length of the derived key.
.PARAMETER Iterations
[UInt64] The number of iterations for the PBKDF2 function.
.OUTPUTS
[byte[]] The derived key of the PBKDF2 function run.
.EXAMPLE
$salt = New-Object -TypeName byte[] -ArgumentList 32
$random_gen = New-Object -TypeName System.Security.Cryptography.RNGCryptoServiceProvider
$random_gen.GetBytes($salt)
New-PBKDF2Key -Algorithm SHA256 -Password $sec_string -Salt $salt -Length 32 -Iterations 10000
.NOTES
As Windows has not automatic marshalling for a SecureString to a P/Invoke
call, the SecureString is temporarily assigned to a IntPtr before being
passed to the BCryptDeriveKeyPBKDF2 with the SecureStringToGlobalAllocAnsi
function. This pointer is immediately cleared withZeroFreeGlobalAllocAnsi
as soon as possible.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Justification="Does not adjust system state, creates a new key that is in memory")]
[CmdletBinding()]
[OutputType([byte[]])]
param(
[Parameter(Mandatory=$true)] [String]$Algorithm,
[Parameter(Mandatory=$true)] [SecureString]$Password,
[Parameter(Mandatory=$true)] [byte[]]$Salt,
[Parameter(Mandatory=$true)] [UInt32]$Length,
[Parameter(Mandatory=$true)] [UInt64]$Iterations
)
# Rfc2898DeriveBytes only allowed a custom hash algorithm in 4.6 or newer. We check to see whether the enum is
# available and fallback to PInvoking.
try {
$null = [System.Security.Cryptography.HashAlgorithmName]
$use_dotnet = $true
} catch [System.Management.Automation.RuntimeException] {
$use_dotnet = $false
}
if ($use_dotnet) {
$algo = [System.Security.Cryptography.HashAlgorithmName]$Algorithm
$pass_ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($Password)
try {
$pass_str = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($pass_ptr)
$provider = New-Object -TypeName System.Security.Cryptography.Rfc2898DeriveBytes -ArgumentList @(
$pass_str,
$Salt,
$Iterations,
$algo
)
try {
return $provider.GetBytes($Length)
} finally {
$provider.Dispose()
}
} finally {
[System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($pass_ptr)
}
}
# Rfc2898DeriveBytes not available on older platforms, rely on PInvoke for this step.
$return_codes = @{
"3221225485" = "An invalid parameter was passed to a service or function (STATUS_INVALID_PARAMETER 0xC0000000D)"
"3221225480" = "An invalid HANDLE was specified (STATUS_INVALID_HANDLE 0xC0000008)"
"3221225495" = "A memory allocation failure occurred (STATUS_NO_MEMORY 0xC0000017)"
"3221226021" = "The object was not found (STATUS_NOT_FOUND 0xC0000225)"
}
$algo = [IntPtr]::Zero
$open_flags = 0x00000008 # BCRYPT_ALG_HANDLE_HMAC_FLAG
$res = Invoke-Win32Api -DllName Bcrypt.dll `
-MethodName BCryptOpenAlgorithmProvider `
-ReturnType UInt32 `
-ParameterTypes @([Ref], [String], [String], [UInt32]) `
-Parameters @([Ref]$algo, $Algorithm, $null, $open_flags)
if ($res -ne 0) {
if ($return_codes.ContainsKey($res.ToString())) {
$exception_msg = $return_codes.$($res.ToString())
} else {
$hex_code = ("{0:x8}" -f $res).ToUpper()
$exception_msg = "Unknown error (0x$hex_code)"
}
throw "Failed to open algorithm provider with ID '$Algorithm': $exception_msg"
}
try {
$key = New-Object -TypeName byte[] -ArgumentList $Length
$pass = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocAnsi($Password)
try {
$res = Invoke-Win32Api -DllName Bcrypt.dll `
-MethodName BCryptDeriveKeyPBKDF2 `
-ReturnType UInt32 `
-ParameterTypes @([IntPtr], [IntPtr], [UInt32], [byte[]], [UInt32], [UInt64], [byte[]], [UInt32], [UInt32]) `
-Parameters @($algo, $pass, $Password.Length, $Salt, $Salt.Length, $Iterations, $key, $key.Length, 0)
} finally {
[System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocAnsi($pass)
}
if ($res -ne 0) {
if ($return_codes.ContainsKey($res.ToString())) {
$exception_msg = $return_codes.$($res.ToString())
} else {
$hex_code = ("{0:x8}" -f $res).ToUpper()
$exception_msg = "Unknown error (0x$hex_code)"
}
throw "Failed to derive key: $exception_msg"
}
} finally {
$res = Invoke-Win32Api -DllName Bcrypt.dll `
-MethodName BCryptCloseAlgorithmProvider `
-ReturnType UInt32 `
-ParameterTypes @([IntPtr], [UInt32]) `
-Parameters @($algo, 0)
if ($res -ne 0) {
if ($return_codes.ContainsKey($res.ToString())) {
$exception_msg = $return_codes.$($res.ToString())
} else {
$hex_code = ("{0:x8}" -f $res).ToUpper()
$exception_msg = "Unknown error (0x$hex_code)"
}
throw "Failed to close algorithm provider: $exception_msg"
}
}
return [byte[]]$key
}