Question:
Let’s say I need to do this in Powershell:
1 2 3 |
$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:
1 2 |
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?
1 2 3 4 5 6 7 |
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:
1 2 |
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.
Answer:
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.
1 2 3 4 5 6 |
# 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
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); } |