One of the most common tasks out in the field is the need to run PowerShell scripts that require credentials to be saved in some form of another so that they can be fed into scripts to be executed autonomously.  This is particularly common in cloud environments where the current user context in which the scripts are run (e.g. within a scheduled task) are insufficient or not appropriate for the remote execution.   Office 365 management is a perfect example of this, where often a credential object must be passed in order to connect and execute the cmdlets.

The quick and dirty (and all too common) approach is to simply have some code like the following:

Here we are simply writing the username and password directly into the PowerShell script as plaintext and then creating a PowerShell credential object that can then be passed onto things like Connect-MsolService.

It works, but obviously terribly insecure.  Particularly since these accounts are often high powered administrator accounts.

A better approach is to leverage the inherent ‘secure string’ nature of PowerShell credential objects and the way it represents passwords.  Specifically, you can export the ‘secure string’ into a readable string by doing something like the following:

This will give you a nice long, complex looking string like so (shortened for conciseness):  1000000d08c9ddf0115d1…..d3d491bb6d740864122a041d11

You can store that string into a text file, and when needed, read it back in and reverse it back into a ‘Secure String’ object and feed into a credential object creation by doing the following:

What is effectively happening here is that PowerShell is using the native Windows Data Protection API (DAPI) functionality to encrypt the password from the ‘secure string’ into a text string.  This string can be written to a plain text file, but the way that DAPI works is that the encryption is such that only the original user on the original machine the encryption was performed on can decrypt the string back into a ‘Secure string’ to be reused.

While not a 100% foolproof solution, it is a pretty effective way to secure the password as it reduces the attack vectors significantly.  At a minimum as long as the credentials to the account that originally ran the encryption are protected, even if a would be malicious administrator had access to the machine (and thus would have had access to your plaintext passwords stored in your script), they wouldn’t be able to reverse engineer the password that has been stored.

From that perspective your process to have a PowerShell script with a secure ‘saved’ password would be as follows:

  1. Run the Get-Credential command to prompt an administrator to provide the credentials they wish to save
  2. Convert the secure-string object that is part of that credential object into a text string (which is now encrypted) and store that in a file
  3. For scripts that need the saved credentials, read in the file, decrypt the string and recreate the credential object and feed to the appropriate cmdlets.

There a few key caveats with this approach:

  • The script that runs and reads the saved credentials, must be run on the same machine and in the same user context.
  • This means you can’t just copy the ‘saved credential’ file to other machines and reuse it.
  • In the scenario where your scripts are run as scheduled tasks under a service account, be aware that in order to prompt an admin to provide the credentials in the first place, the service account requires ‘Interactive’ ability.  This means the service account, at least temporarily, needs ‘log on locally’ to give you that interactive session.
  • In order for DAPI to work, the GPO setting Network Access: Do not allow storage of passwords and credentials for network authentication must be set to Disabled (or not configured).  Otherwise the encryption key will only last for the lifetime of the user session (i.e. upon user logoff or a machine reboot, the key is lost and it cannot decrypt the secure string text)

Now, assuming the above caveats are no good for you.  Say, for whatever reason, you can’t give your service accounts the ability to log on locally, even temporarily.  Or, the scripts that need these saved passwords need to be run on a bajillion machines and you can’t afford to log onto each one to create a secure string unique for that machine.  There is one alternative, albeit a less secure one, and realistically speaking, only slightly more secure then storing it as plain text.

In the above example, when you are performing the Convert-FromSecureString cmdlet with no parameters, you are effectively telling PowerShell to do the encryption using DAPI. You can however also provide a specific AES Key for it to use to perform the encryption instead.  In the below example, I’m generating a random AES Key to use:

To re-read the password, the following is done:

In that scenario, the AES Key is your secret, and anyone who knows the AES Key can decrypt your password, so it’s not a great idea to simply embed the AES key into your script.   So for the above example, I export the AES Key as a separate text file, that I would then recommend you secure using something like NTFS ACLs.  That way at a minimum, your security barrier is access to that ‘password file’.

Arguably you could have just done that with a plain text password file, but adding the extra layers of encryption/decryption is intended to stop the generic lazy malicious/curious/whatever administrator (or user!) from easily seeing your passwords.

With this, the AES approach to encrypting your password the process now becomes:

  1. Run the Get-Credential command to prompt an administrator to provide the credentials they wish to save
  2. Generate a random AES Key to convert the secure-string object.  Store the AES key and the secure string text as separate files.  Secure these files with NTFS permissions.
  3. For scripts that need the saved credentials, read in both files and recreate the credential object and feed to the appropriate cmdlets.

The main differences with this approach are:

  • The password files can be generated once and subsequently used on any machine by any user, as long as they can read in the files
  • The security barrier here are the NTFS permissions on the password file.

Script Template

