Private/New-PBKDF2Key.ps1
# 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 ) $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 } |