34

I was doing something like described in this post to save credentials in a secured file so our automated process can use that to run remote PS scripts via Invoke-command: http://blogs.technet.com/b/robcost/archive/2008/05/01/powershell-tip-storing-and-using-password-credentials.aspx

This works great when I run this under my account - password is read from encrypted file, passed to Invoke-command and everything is fine.

Today, when my script was ready for its prime time, I tried to run it under windows account that will be used by automated process and got this error below while my script was trying to read secured password from a file:

ConvertTo-SecureString : Key not valid for use in specified state.
At \\remoted\script.ps1:210 char:87
+ $password = get-content $PathToFolderWithCredentials\pass.txt | convertto-sec
urestring <<<<
    + CategoryInfo          : InvalidArgument: (:) [ConvertTo-SecureString], C
   ryptographicException
    + FullyQualifiedErrorId : ImportSecureString_InvalidArgument_Cryptographic
   Error,Microsoft.PowerShell.Commands.ConvertToSecureStringCommand

Asked my workmate to run under his account and he got the same error.

This is the code I am using to save credentials:

$PathToFolderWithCredentials = "\\path\removed"

write-host "Enter login as domain\login:"
read-host | out-file $PathToFolderWithCredentials\login.txt

write-host "Enter password:"
read-host -assecurestring | convertfrom-securestring | out-file $PathToFolderWithCredentials\pass.txt

write-host "*** Credentials have been saved to $pathtofolder ***"

This is the code in the script to run by automated process to read them to use in Invoke-command:

$login= get-content $PathToFolderWithCredentials\login.txt
$password = get-content $PathToFolderWithCredentials\pass.txt | convertto-securestring
$credentials = new-object -typename System.Management.Automation.PSCredential -argumentlist $login,$password

Error happens on line $password = get-content $PathToFolderWithCredentials\pass.txt | convertto-securestring

Any ideas?

Lars Truijens
  • 42,837
  • 6
  • 126
  • 143
mishkin
  • 5,932
  • 8
  • 45
  • 64
  • 2
    I am reading that: "ConvertFrom-SecureString cmdlet encrypts this data using the Windows standard Data Protection API. This ensures that only your user account can properly decrypt its contents". That's why it does not work...Any ideas what would be the best way to save the password then that can be decrypted by another windows account? – mishkin Aug 18 '11 at 15:35
  • 1
    found this blog post which was also very helpful http://powertoe.wordpress.com/2011/06/05/storing-passwords-to-disk-in-powershell-with-machine-based-encryption/ – mishkin Aug 18 '11 at 17:43
  • 2
    And I would also recommend that you read "Powershell Cookbook" - goob book for both beginners and advanced users. It covers this and lots of other things. – manojlds Aug 18 '11 at 19:57

5 Answers5

37

You have to create the password string on the same computer and with the same login that you will use to run it.

Michele
  • 3,617
  • 12
  • 47
  • 81
33

ConvertFrom-SecureString takes a Key ( and SecureKey) parameter. You can specify the key to save the encrypted standard string and then use the key again in ConvertTo-SecureString to get back the secure string, irrespective of the user account.

http://technet.microsoft.com/en-us/library/dd315356.aspx

In a project, I have implemented asymmetric encryption, whereby people encrypt the password using the public key and the automation process has the private key to decrypt passwords: Handling passwords in production config for automated deployment

mklement0
  • 382,024
  • 64
  • 607
  • 775
manojlds
  • 290,304
  • 63
  • 469
  • 417
  • 1
    thank you, it works! you saved me again! :) just a question (probably dumb) but if I put a key in my script, won't it be the same thing as just typing password there? – mishkin Aug 18 '11 at 16:40
  • 1
    I haven't use the key method much, but general idea is you have it in a particular location ( key store) on machine where you run the scripts, the script reads from there and decrypts. In the assymetric version, the public key is known, so people can encrypt the password, but only those who have the private key can decrypt. The private key is on the machine that runs the scripts. The script only knows where to look for it. Hope this makes sense. – manojlds Aug 18 '11 at 16:51