Now, if you’ve made it this far, I congratulate you and reward you with this – all that mumbo jumbo that I spoke about above has been packaged together into a nice, easy to use PowerShell script template. This template is structured so that you can simply embed your core ‘worker’ code into the main function, and the template will handle all the rest.  It includes the following handy features:

  • Two ‘modes’:  One to collect and prepare the credential files (i.e. interactively), and another to execute the core worker code (i.e. the mode you run in the scheduled task)
  • Provides the user the option of which encryption method they wish to use, the more secure DPAPI (default) way or the more flexible AES way.
  • Code will determine automatically which method to use for decryption based on what credential files have been created
  • PLUS all those boring error handling and logging functions are included for no extra charge

Enjoy!

Download Link via GitHub

Category:
Office 365, PowerShell, Security
Tags:
,

Join the conversation! 26 Comments

  1. C:\Scripts\Script-Template-WithCreds.ps1
    At C:\Scripts\Script-Template-WithCreds.ps1:55 char:1
    + [CmdletBinding()]
    + ~~~~~~~~~~~~~~~~~
    Unexpected attribute ‘CmdletBinding’.
    At C:\Scripts\Script-Template-WithCreds.ps1:56 char:1
    + Param(([switch]$PrepareCredentials, [switch]$Execution, $param1, $par …
    + ~~~~~
    Unexpected token ‘Param’ in expression or statement.
    At C:\Scripts\Script-Template-WithCreds.ps1:56 char:74
    + … m(([switch]$PrepareCredentials, [switch]$Execution, $param1, $param2)
    + ~
    Missing closing ‘)’ in expression.
    + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : UnexpectedAttribute

    Reply
    • Hi HiltonT,

      Thanks for the heads up! It looks like a rogue ‘(‘ character snuck in 🙂
      I’ve fixed the gist repo now, but if you want to modify your own version, you’ll note that in line 46 of the code it has param(([switch]….. please remove one of the ‘(‘ characters and you’ll be good to go!

      Cheers,
      Dave.

      Reply
  2. Great article. I was happy to see you pointing out that rebooting can cause the encrypted file to not read anymore. Sadly my server has the network access policy set to disabled already. I will post back if I find a solution, I would rather stick with this one if possible.

    Reply
    • Hi Chris,

      Glad you found the post helpful. Unfortunately I couldn’t find a way to work around the DPAPI issue with that GPO setting applied. Many hairs was lost doing so 🙂 It’s actually specifically the reason why I added the ‘less secure’ AES key file approach.

      One other technique that was suggested to me via a colleague was that the AES key approach could be further improved by utilising the private key of a PKI certificate to actually be the source of that AES key. The process would basically involve:

      1) Generate/obtain a certificate from any PKI cert authority
      2) Import the certificate with the private key (for security, do not allow it to be exported) in the computer certificate store
      3) Grant ACLs to the certificate to allow your service account to read the private key of that certificate
      4) Update the scripts to look for that specific certificate and read the private key and use that as the AES key for encrypting the password
      5) This approach effectively secures your password under the cryptographic certficiate store mechanisms within Windows

      Never got around to streamlining this approach to make it practical though, but you’re welcomed to give it a crack 🙂

      Regards,
      Dave.

      Reply
  3. Hi,
    I have a question. I got to create the AES.key and file.txt, but now I would want to execute my script .PS1. How do I do that using this script ??

    Reply
    • Hi Anderson,

      Simply fill in the part of the script that I have commented and use the $credObject as your creoudential parameter. Then when you execute the script using the -Execution parameter – it will trigger that part of the script to read in the credentials and exectute your code accordingly.

      Reply
  4. Hi David,

    Fantastic article. I’ve decided to try portions of the code and during my testing I’ve come across an issue I’m hoping you can help with.
    I’ve successfully exported the secure string to a csv and I import it back in.
    I can verify that the variable contains the string I’ve previously exported, but when I attempt to ConvertTo-SecureString I get the following error:
    ConvertTo-SecureString : Cannot bind argument to parameter ‘String’ because it is null.
    Any suggestions?

    sreck

    Reply
    • Hi Sreck

      The error your described is most commonly due to the variable name being wrong (i.e. typo and hence referring to a non-existent variable, giving a null value) or the parameter is not correctly defined and auto-parameter mapping is not working.

      Which example set of code are you trying? If you show me your sample code, can probably work it out from here 🙂

      Cheers,
      Dave.

      Reply
  5. Hi David,

    Just dropped by to say great article!

    Cheers,

    Nick

    Reply
  6. […] Benutzerkontext ausgeführt werden, in dem später das Script laufen wird (siehe dazu zum Beispiel Using saved credentials securely in PowerShell scripts und Decrypt PowerShell Secure String Password). Vorher natürlich bitte den Ordner ‘Certs’ […]

    Reply
  7. […] Benutzerkontext ausgeführt werden, in dem später das Script laufen wird (siehe dazu zum Beispiel Using saved credentials securely in PowerShell scripts und Decrypt PowerShell Secure String Password). Vorher natürlich bitte den Ordner ‘Certs’ im […]

    Reply
  8. This has helped me get my head around the storing of secure passwords. However when I am trying your sample scripts I get to this part
    $password = $passwordSecureString | ConvertFrom-SecureString -Key $AESKey and get the following error
    “ConvertFrom-SecureString : Cannot bind argument to parameter ‘SecureString’ because it is null.”

    Looking through all the previous parts of the sample I can’t see a reference to $passwordSecureString anywhere. I am just wondering what I am missing or what should be the variable that is entered at this point? I appreciate your assistance.

    Reply
    • Hey Angelo!

      Glad I could help!
      In regards to your question, that’s a bit of sloppy code consistency across my examples there 🙂

      So here’s a more practical example of how you would use my example code:

      # Prompt you to enter the username and password
      $credObject = Get-Credential

      # The credObject now holds the password in a ‘securestring’ format
      $passwordSecureString = $credObject.password

      # Define a location to store the AESKey
      $AESKeyFilePath = “C:\temp\aeskey.txt”
      # Define a location to store the file that hosts the encrypted password
      $credentialFilePath = “C:\temp\credpassword.txt”

      # Generate a random AES Encryption Key.
      $AESKey = New-Object Byte[] 32
      [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($AESKey)

      # Store the AESKey into a file. This file should be protected! (e.g. ACL on the file to allow only select people to read)

      Set-Content $AESKeyFilePath $AESKey # Any existing AES Key file will be overwritten

      $password = $passwordSecureString | ConvertFrom-SecureString -Key $AESKey

      Add-Content $credentialFilePath $password

      Hope that helps!
      Also don’t forget to check out the link at the end of the post – I have a powershell template that has all this wrapped up in an easy to use format so you don’t have to do all the extra coding! 🙂

      Reply
  9. […] make use of the SecureString and the native Windows Data Protection API (DAPI), which is secure. (take a look here) But the downside is that you can decrypt the securestring only at the computer and with the user […]

    Reply
  10. Really great article.. in fact I was looking for the approach to follow to store the credentials and reuse in my windows forms projects I prepare for my team…

    I have got few queries…

    1. I understand how to create/store the credentials using . \MyCredentials.ps1 -PrepareCredentials but reuse part is not clear for me.. for exmaple, if I have a buttton in my form against which the credentalls should be validated once clicked ?

    2. I want to store multiple credentials… is there a way to store multiple credentials instead of getting same place file overwritten ?

    Reply
    • Hi Aamir,

      Glad I could be of assistance! In answer to your questions:

      1. So if you’re referring to my script template, you’ll see under the Execution chunk of the code the following bits of code:

      $credFiles = Get-Content $credentialFilePath
      $userName = $credFiles[0]
      if($decryptMode -eq “DPAPI”)
      {
      $password = $credFiles[1] | ConvertTo-SecureString
      }
      elseif($decryptMode -eq “AES”)
      {
      $password = $credFiles[1] | ConvertTo-SecureString -Key $AESKey
      }
      else
      {
      # Placeholder in case there are other decrypt modes
      }
      $credObject = New-Object System.Management.Automation.PSCredential -ArgumentList $userName, $password

      From here I’m simply reading in the files (after working out which files exist to determine what mode we are using) and using the ConvertTo-SecureString cmdlet to regenerate the password secure string to feed into the creation of the PSCredential object. You can then feed that credential object into any other cmdlets, e.g.
      Connect-MsolService -credential $credObject

      2. In my script template, I assume a single credential. But you could modify it so that instead of overwriting the file, you append. But I guess the trick is for reuse, you’ll need to know which position your stored credentials are – so you’ll need to code in some extra logic to handle that.

      Reply
  11. My only issue with this approach is the fact that you can display the actual password through the $securePwd variable.

    Granted, you have bigger problems if someone with malicious intentions has access to the box and is able to execute the script locally.

    Reply
    • Hi Seal,

      Spot on! This approach is certainly not fool proof – it simply provides a barrier of entry. You’ll notice that I’ve even been a bit lax by having debug code that allows the password to be written out to files as well (terrible practice!) 🙂

      Ultimately the principals of least privilege still need to apply – don’t grant your svc accounts more permissions than necessary, don’t allow unnecessary administrators onto machines that hold scripts, don’t allow access to modify scripts etc.

      Cheers,
      Dave.

      Reply
  12. […] It is strongly recommended that this ‘Service Account’ user in the ‘Invited Azure AD’ has a very strong & complex password, and that any credential used for that account within a PowerShell script be encrypted using David Lee’s blog. […]

    Reply
  13. […] permissions to utilize the Rest API. There are some things to keep in mind when using this method, this post by David Lee does a great job pointing out some of the requirements and caveats to handling credentials in this […]

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: