Как я могу использовать ConvertTo-SecureString
Допустим, мне нужно сделать это в Powershell:
$SecurePass = Get-Content $CredPath | ConvertTo-SecureString -Key (1..16)
[String]$CleartextPass = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($CredPass));
Содержимое $CredPath представляет собой файл, который содержит выходные данные ConvertFrom-SecureString -Key (1..16).
Как мне выполнить ConvertTo-SecureString -key (1..16)
часть в C#/.NET?
Я знаю, как создать SecureString
, но я не уверен, как шифрование должно быть обработано.
Должен ли я шифровать каждый символ с помощью AES или расшифровывать строку, а затем создавать безопасную строку для каждого символа?
Я почти ничего не знаю о криптографии, но из того, что я собрал, я мог бы просто вызвать команду Powershell с помощью C#.
Для справки, я нашел похожий пост о шифровании / дешифровании AES здесь: Использование шифрования AES в C#
ОБНОВИТЬ
Я просмотрел ссылку, опубликованную Китом, но столкнулся с дополнительными неизвестными. DecryptStringFromBytes_Aes принимает три аргумента:
static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
Первый аргумент - это байтовый массив, представляющий зашифрованный текст. Вопрос здесь в том, как строка должна быть представлена в байтовом массиве? Должен ли он быть представлен с кодировкой или без нее?
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);
Второй байтовый массив, ключ должен быть просто массивом целых чисел:
byte[] key = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 };
Третий байтовый массив представляет собой "Вектор инициализации" - похоже, что вызов Aes.Create() сгенерирует byte[] для IV в случайном порядке. Читая вокруг, я обнаружил, что мне может понадобиться тот же IV. Так как ConvertFrom-SecureString и ConvertTo-SecureString могут шифровать / дешифровать, используя просто ключ, у меня остается предположение, что IV[] может быть случайным или иметь статическое определение.
Я еще не нашел выигрышную комбинацию, но буду продолжать пытаться.
4 ответа
Я знаю, что это старый пост. Я публикую это для полноты и потомков, потому что не смог найти полного ответа по MSDN или stackru. Это будет здесь на случай, если мне когда-нибудь понадобится сделать это снова.
Это реализация C# для PowerShell ConvertTo-SecureString с шифрованием AES (включается с помощью параметра -key). Я оставлю это для упражнения, чтобы закодировать реализацию ConvertFrom-SecureString на C#.
# 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)
Моя работа состоит в том, чтобы объединить ответы и изменить порядок ответов пользователя user2748365, чтобы сделать его более читабельным, и добавить комментарии для обучения! Я также исправил проблему с использованием подстроки - на момент написания этой статьи его код содержал только два элемента в 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://stackru.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://stackru.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);
}
Согласно документам на ConvertFrom-SecureString используется алгоритм шифрования AES:
Если ключ шифрования указан с использованием параметров Key или SecureKey, используется алгоритм шифрования Advanced Encryption Standard (AES). Указанный ключ должен иметь длину 128, 192 или 256 бит, потому что это длины ключей, поддерживаемые алгоритмом шифрования AES. Если ключ не указан, API-интерфейс защиты данных Windows (DPAPI) используется для шифрования стандартного строкового представления.
Посмотрите на пример DecryptStringFromBytes_Aes в документах MSDN.
Кстати, простой вариант - использовать движок PowerShell из C# для выполнения ConvertTo-SecureString
Командлет, чтобы сделать работу. В противном случае, похоже, что вектор инициализации внедрен где-то в выходной файл ConvertFrom-SecureString и может быть или не может быть легко извлечен.
Добавление к ответу Ченга - я обнаружил, что должен был изменить:
byte[] magicHeader = HexStringToByteArray(encrypted.Substring(0, 32));
в
byte[] magicHeader = HexStringToByteArray(psProtectedString.Substring(0, 32));
а также
SymmetricAlgorithm algorithm = SymmetricAlgorithm.Create();
в
SymmetricAlgorithm algorithm = Aes.Create();
но в остальном работает чудесно.
Как мне выполнить часть ConvertTo-SecureString -key (1..16) в C#/.NET?
Пожалуйста, смотрите следующий код:
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;
}
}
Пример:
encrypted = "76492d1116743f0423413b16050a5345MgB8ADcAbgBiAGoAVQBCAFIANABNADgAYwBSAEoAQQA1AGQAZgAvAHYAYwAvAHcAPQA9AHwAZAAzADQAYwBhADYAOQAxAGIAZgA2ADgAZgA0AGMANwBjADQAYwBiADkAZgA1ADgAZgBiAGQAMwA3AGQAZgAzAA==";
header = "76492d1116743f0423413b16050a5345";
Если вы хотите получить расшифрованные символы, пожалуйста, проверьте данные в методе.
Я нашел самый простой и простой способ позвонить ConvertTo-SecureString
Команда PowerShell напрямую из C#. Таким образом, нет никакой разницы в реализации, и результат будет именно таким, каким он был бы, если бы вы вызывали его непосредственно из PowerShell.
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();
}
}