3

The below will allow credentials to be saved as a file, then those credentials to be used by another script being run by a different user, remotely.

The code was taken from a great article produced by David Lee, with only some minor adjustments from myself https://blog.kloud.com.au/2016/04/21/using-saved-credentials-securely-in-powershell-scripts/

First step is to save a a secure password to a file using AES. The below will run as a stand alone script:

            # 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 = “aeskey.txt”
            # Define a location to store the file that hosts the encrypted password
            $credentialFilePath = “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

Then in your script where you need to use credentials use the following:

            #set up path and user variables
            $AESKeyFilePath = “aeskey.txt” # location of the AESKey                
            $SecurePwdFilePath = “credpassword.txt” # location of the file that hosts the encrypted password                
            $userUPN = "domain\userName" # User account login 

            #use key and password to create local secure password
            $AESKey = Get-Content -Path $AESKeyFilePath 
            $pwdTxt = Get-Content -Path $SecurePwdFilePath
            $securePass = $pwdTxt | ConvertTo-SecureString -Key $AESKey

            #crete a new psCredential object with required username and password
            $adminCreds = New-Object System.Management.Automation.PSCredential($userUPN, $securePass)

            #use the $adminCreds for some task
            some-Task-that-needs-credentials -Credential $adminCreds

Please be aware that if the user can get access to the password file and the key file, they can decrypt the password for the user.

Chris Horan
  • 131
  • 1
  • 2
  • For those using `New-Object System.Net.NetworkCredential($userUPN, $securePass)` just replace that with `New-Object System.Management.Automation.PSCredential($userUPN, $securePass)` and it should work the same assuming you create the files and follow the steps of the scripted logic. Seems to work just fine for me and exactly what I was looking for rather than needing to define the credential with `Get-Credential` on each machine or profile it is run without using this method. Thanks Chris!! – Bitcoin Murderous Maniac Sep 13 '19 at 19:53
1

Another approach would be to protect the data using scope 'LocalMachine' instead of 'CurrentUser' which is the one used by ConvertFrom-SecureString.

public static string Protect(SecureString input, DataProtectionScope dataProtectionScope = DataProtectionScope.CurrentUser, byte[] optionalEntropy = null)
{
    byte[] data = SecureStringToByteArray(input);
    byte[] data2 = ProtectedData.Protect(data, optionalEntropy, dataProtectionScope);
    for (int i = 0; i < data.Length; i++)
    {
        data[i] = 0;
    }

    return ByteArrayToString(data2);
}
private static byte[] SecureStringToByteArray(SecureString s)
{
    var array = new byte[s.Length * 2];
    if (s.Length > 0)
    {
        IntPtr intPtr = Marshal.SecureStringToGlobalAllocUnicode(s);
        try
        {
            Marshal.Copy(intPtr, array, 0, array.Length);
        }
        finally
        {
            Marshal.FreeHGlobal(intPtr);
        }
    }

    return array;
}
private static string ByteArrayToString(byte[] data)
{
    var stringBuilder = new StringBuilder();
    for (int i = 0; i < data.Length; i++)
    {
        stringBuilder.Append(data[i].ToString("x2", CultureInfo.InvariantCulture));
    }

    return stringBuilder.ToString();
}

The encrypted string can be used by ConvertTo-SecureString which is using scope 'CurrentUser'.

wangzq
  • 896
  • 8
  • 17
  • 4
    Since the original question uses `PowerShell`, could you convert this answer from `C#` to `PowerShell` as well? – oɔɯǝɹ Oct 16 '14 at 21:18
0

Assuming you have a known list of N users who will use the credentials (e.g. one developer userMe and a system/service user userSys) you can just (get those users to) make N copies of the pass.txt file: one for each user.

So the password of userX will result in e.g. 2 *.pass.txt files:

  • userX.userMe.pass.txt
  • userX.userSys.pass.txt

When userMe wants the creds he/she reads userX.userMe.pass.txt etc.

Marc
  • 13,011
  • 11
  • 78
  • 98