Protecting credentials used for automation
When writing a script file or a code using .NET assembly, you need to store credentials (such as a username and a password) somewhere. Storing them in the script/code directly has obvious disadvantages, for example:
- The script/code is often stored in a revision control system, making the credentials easily accessible.
- The script/code may often need to be accessible on the production system for review or auditing purposes or reuse, while the credentials should not.
Advertisement
There is no way to store passwords in script in an encrypted way. In general, it is not possible to encrypt any kind of information in a way that still allows for its use in an automatic way. If WinSCP should be able to decrypt the information, anyone can.1
Solution is to separate the credentials from the script/code into a configuration file. While the script/code without explicit credentials can be safely stored into a revision system and be otherwise accessible, the configuration file should be protected as much as possible. Particularly its file permissions should be restricted only to administrators (for writing) and user under which the script/code runs (for reading). The configuration file can also be encrypted, for example with built-in NTFS filesystem-level encryption.
Using WinSCP scripting
In script, you can replace actual credentials with reference to environment variables. You can then call WinSCP from a batch file that sets these variables. The batch file itself then serves as a “configuration file”.
For example, the following script (example.txt
)2:
open -username=%USERNAME% -password=%PASSWORD% sftp://example.com/ ...
Advertisement
can be called from this batch file (“configuration file”):
@echo off set USERNAME=martin set PASSWORD=mypassword winscp.com /script=example.txt
Another way is to store the password to a separate file and use -passwordsfromfiles
:
open -password=C:\path\password.txt -passwordsfromfiles sftp://username@example.com/
Using WinSCP .NET assembly
PowerShell
In PowerShell code using WinSCP .NET library you can use Get-Content
cmdlet to read an XML configuration file.
For example with following XML configuration file (config.xml
):
<Configuration> <UserName>martin</UserName> <Password>mypassword</Password> </Configuration>
use this PowerShell code to read and use it:
# Read XML configuration file [xml]$config = Get-Content ".\config.xml" # Use read credentials $sessionOptions = New-Object WinSCP.SessionOptions -Property @{ Protocol = [WinSCP.Protocol]::Sftp HostName = "example.com" UserName = $config.Configuration.UserName Password = $config.Configuration.Password } ...
You can also leverage Windows Data Protection API to encrypt the password in the XML file.
If you want to encrypt the password within the configuration file, you can use use ConvertFrom-SecureString
cmdlet. Put the following code to an ad-hoc script (or an interactive PowerShell console):
Read-Host -AsSecureString | ConvertFrom-SecureString
Advertisement
A password encrypted this way can be decrypted by the same Windows account only.
Store the encrypted password to the XML file instead of the plain-text one:
<Configuration> <UserName>martin</UserName> <Password>01000000d08c9ddf0115d1118c7a00c04fc297eb01000000cf6dbc52515...</Password> </Configuration>
To decrypt the password, use ConvertTo-SecureString
cmdlet and assign the resulting SecureString
to SessionOptions.SecurePassword
, instead of using plain text SessionOptions.Password
:
$sessionOptions.SecurePassword = ConvertTo-SecureString $config.Configuration.Password
Visual Studio (C#, VB.NET)
In .NET projects (C#, VB.NET) an application configuration file (App.config
) is used to store the settings. Though this file contains other configuration that needs to be shared and/or stored in a revision control system. To separate the credentials from other settings, you can link another configuration file like shown below.
Use the file
attribute of appSettings
element in the primary application configuration file (App.config
):
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> <appSettings file="ProtectedConfig.config"> <!-- other settings --> </appSettings> </configuration>
Add a new XML file to the project and name it ProtectedConfig.config
:
<?xml version="1.0" encoding="utf-8" ?> <appSettings> <add key="UserName" value="martin" /> <add key="Password" value="test" /> </appSettings>
Set the Copy to Output Directory property of the file to Copy if newer and the Build Action to Content to have the new configuration file correctly deployed.
To read the settings from the configuration file use ConfigurationManager.AppSettings
:3
SessionOptions sessionOptions = new SessionOptions { Protocol = Protocol.Sftp, HostName = "example.com", UserName = ConfigurationManager.AppSettings["UserName"], Password = ConfigurationManager.AppSettings["Password"], };
Advertisement
You can also leverage Windows Data Protection API to encrypt the password in the XML file.
If you want to encrypt the password within the configuration file, you can use use PowerShell ConvertFrom-SecureString
cmdlet:
powershell.exe -Command "& Read-Host -AsSecureString | ConvertFrom-SecureString"
A password encrypted this way can be decrypted by the same Windows account only.
Store the encrypted password to the XML file instead of the plain-text one:
<?xml version="1.0" encoding="utf-8" ?> <appSettings> <add key="UserName" value="martin" /> <add key="Password" value="01000000d08c9ddf0115d1118c7a00c04fc297eb01000000cf6dbc52515..." /> </appSettings>
To decrypt the password, use ProtectedData.Unprotect
:4
string hex = ConfigurationManager.AppSettings["Password"]; byte[] bytes = Enumerable.Range(0, hex.Length / 2). Select(x => Convert.ToByte(hex.Substring(x * 2, 2), 16)).ToArray(); byte[] decrypted = ProtectedData.Unprotect(bytes, null, DataProtectionScope.CurrentUser); sessionOptions.Password = Encoding.Unicode.GetString(decrypted);
SSIS
In SSIS, you can configure script variables in SSIS > Variables. To make them accessible from the script task, in the context menu of the task, choose Edit. On the Script task editor on Script page, select ReadOnlyVariables, and tick the below properties.
See SSIS example.
Session URL
Instead of storing credentials (username and password) individually, you can use a single string, the session URL (a kind of “connection string”). This gives you an additional flexibility of changing even protocol by mere configuration.
Use the SessionOptions.ParseUrl
to parse the URL into individual components.
You can change the above PowerShell example as below.
The configuration file:
<Configuration> <SessionUrl>sftp://martin:mypassword@example.com/</SessionUrl> </Configuration>
Advertisement
The code:
# Read XML configuration file [xml]$config = Get-Content ".\config.xml" # Use read credentials $sessionOptions = New-Object WinSCP.SessionOptions $sessionOptions.ParseUrl($config.Configuration.SessionUrl) ...
You can apply the same technique to the above C# example.
Only if you want to encrypt the password as shown in the previous sections, you need to separate the password from the rest of the session URL. First call the SessionOptions.ParseUrl
and then separately set the SessionOptions.Password
.
- For a real encryption, one needs to use a key. And the key needs to be stored somewhere again. It’s the chicken or the egg problem.Back
- Using
-username
and-password
switches instead of specifying credentials in session URL, because the switches do not require the values to be URL-encoded.Back - You need to reference the
System.configuration
assembly in your project to use theConfigurationManager
class.Back - You need to reference the
System.Security
assembly in your project to use theProtectedData
class.Back