Documentation » Using WinSCP » Guides » Scripting/Automation »

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.

  1. 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
  2. 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
  3. You need to reference the System.configuration assembly in your project to use the ConfigurationManager class.Back
  4. You need to reference the System.Security assembly in your project to use the ProtectedData class.Back

Last modified: by martin