7

Let's say I need to do this in Powershell:

    $SecurePass = Get-Content $CredPath | ConvertTo-SecureString -Key (1..16)
    [String]$CleartextPass = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($CredPass));

The content of $CredPath is a file that contains the output of ConvertFrom-SecureString -Key (1..16).

How do I accomplish the ConvertTo-SecureString -key (1..16) portion in C#/.NET?

I know how to create a SecureString, but I'm not sure how the encryption should be handled.

Do I encrypt each character using AES, or decrypt the string and then create a the secure string per character?

I know next to nothing about cryptography, but from what I've gathered I might just want to invoke the Powershell command using C#.

For reference, I found a similar post about AES encryption/decryption here: Using AES encryption in C#

UPDATE

I have reviewed the link Keith posted, but I face additional unknowns. The DecryptStringFromBytes_Aes takes three arguments:

static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)

The first argument is a byte array represents the encrypted text. The question here is, how should the string be represented in the byte array? Should it be represented with or without encoding?

byte[] ciphertext = Encoding.ASCII.GetBytes(encrypted_text);
byte[] ciphertext = Encoding.UTF8.GetBytes(encrypted_text);
byte[] ciphertext = Encoding.Unicode.GetBytes(encrypted_text);    

byte[] ciphertext = new byte[encrypted_password.Length * sizeof(char)];
System.Buffer.BlockCopy(encrypted_password.ToCharArray(), 0, text, 0, text.Length);

The second byte array is the key should simply be an array of integers:

byte[] key = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 };

The third byte array is an "Initialization Vector" - it looks like the Aes.Create() call will generate a byte[] for IV randomly. Reading around, I've found that I might need to use the same IV. As ConvertFrom-SecureString and ConvertTo-SecureString are able to encrypt/decrypt using simply the key, I am left with the assumption that the IV[] can be random -or- has a static definition.

I have not yet found a winning combination, but I will keep trying.

Community
  • 1
  • 1
Rex Hardin
  • 1,979
  • 2
  • 17
  • 16
  • 1
    possible duplicate of [Convert String to SecureString](http://stackoverflow.com/questions/1570422/convert-string-to-securestring) – EBGreen Nov 29 '12 at 20:29
  • One of the other PowerShell MVPs believes the string is Base64 encoded. It appears that there are three sets of data delimited by the `|` char. The middle set may be the IV data. – Keith Hill Dec 01 '12 at 05:03
  • Hmm, (s)he might be right. If one of the three is an IV and one represents the encrypted text, what would the third delimited item be? Perhaps a salt for the supplied key? – Rex Hardin Dec 01 '12 at 22:45
  • For the same problem, but in the case where a key is not provided (i.e. where ConvertFrom-SecureString has been used without a key), see http://stackoverflow.com/questions/33880731/securely-convert-encrypted-standard-string-to-securestring. – David I. McIntosh Nov 25 '15 at 04:08

5 Answers5

13

I know this is an old post. I am posting this for completeness and posterity, because I couldn't find a complete answer on MSDN or stackoverflow. It will be here in case I ever need to do this again.

It is a C# implementation of of powershell's ConvertTo-SecureString with AES encryption (turned on by using the -key option). I will leave it for exercise to code a C# implementation of ConvertFrom-SecureString.

# forward direction
[securestring] $someSecureString = read-host -assecurestring
[string] $psProtectedString = ConvertFrom-SecureString -key (1..16) -SecureString $someSecureString
# reverse direction
$back = ConvertTo-SecureString -string $psProtectedString -key (1..16)

My work is combining answers and re-arranging user2748365's answer to be more readable and adding educational comments! I also fixed the issue with taking a substring -- at the time of this post, his code only has two elements in strArray.

using System.IO;
using System.Text;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Globalization;

// psProtectedString - this is the output from
//   powershell> $psProtectedString = ConvertFrom-SecureString -SecureString $aSecureString -key (1..16)
// key - make sure you add size checking 
// notes: this will throw an cryptographic invalid padding exception if it cannot decrypt correctly (wrong key)
public static SecureString ConvertToSecureString(string psProtectedString, byte[] key)
{
    // '|' is indeed the separater
    byte[] asBytes = Convert.FromBase64String( psProtectedString );
    string[] strArray = Encoding.Unicode.GetString(asBytes).Split(new[] { '|' });

    if (strArray.Length != 3) throw new InvalidDataException("input had incorrect format");

    // strArray[0] is a static/magic header or signature (different passwords produce
    //    the same header)  It unused in our case, looks like 16 bytes as hex-string
    // you know strArray[1] is a base64 string by the '=' at the end
    //    the IV is shorter than the body, and you can verify that it is the IV, 
    //    because it is exactly 16bytes=128bits and it decrypts the password correctly
    // you know strArray[2] is a hex-string because it is [0-9a-f]
    byte[] magicHeader = HexStringToByteArray(encrypted.Substring(0, 32));
    byte[] rgbIV = Convert.FromBase64String(strArray[1]);
    byte[] cipherBytes = HexStringToByteArray(strArray[2]);

    // setup the decrypter
    SecureString str = new SecureString();
    SymmetricAlgorithm algorithm = SymmetricAlgorithm.Create();
    ICryptoTransform transform = algorithm.CreateDecryptor(key, rgbIV);
    using (var stream = new CryptoStream(new MemoryStream(cipherBytes), transform, CryptoStreamMode.Read))
    {
        // using this silly loop format to loop one char at a time
        // so we never store the entire password naked in memory
        int numRed = 0;
        byte[] buffer = new byte[2]; // two bytes per unicode char
        while( (numRed = stream.Read(buffer, 0, buffer.Length)) > 0 )
        {
            str.AppendChar(Encoding.Unicode.GetString(buffer).ToCharArray()[0]);
        }
    }

    //
    // non-production code
    // recover the SecureString; just to check
    // from http://stackoverflow.com/questions/818704/how-to-convert-securestring-to-system-string
    //
    IntPtr valuePtr = IntPtr.Zero;
    string secureStringValue = "";
    try
    {
        // get the string back
        valuePtr = Marshal.SecureStringToGlobalAllocUnicode(str);
        secureStringValue = Marshal.PtrToStringUni(valuePtr);
    }
    finally
    {
        Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
    }

    return str;
}
// from http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa
public static byte[] HexStringToByteArray(String hex)
{
    int NumberChars = hex.Length;
    byte[] bytes = new byte[NumberChars / 2];
    for (int i = 0; i < NumberChars; i += 2) bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);

    return bytes;
}
public static SecureString DecryptPassword( string psPasswordFile, byte[] key )
{
    if( ! File.Exists(psPasswordFile)) throw new ArgumentException("file does not exist: " + psPasswordFile);

    string formattedCipherText = File.ReadAllText( psPasswordFile );

    return ConvertToSecureString(formattedCipherText, key);
}
Cheng Tsai
  • 152
  • 1
  • 3
  • 2
    ConvertToSecureString(password, new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); was what i was missing – Alfons Oct 07 '15 at 13:21
3

According to the docs on ConvertFrom-SecureString the AES encryption algorithm is used:

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 no key is specified, the Windows Data Protection API (DPAPI) is used to encrypt the standard string representation.

Look at the DecryptStringFromBytes_Aes example in the MSDN docs.

BTW an easy option would be to use the PowerShell engine from C# to execute the ConvertTo-SecureString cmdlet to do the work. Otherwise, it looks like the initialization vector is embedded somewhere in the ConvertFrom-SecureString output and may or may not be easy to extract.

Keith Hill
  • 194,368
  • 42
  • 353
  • 369
2

Adding on to Cheng's answer - I found I had to change:

byte[] magicHeader = HexStringToByteArray(encrypted.Substring(0, 32));

to

byte[] magicHeader = HexStringToByteArray(psProtectedString.Substring(0, 32));

and

SymmetricAlgorithm algorithm = SymmetricAlgorithm.Create();

to

SymmetricAlgorithm algorithm = Aes.Create();

but it otherwise works wonderfully.

1

How do I accomplish the ConvertTo-SecureString -key (1..16) portion in C#/.NET?

Please see the following code:

    private static SecureString ConvertToSecureString(string encrypted, string header, byte[] key)
    {
        string[] strArray = Encoding.Unicode.GetString(Convert.FromBase64String(encrypted.Substring(header.Length, encrypted.Length - header.Length))).Split(new[] {'|'});
        SymmetricAlgorithm algorithm = SymmetricAlgorithm.Create();
        int num2 = strArray[2].Length/2;
        var bytes = new byte[num2];
        for (int i = 0; i < num2; i++)
            bytes[i] = byte.Parse(strArray[2].Substring(2*i, 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
        ICryptoTransform transform = algorithm.CreateDecryptor(key, Convert.FromBase64String(strArray[1]));
        using (var stream = new CryptoStream(new MemoryStream(bytes), transform, CryptoStreamMode.Read))
        {
            var buffer = new byte[bytes.Length];
            int num = stream.Read(buffer, 0, buffer.Length);
            var data = new byte[num];
            for (int i = 0; i < num; i++) data[i] = buffer[i];
            var str = new SecureString();
            for (int j = 0; j < data.Length/2; j++) str.AppendChar((char) ((data[(2*j) + 1]*0x100) + data[2*j]));
            return str;
        }
    }

Example:

    encrypted = "76492d1116743f0423413b16050a5345MgB8ADcAbgBiAGoAVQBCAFIANABNADgAYwBSAEoAQQA1AGQAZgAvAHYAYwAvAHcAPQA9AHwAZAAzADQAYwBhADYAOQAxAGIAZgA2ADgAZgA0AGMANwBjADQAYwBiADkAZgA1ADgAZgBiAGQAMwA3AGQAZgAzAA==";
    header = "76492d1116743f0423413b16050a5345";

If you want to get decrypted characters, please check data in the method.

1

I found the easiest and simplest way was to call the ConvertTo-SecureString PowerShell command directly from C#. That way there's no difference in the implementation and the output is exactly what it would be if you called it from PowerShell directly.

    string encryptedPassword = RunPowerShellCommand("\"" 
            + password 
            + "\" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString", null);

    public static string RunPowerShellCommand(string command, 
        Dictionary<string, object> parameters)
    {
        using (PowerShell powerShellInstance = PowerShell.Create())
        {
            // Set up the running of the script
            powerShellInstance.AddScript(command);

            // Add the parameters
            if (parameters != null)
            {
                foreach (var parameter in parameters)
                {
                    powerShellInstance.AddParameter(parameter.Key, parameter.Value);
                }
            }

            // Run the command
            Collection<PSObject> psOutput = powerShellInstance.Invoke();

            StringBuilder stringBuilder = new StringBuilder();

            if (powerShellInstance.Streams.Error.Count > 0)
            {
                foreach (var errorMessage in powerShellInstance.Streams.Error)
                {
                    if (errorMessage != null)
                    {
                        throw new InvalidOperationException(errorMessage.ToString());
                    }
                }
            }

            foreach (var outputLine in psOutput)
            {
                if (outputLine != null)
                {
                    stringBuilder.Append(outputLine);
                }
            }

            return stringBuilder.ToString();
        }
    }
SharpC
  • 6,974
  • 4
  • 45
  • 40