36

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?

3
  • 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? Commented Aug 18, 2011 at 15:35
  • 1
    found this blog post which was also very helpful powertoe.wordpress.com/2011/06/05/… Commented Aug 18, 2011 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. Commented Aug 18, 2011 at 19:57

5 Answers 5

39

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

Sign up to request clarification or add additional context in comments.

1 Comment

"...and with the same login." That was the important part for me.
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

2 Comments

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?
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.
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.

1 Comment

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!!
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'.

1 Comment

Since the original question uses PowerShell, could you convert this answer from C# to PowerShell as well?
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.