AES encrypted credentials for Powershell scripts (and a bit about DPAPI)

I recently went through the process of learning about how you could encrypt and store passwords for user accounts in a way that they can be easily used in a Powershell script.  While researching how to do this, I found a lot of information on how to encrypt the password using Microsoft’s Data Protection Application Programming Interface (DPAPI) encryption.  However, I didn’t find very many write-ups on how to do the same thing using AES encryption.  So I’m going to share what I found and how it can be implemented.

AES? DPAPI? Why might I choose one over the other?

Before getting to the details for implementation, I want to mention some reasons why you may (or may not) want to use AES instead of DPAPI.  Both DPAPI and AES have native support in Powershell, so they can both be used relatively easily.

Data that is encrypted using DPAPI can only be decrypted on the same Host that encrypted the data (and it might even need to be the same user profile…I read a couple of things that mentioned that, but haven’t dug deeper to find out if that’s accurate or tested it).  That means that the data that’s encrypted using DPAPI isn’t portable – it can’t be decrypted on other computers (excluding potential attacks against the encryption).  Some people might find DPAPI to be appealing because its lack of portability keeps the data more secure from a Confidentiality perspective.

Data that is encrypted using AES, on the other hand, can be decrypted by any computer that has the AES key that was used to encrypt the data.  That means that the data that’s encrypted using AES is portable.  If the data needs to be migrated to another host for any reason (normal systems migrations, disaster recovery, etc), it will continue to be accessible as long as you have access to the AES key that was used.  Some people might find the portability of AES to be appealing because it keeps the encrypted data more secure from an Availability perspective.

Ultimately, whether you choose to use DPAPI or AES is a decision that each person or organization needs to make, and you need to weigh the pros and cons of each option.  In either case, if any variety of encryption is implemented without an appropriate amount of consideration, it can result in bad situations like an inability to access data that you need (effectively, a self inflicted Denial of Service attack) or the data not being as confidential as you thought because of poor access controls on the keys.

Now that we’ve covered that part, let’s move on to how you can use Powershell to (1) generate and store a 256-bit AES key, (2) encrypt the password for a User Account using that AES key, and (3) use that AES encrypted password in a script (to authenticate with a mail server, in this case).

Preparing your environment to use AES encrypted passwords

Use the Powershell below to get your environment prepared.  Before executing these steps, you will need to have: (1) a secure location to store your key, (2) a secure location to store your encrypted password, (3) the password for the User account that you need to use in your script.

#Prep Step 1 - Use Powershell to Generate a 256-Bit Key and store it in a given path
#            - For this step, you need to identify a secure location to store your Key.  It is critical that you limit access to this location using ACLs.
$KeyStoragePath = "c:\YourKeyStorage   OR   \\FileServer01\KeyStorageShare"
$KeyFileName = "Username@YourDomainDotCom.AES.Key"
$CreateKey = New-Object Byte[] 32
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($CreateKey)
$CreateKey | out-file "$KeyStoragePath\$KeyFileName"

#Prep Step 2 - Capture the password for your User Account as a Secure String, AES Encrypt it using the Key generated in Step 1, and save it to a file.
#            - For this step, you need to identify a secure location to store your encrypted Password.  Although this password is encrypted, you should still limit access to this location using ACLs.
#            - Complete this step immediately after Prep Step 1.  It relies on variables that were defined in Prep Step 1.
#            - After the 4th line below is executed, you will need to type the password for your user account (Username@YourDomainDotCom) at Powershell's Read-Host prompt.
$GetKey = Get-Content "$KeyStoragePath\$KeyFileName"
$CredentialsStoragePath = "C:\YourEncryptedCredentialsStorage   OR   \\FileServer02\CredentialsStorageShare"
$CredentialsFileName = "Username@YourDomainDotCom.securestring"
$PasswordSecureString = Read-Host -AsSecureString
$PasswordSecureString | ConvertFrom-SecureString -key $GetKey | Out-File -FilePath "$CredentialsStoragePath\$CredentialsFileName"

 

The screenshot below shows Prep Step 1.  A 256-Bit (32-Byte) key is generated using a .NET Random Number Generator.  For demonstration purposes, the contents of the key file is displayed.

Prep Step 1

The screenshot below shows Prep Step 2.  The password for “Username@YourDomainDotCom” is typed into the Powershell “Read-Host -AsSecureString” prompt, and then the password is encrypted and saved to a file.  For demonstration purposes, the content of the encrypted password file is displayed.

