PowerShell: Storing Credentials Securely
Recently I’ve been working on several PowerShell scripts that require credentials to access REST APIs. In this blog post, I will showcase two approaches for storing credentials securely for use in PowerShell scripts.
Encrypted Password File 🔒
The encrypted password file leverages the Windows Data Protection API (DPAPI) to encrypt the password as a System.Security.SecureString:
1$Credentials = Get-Credential
2$Credentials.Password
3System.Security.SecureString
4$Credentials.Password | ConvertFrom-SecureString
501000000d08c9ddf0115d1118c7a00c04fc297eb01000000f5ab85d7ee9da048ae4ae797ee7eaf0a000000000200000000001066000000010000200000008c4a03d2f0731e0e7661d695fda8b441eaff31e75724931f31374a0c8292b636000000000e800000000200002000000028da885828bd627480178382ce9a1b477819e7703546ce41819d37f4e63d33ba20000000ab2c4401635ec24db9f20071e18dea0b79ce16ba38b5503ec9937b7fbc849dcf40000000155053a793c210998ef7317b0161e7344c2174b904b527c0cf24e7bbf2243b99e936df3ab67bc9e285a1be33aed37c7604fb07f5d0c44ceb7d6334ca30b0a610
By default DPAPI uses the current user context to generate an encryption key. This encryption key is then used to encrypt the PSCredential.Password property as a System.Security.SecureString (as shown above). It is possible to provide your own encryption key, but I won’t be covering that in this post. If you want to read more on this, check out Travis Gan’s blog 1.
It’s also worth noting there are several caveats to this approach:
- You must encrypt the password file as the user which will be accessing it.
- DPAPI is specific to the device which you encrypt the password file on. You cannot decrypt the password file on another system with the same user.
Generating and using the Encrypted Password File
To store the password securely in a file, we can use the Export-Clixml cmdlet which will store the System.Management.Automation.PSCredential object in XML format:
1$Credentials = Get-Credential
2$Credentials | Export-Clixml -Path "$(pwd)\EncryptedCreds.xml"
Once created, the EncryptedCreds.xml file will contain the XML representation of the PSCredential object. As shown below, the Password property is stored as a SecureString:
1<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
2 <Obj RefId="0">
3 <TN RefId="0">
4 <T>System.Management.Automation.PSCredential</T>
5 <T>System.Object</T>
6 </TN>
7 <ToString>System.Management.Automation.PSCredential</ToString>
8 <Props>
9 <S N="UserName">User</S>
10 <SS N="Password">01000000d08c9ddf0115d1118c7a00c04fc297eb01000000f5ab85d7ee9da048ae4ae797ee7eaf0a000000000200000000001066000000010000200000008c4a03d2f0731e0e7661d695fda8b441eaff31e75724931f31374a0c8292b636000000000e800000000200002000000028da885828bd627480178382ce9a1b477819e7703546ce41819d37f4e63d33ba20000000ab2c4401635ec24db9f20071e18dea0b79ce16ba38b5503ec9937b7fbc849dcf40000000155053a793c210998ef7317b0161e7344c2174b904b527c0cf24e7bbf2243b99e936df3ab67bc9e285a1be33aed37c7604fb07f5d0c44ceb7d6334ca30b0a610</SS>
11 </Props>
12 </Obj>
13</Objs>
We can then re-create the PSCredential object for use in PowerShell scripts using the Import-Clixml cmdlet:
1$Credentials = Import-Clixml -Path "$(pwd)\EncryptedCreds.xml"
2$Credentials
3
4UserName Password
5-------- --------
6User System.Security.SecureString
7
8$Credentials.GetType()
9
10IsPublic IsSerial Name BaseType
11-------- -------- ---- --------
12True True PSCredential System.Object
13
14Write-Output -InputObject "Username: ""$($Credential.UserName)"" - Password: ""$($Credential.GetNetworkCredential().Password)"""
15Username: "User" - Password: "Password123!"
Tada! ✨
This approach can be good for one off scripts but, can quickly become a maintainance burden when you’re doing this for many scripts. For this type of use-case, the next approach is probably more suitable.
SecretManagement & SecretStore Modules 🔐
The SecretManagement and SecretStore modules are a great choice for storing credentials securely. To begin, install the modules:
1Install-Module -Name "Microsoft.PowerShell.SecretManagement", "Microsoft.PowerShell.SecretStore" -Verbose
Creating a SecretVault
Next, we need to create a SecretVault to store secrets using the Register-SecretVault cmdlet 🔐:
1Register-SecretVault -Name "MySecretVault" -ModuleName "Microsoft.PowerShell.SecretStore" -DefaultVault -Description "Secret vault storing my secrets." -PassThru
2
3Name ModuleName IsDefaultVault
4---- ---------- --------------
5MySecretVault Microsoft.PowerShell.SecretStore True
Storing Secrets
Nice! 👍 Now lets store a PSCredential in the SecretVault with the Set-Secret cmdlet. When creating the first secret you will be prompted for a password which will be used to encrypt the SecretVault:
ℹ You can store a
SecureString,HashTable,StringorByte[]in a secret as well!
1$RestAPICreds = Get-Credential
2Set-Secret -Name "REST API Creds" -Vault "MySecretVault" -Secret $RestAPICreds
3
4Creating a new MySecretVault vault. A password is required by the current store configuration.
5Enter password:
6********
7Enter password again for verification:
8********
9
10# Bonus! Store metadata alongside the secret itself!
11$Metadata = @{ SecretExpiration = ([DateTime]::New(2022, 12, 22)) }
12Set-Secret -Name "REST API Creds" -Vault "MySecretVault" -Secret $RestAPICreds -Metadata $Metadata
Retrieving Secrets
Now that the secret is stored securely, we can retrieve it in another script using the Get-Secret cmdlet:
1$RestAPICredentialsSecret = Get-Secret -Name "REST API Credentials" -Vault "MySecretVault"
2$RestAPICredentialsSecret
3
4UserName Password
5-------- --------
6test System.Security.SecureString
7
8# Bonus! Get information about a secret too!
9# https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.secretmanagement/get-secretinfo?view=ps-modules
10Get-SecretInfo -Name "REST API Credentials" -Vault "MySecretVault"
11
12Name Type VaultName
13---- ---- ---------
14REST API Credentials PSCredential MySecretVault
15
16# As it's a PSCredential object, we can use the GetNetworkCredential method to finally get the password!
17$RestAPICredentialsSecret.GetNetworkCredential().Password
Awesome! 😎 We’ve stored the first secret securely! ✨🚀
However, if you use the Get-Secret cmdlet in a new session, or after a certain amount of time has elapsed, you may notice that you’re prompted for the password to decrypt the SecretVault again! 😱
1Get-Secret -Name "REST API Credentials" -Vault "MySecretVault"
2Vault MySecretVault requires a password.
3Enter password:
Obviously this is an issue if we want our scripts to work without any interaction!
Retrieving Secrets Non-Interactively
To fix this we can leverage the first approach and store our SecretVault decryption password in an encrypted password file and retrieve it later! So… lets do it! 😄
1$SecretVaultPassword = Get-Credential
2$SecretVaultPassword | Export-Clixml -Path "$(pwd)\SecretVaultPassword.xml"
3Get-Content -Path "$(pwd)\SecretVaultPassword.xml"
4
5...
6<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
7 <Props>
8 ...
9 <SS N="Password">700061007300730077006f0072006400</SS>
10 </Props>
11 </Obj>
Once done, we can use the Unlock-SecretStore cmdlet in our scripts to unlock the SecretVault non-interactively:
1$SecretVaultPassword = (Import-Clixml -Path "$(pwd)\SecretVaultPassword.xml").Password
2Unlock-SecretStore -Password $SecretVaultPassword
Automation FTW! ⚙ 💪
Modifying SecretVault Configuration
Finally, you may also want to modify the configuration of a SecretVault. You can do this using the SecretStore cmdlets Get-SecretStoreConfiguration and Set-SecretStoreConfiguration.
Let’s check the SecretVault configuration with the Get-SecretStoreConfiguration cmdlet:
1Get-SecretStoreConfiguration
2
3 Scope Authentication PasswordTimeout Interaction
4 ----- -------------- --------------- -----------
5CurrentUser Password 900 Prompt
We can see that the SecretVault configuration is set to prompt for a password, and the timeout before being prompted for the password again is 900 seconds (15 minutes) by default.
The timeout can be changed using the Set-SecretStoreConfiguration cmdlet:
1Set-SecretStoreConfiguration -PasswordTimeout 1800 -Confirm:$false
2# Check the configuration again
3Get-SecretStoreConfiguration
4
5 Scope Authentication PasswordTimeout Interaction
6 ----- -------------- --------------- -----------
7CurrentUser Password 1800 Prompt
Conclusion
Finished! 😄 Hopefully you learned something new and found this post helpful! :slight_smile: There is a lot that I didn’t cover when it comes to the SecretManagement module. For example, extensions which create integrations with third-party secret management products like Azure KeyVault, KeePass, HashiCorp Vault and more!
Until the next one! 👋