Search code examples
c#.netpowershellencryptionaes

How can I use ConvertTo-SecureString


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.


Solution

  • 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);
    }