Prep Step 2

After you complete those two Prep Steps, you will have your Key and Encrypted Password saved to files, and you will be able to use them when you execute other Powershell scripts in the future.

Using your AES Encrypted password in a script

The script below demonstrates how you can use your AES Encrypted password in a script.  In this example, the password is being used to authenticate with a Mail Server.

#Use your AES Encrypted password file to authenticate with a Mail Server. Define mail server and user, decrypt the encrypted Credentials file, using the Key File, and load it into PSCredential so it can be passed to Send-MailMessage, compose email, and send.
#Define Mail Server Details
$PSEmailServer = "Mail.YourDomainDotCom"
$SMTPPort = 587
$SMTPUsername = "Username"
#Define Key File Details
$KeyStoragePath = "c:\YourKeyStorage\"
$KeyFileName = "Username@YourDomainDotCom.AES.Key"
$GetKey = Get-Content "$KeyStoragePath\$KeyFileName"
#Define Encrypted Password File Detaisl
$CredentialsStoragePath = "C:\YourEncryptedCredentialsStorage"
$CredentialsFileName = "Username@YourDomainDotCom.securestring"
$EncryptedPasswordFile = "$CredentialsStoragePath\$CredentialsFileName"
#Use the Key to decrypt the password and load it into memory as a SecureString
$SecureStringPassword = Get-Content -Path $EncryptedPasswordFile | ConvertTo-SecureString -Key $GetKey
$EmailCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $SMTPUsername,$SecureStringPassword
#Define Email Message Options
$MailTo = "RecipientAddress@DomainDotCom"
$MailFrom = "Username@YourDomainDotCom"
$MailSubject = "Hello world"
$MailBody = "Here's the a test email that was sent using Powershell Send-MailMessage to send an email.  The Powershell Script authenticated with the sending mail server using credentials that were stored in an encrypted file and decrypted on the fly during script execution to be passed as a System.Management.Automation.PSCredential."
#Send Email
Send-MailMessage -From $MailFrom -To $MailTo -Subject $MailSubject -Body $MailBody -Port $SMTPPort -Credential $EmailCredential -UseSsl

 

The screenshot below shows the demonstration script excluding the “Define Email Message Options” and “Send Email” portions at the end of the script.  For demonstration purposes, it also shows what the content of the $EmailCredentials variable, a PSCredential object, looks like.  Because the password was loaded as a SecureString, the Password is displayed as “System.Security.SecureString” instead of the actual password being displayed.

Prep Step 3

I don’t show the email portion in the demonstration screenshots because I’m not using a real Mail Server and User Account for this.  But it all works as long as all of the options that you define for the Send-MailMessage command are valid for the Mail Server that you use.

Decrypting a password file to reveal the plaintext password

As a final note on this post – I mentioned earlier that controlling access to your Key is critical, because anyone that has access to the key can decrypt the data that was secured with it.  In the situation that I’m demonstrating, since the key is being used to encrypt the password for an account, if an attacker can get their hands on the Key and the encrypted password file, then they may be able to use the User Account for accessing other services on the network (in addition to authenticating with the Mail Server, as the scripts demonstrate).

So, you got your hands on the key and password file, and you want to decrypt it to recover the plaintext password?  This will do it (re-using some variables that were used in previous scripts above)

$SMTPUsername = "Username"
$KeyStoragePath = "c:\YourKeyStorage\"
$KeyFileName = "Username@YourDomainDotCom.AES.Key"
$GetKey = Get-Content "$KeyStoragePath\$KeyFileName"
$CredentialsStoragePath = "C:\YourEncryptedCredentialsStorage"
$CredentialsFileName = "Username@YourDomainDotCom.securestring"
$EncryptedPasswordFile = "$CredentialsStoragePath\$CredentialsFileName"
$SecureStringPassword = Get-Content -Path $EncryptedPasswordFile | ConvertTo-SecureString -Key $GetKey
$EmailCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $SMTPUsername,$SecureStringPassword
$EmailCredential.GetNetworkCredential().Password

 

The screenshot below shows that Powershell code being used to expose the plaintext password we encrypted earlier.  The password is “ThisIsMyP@ssw0rdfjaldskf;jNENROIEFDnlndlfw392fdkjslfjo3fdkNLFDSNFKJLSo32eo9#(*$)#$#(*%&NFJEI#fdklew”

Prep Step 4

AES encrypted credentials for Powershell scripts (and a bit about DPAPI)