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?
You have to create the password string on the same computer and with the same login that you will use to run it.
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
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.
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'.
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.
Related
I want to Encrypt a password in Powershell to save it to a file. The ConvertTo-SecureString uses the current credentials to encrypt and decrypt the string.
I want to encrypt it with the local machine key (probably the SYSTEM account credentials) so every username on the same computer will be able to use the password.
I want the string to be undecryptable on other computers.
It's possible to use the [Security.Cryptography.ProtectedData]::Protect function along with [Security.Cryptography.DataProtectionScope]::LocalMachine as the entity.
Code example:
Function Encrypt-WithMachineKey($s) {
Add-Type -AssemblyName System.Security
$bytes = [System.Text.Encoding]::Unicode.GetBytes($s)
$SecureStr = [Security.Cryptography.ProtectedData]::Protect($bytes, $null, [Security.Cryptography.DataProtectionScope]::LocalMachine)
$SecureStrBase64 = [System.Convert]::ToBase64String($SecureStr)
return $SecureStrBase64
}
Function Decrypt-WithMachineKey($s) {
Add-Type -AssemblyName System.Security
$SecureStr = [System.Convert]::FromBase64String($s)
$bytes = [Security.Cryptography.ProtectedData]::Unprotect($SecureStr, $null, [Security.Cryptography.DataProtectionScope]::LocalMachine)
$Password = [System.Text.Encoding]::Unicode.GetString($bytes)
return $Password
}
I am trying to have my password secured and stored in a file so that I don't need to enter each time when I run the script.
First step, I ran the following, entered the password which got stored into E:\cred.txt file. The txt file now contains an encrypted password.
(Get-Credential).Password | ConvertFrom-SecureString | Out-File "E:\cred.txt"
Secondly, I ran the below Script:
$File = "E:\cred.txt"
$User = "jason#domain.com"
#### I have two different user accounts, one for admin and other for operator,
#### however both user accounts use same password.
$adminuser = $User
$operator = $User -replace "#domain.com"
#### I would need to read $File to get only the password
$pass = New-Object -TypeName System.Management.Automation.PSCredential `
-ArgumentList (Get-Content $File | ConvertTo-SecureString)
$adminuser
$operator
$pass
Output:
jason#domain.com
jason
UserName Password
-------- --------
From the output, it seems New-Object refers to both UserName and Password. And when I try to connect to systems, it fails with Authentication error. Since I already have two different usernames hard coded within the script, how should I get only the password stored in $pass? or is it possible to include all usernames ($User, $adminuser, $operator) into the cred.txt file?
Try this:
#saving credentials
Get-Credential | Export-CliXml -Path c:\credential.xml
#importing credentials to a variable
$Credential = Import-CliXml -Path c:\credential.xml
Or this:
#you could then write it to a file or, i say its a better approach to a registry key
$SecurePassword = ConvertTo-SecureString -String 'P#ssw0rd' -AsPlainText -Force | ConvertFrom-SecureString
#now you are taking it back as a secure string
$RegistrySecureString = $SecurePassword | ConvertTo-SecureString
#you can aslo see the password
$UserName = "NULL"
$Credentials = New-Object System.Management.Automation.PSCredential -ArgumentList $UserName, $RegistrySecureString
$Password = $Credentials.GetNetworkCredential().Password
#P#ssw0rd
I'm trying to use WinSCP .NET assembly with secure credential, when the password secured in external file.
# Load WinSCP .NET assembly
Add-Type -Path "D:\WinSCPnet.dll"
# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions
$sessionOptions.Protocol = [WinSCP.Protocol]::Sftp
# Env
$sessionOptions.HostName = "blabla.com"
$sessionOptions.UserName = "UUUU"
#$sessionOptions.Password = "PPPP"
$sessionOptions.SshHostKeyFingerprint = "XXXXXXXXX"
$remotePath = "/home/UUUU/"
With hard coded password its working. If I would like to use securestring for the password, how should I do that?
I tried:
read-host -assecurestring | convertfrom-securestring | out-file D:\securestring.txt
To keep the secure password in a file. Then, in my script, I get it back:
$sessionOptions.Password = get-Content D:\securestring.txt | convertto-securestring
$Cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $sessionOptions.UserName, $sessionOptions.Password}
But it's not working...
Since WinSCP 5.7, the assembly accepts the SecureString using the SessionOptions.SecurePassword.
See https://winscp.net/eng/docs/library_sessionoptions
Thanks for inspiring this improvement.
While the assembly keeps the password encrypted in memory, it still needs to decrypt it eventually. Improvements to limit internal copies of the decrypted password are pending.
As #Matt pointed out, the WinSCP .Net assembly doesn't accept secure strings or credential objects. You need to pass the password as a plaintext string. You can store the secure string in a file, though:
Read-Host 'Enter password' -AsSecureString |
ConvertFrom-SecureString |
Out-File 'C:\password.txt'
and use a PSCredential object to retrieve the decrypted password from the secure string after you read it from the file:
$un = 'username'
$pw = Get-Content 'C:\password.txt' | ConvertTo-SecureString
$cred = New-Object Management.Automation.PSCredential $un, $pw
try {
Add-Type -Path 'WinSCPnet.dll'
$opt = New-Object WinSCP.SessionOptions
$opt.Protocol = [WinSCP.Protocol]::Sftp
$opt.HostName = 'example.org'
$opt.UserName = $cred.UserName
$opt.Password = $cred.GetNetworkCredential().Password
$opt.SshHostKeyFingerprint = 'ssh-rsa 2048 ...'
$sftp = New-Object WinSCP.Session
$sftp.Open($opt)
...
} catch {
...
} finally {
if ($sftp) { $sftp.Dispose() }
}
WinSCP sample code taken from the documentation.
Note, however, that the password must be saved by the same user who is running the SFTP PowerShell script, because the encryption key is derived from that user's password.
According to WinSCP the password property just supports a string. So trying to pass a secure string will not work. If you really want to store the password in a file, you could attempt to store it as the secure string but that is a really bad idea in general since it can be unsecured just as easily (Also not sure if it is possible). I recommend the following option.
# Only stored in memory which is safer.
$sessionOptions.Password = read-host
If you have your heart set on something else you could try this. Just know for previous reasons I do not condone this. Also i have to see if it even works because it looks like you cant output securestring to file.
read-host | out-file D:\securestring.txt
$sessionOptions.Password = get-Content D:\securestring.txt
Ansgar's explains what I didn't know was possible. You can stored the secure string in a file and use it elsewhere.
I'm looking to write a script that will change the administrator password on a remote server using PowerShell. The following command will do this
$Admin=[adsi]("WinNT://" + MyServer + "/Administrator, user")
$Admin.SetPassword("NewPassword")
But I would like to be able to hide the "NewPassword" in the script to make it more secure.
So is there a way to save the "NewPassword" to a secure .txt file then be able to use it like this?
$Admin.SetPassword("$secureFile")
The script will be run as a scheduled task.
Yes, you can use the ConvertTo-SecureString and ConvertFrom-SecureString cmdlets to encrypt the password before saving it to a file on disk.
However, keep in mind that you'll need an encryption key in order to encrypt/decrypt the password using the cmdlets. From the documentation:
If an encryption key is specified by using the Key or SecureKey
parameters, the Advanced Encryption Standard (AES) encryption
algorithm is used. The specified key must have a length of 128, 192,
or 256 bits because those are the key lengths supported by the AES
encryption algorithm.
If you don't specify a key, the Windows Data Protection API (DPAPI) will be used for the encryption. This means that the key will be tied to the user account who invoked the cmdlets. Now, if you're running the script as a scheduled job this solution will work just fine.
Here's a couple of scripts that will save and read an encrypted password to an XML file on disk using a generated key:
function Get-SecurePassword {
<#
.Synopsis
Gets a password stored securely in an XML file.
.Parameter Path
The path to the XML file to import the password from.
#>
[CmdletBinding()]
param(
[Parameter(Position=1)]
[string]$Path = "Password.xml"
)
if (Test-Path $Path) {
$cache = Import-Clixml $Path
$key = [System.Convert]::FromBase64String($cache.Secret)
$password = $cache.EncryptedPassword | ConvertTo-SecureString -Key $key
$password
}
}
function Set-SecurePassword {
<#
.Synopsis
Stores a password securely in an XML file.
.Parameter Path
The path to the XML file to export the password to.
#>
[CmdletBinding()]
param(
[Parameter(Position=1)]
[string]$Password,
[Parameter(Position=2)]
[string]$Path = "Password.xml"
)
$key = New-StrongPasswordBytes -Length 32
$textualKey = [System.Convert]::ToBase64String($key)
$securePassword = $Password | ConvertFrom-SecureString -Key $key
$cache = New-Object PSObject -Property #{ "EncryptedPassword" = $securePassword; "Secret" = $textualKey }
$cache.PSObject.TypeNames.Insert(0, "SecurePassword")
$cache | Export-Clixml $Path
}
function New-StrongPasswordBytes ($length) {
Add-Type -Assembly System.Web
$password = [System.Web.Security.Membership]::GeneratePassword($length, $length / 2)
[System.Text.Encoding]::UTF8.GetBytes($password)
}
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?
You have to create the password string on the same computer and with the same login that you will use to run it.
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
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.
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'.
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.