One of the challenges with scripting / programming is securely storing passwords or API secrets across many different languages. Most of what i develop runs in Windows and as such it made sense to tap into the ProtectedData API and the Windows Registry.
PowerShell Code
Add-Type -AssemblyName System.Security;
$RegRootPath = "HKCU:\SOFTWARE\MIKE"
[byte[]]$AdditionalEntropy = 77,33,22,44,66,12,46,74,22,11,45,23,11,34,66
function Load-SecureDataFromRegistry
{
param (
[string]$name,
[string]$keyName
)
$regPath = "$($regRootPath)\$($name)\"
if (Test-Path $regPath)
{
$encryptedBase64String = Get-ItemPropertyValue -Path $regPath -Name $keyName
#Write-Host "base64 string$($encryptedBase64String)"
$toDecrypt = [System.Convert]::FromBase64String($encryptedBase64String)
#Write-Host "base64 data: $($toDecrypt)"
$toDecrypt = [System.Security.Cryptography.ProtectedData]::Unprotect($toDecrypt, $AdditionalEntropy, 'CurrentUser')
$toDecrypt = [System.Text.UnicodeEncoding]::ASCII.GetString($toDecrypt)
return $toDecrypt
}
return [String]::Empty
}
function Save-SecureDataToRegistry
{
param (
[string]$name,
[string]$keyName,
[string]$keyValue,
[string]$description
)
$toEncrypt = [System.Text.UnicodeEncoding]::ASCII.GetBytes($keyValue)
$toEncrypt = [System.Security.Cryptography.ProtectedData]::Protect($toEncrypt, $AdditionalEntropy, 'CurrentUser')
$toEncrypt = [System.Convert]::ToBase64String($toEncrypt)
$regPath = "$($regRootPath)\$($name)\"
if (!(Test-Path -Path $regPath))
{
New-Item -Path $regRootPath -Name $name -Force -Confirm:$false
}
Set-ItemProperty -Path $regPath -Name $keyName -Value $toEncrypt -Force -Confirm:$false
Set-ItemProperty -Path $regPath -Name "$($keyName)_Description" -Value $description -Force -Confirm:$false
}
Powershell Usage
Save-SecureDataToRegistry -name "DefenderForEndpoint" -keyName "ApiKey1" -description "Defender for endpoint api key - read only" -keyValue 'example secret'
$apiKey1 = Load-SecureDataFromRegistry -name "DefenderForEndpoint" -keyName "ApiKey1"
C# Code
/// <summary>
/// https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection
/// https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.protecteddata?view=net-8.0&redirectedfrom=MSDN
/// </summary>
public static class EncryptionManagement
{
private static byte[] _additionalEntropy = { 77,33,22,44,66,12,46,74,22,11,45,23,11,34,66 };
private static string _regRootPath = @"SOFTWARE\MIKE";
public static string LoadSecureStringFromRegistry(string name, string keyName)
{
var regKey = Registry.CurrentUser.OpenSubKey($@"{_regRootPath}\{name}");
if (regKey != null)
{
var secureText = (string)regKey.GetValue(keyName);
return secureText.DecryptString();
}
return string.Empty;
}
public static void SaveSecureStringToRegistry(this string keyValue, string name, string keyName, string description)
{
var regKey = Registry.CurrentUser.OpenSubKey($@"{_regRootPath}\{name}");
if (regKey == null)
{
Registry.CurrentUser.CreateSubKey($@"{_regRootPath}\{name}");
regKey = Registry.CurrentUser.OpenSubKey($@"{_regRootPath}\{name}");
}
regKey.SetValue(keyName, keyValue.EncryptString(), RegistryValueKind.String);
regKey.SetValue("{keyName}_Description", description, RegistryValueKind.String);
}
public static string EncryptString(this string text)
{
return EncryptString(text, DataProtectionScope.CurrentUser);
}
public static string DecryptString(this string text)
{
return DecryptString(text, DataProtectionScope.CurrentUser);
}
public static void ProtectedByteArrayToString(this Byte[] myArr)
{
foreach (Byte i in myArr)
{
Console.Write("\t{0}", i);
}
Console.WriteLine();
}
/// <summary>
///
/// </summary>
/// <param name="text">String</param>
/// <param name="scope"></param>
/// <returns>Base64EncodedString</returns>
public static string EncryptString(this string text, DataProtectionScope scope)
{
byte[] toEncrypt = UnicodeEncoding.ASCII.GetBytes(text);
toEncrypt = Encrypt(toEncrypt, scope);
return Convert.ToBase64String(toEncrypt);
}
/// <summary>
///
/// </summary>
/// <param name="text">Base64EncodedString</param>
/// <param name="scope"></param>
/// <returns>String</returns>
public static string DecryptString(this string text, DataProtectionScope scope)
{
byte[] toDecrypt = Convert.FromBase64String(text);
toDecrypt = Decrypt(toDecrypt, scope);
return UnicodeEncoding.ASCII.GetString(toDecrypt);
}
public static byte[] Encrypt(byte[] data, DataProtectionScope scope)
{
try
{
// Encrypt the data using the supplied scope. The result can be decrypted
// only by the same scope.
return ProtectedData.Protect(data, _additionalEntropy, scope);
}
catch (CryptographicException e)
{
Console.WriteLine("Data was not encrypted. An error occurred.");
Console.WriteLine(e.ToString());
return null;
}
}
public static byte[] Decrypt(byte[] data, DataProtectionScope scope)
{
try
{
//Decrypt the data using supplied scope.
return ProtectedData.Unprotect(data, _additionalEntropy, scope);
}
catch (CryptographicException e)
{
Console.WriteLine("Data was not decrypted. An error occurred.");
Console.WriteLine(e.ToString());
return null;
}
}
public static byte[] Encrypt(byte[] data)
{
try
{
// Encrypt the data using DataProtectionScope.CurrentUser. The result can be decrypted
// only by the same current user.
return ProtectedData.Protect(data, _additionalEntropy, DataProtectionScope.CurrentUser);
}
catch (CryptographicException e)
{
Console.WriteLine("Data was not encrypted. An error occurred.");
Console.WriteLine(e.ToString());
return null;
}
}
public static byte[] Decrypt(byte[] data)
{
try
{
//Decrypt the data using DataProtectionScope.CurrentUser.
return ProtectedData.Unprotect(data, _additionalEntropy, DataProtectionScope.CurrentUser);
}
catch (CryptographicException e)
{
Console.WriteLine("Data was not decrypted. An error occurred.");
Console.WriteLine(e.ToString());
return null;
}
}
public static void EncryptInMemoryData(byte[] Buffer, MemoryProtectionScope Scope)
{
if (Buffer == null)
throw new ArgumentNullException(nameof(Buffer));
if (Buffer.Length <= 0)
throw new ArgumentException("The buffer length was 0.", nameof(Buffer));
// Encrypt the data in memory. The result is stored in the same array as the original data.
ProtectedMemory.Protect(Buffer, Scope);
}
public static void DecryptInMemoryData(byte[] Buffer, MemoryProtectionScope Scope)
{
if (Buffer == null)
throw new ArgumentNullException(nameof(Buffer));
if (Buffer.Length <= 0)
throw new ArgumentException("The buffer length was 0.", nameof(Buffer));
// Decrypt the data in memory. The result is stored in the same array as the original data.
ProtectedMemory.Unprotect(Buffer, Scope);
}
public static byte[] CreateRandomEntropy()
{
// Create a byte array to hold the random value.
byte[] entropy = new byte[16];
// Create a new instance of the RNGCryptoServiceProvider.
// Fill the array with a random value.
new RNGCryptoServiceProvider().GetBytes(entropy);
// Return the array.
return entropy;
}
public static int EncryptDataToStream(byte[] Buffer, byte[] Entropy, DataProtectionScope Scope, Stream S)
{
if (Buffer == null)
throw new ArgumentNullException(nameof(Buffer));
if (Buffer.Length <= 0)
throw new ArgumentException("The buffer length was 0.", nameof(Buffer));
if (Entropy == null)
throw new ArgumentNullException(nameof(Entropy));
if (Entropy.Length <= 0)
throw new ArgumentException("The entropy length was 0.", nameof(Entropy));
if (S == null)
throw new ArgumentNullException(nameof(S));
int length = 0;
// Encrypt the data and store the result in a new byte array. The original data remains unchanged.
byte[] encryptedData = ProtectedData.Protect(Buffer, Entropy, Scope);
// Write the encrypted data to a stream.
if (S.CanWrite && encryptedData != null)
{
S.Write(encryptedData, 0, encryptedData.Length);
length = encryptedData.Length;
}
// Return the length that was written to the stream.
return length;
}
public static byte[] DecryptDataFromStream(byte[] Entropy, DataProtectionScope Scope, Stream S, int Length)
{
if (S == null)
throw new ArgumentNullException(nameof(S));
if (Length <= 0)
throw new ArgumentException("The given length was 0.", nameof(Length));
if (Entropy == null)
throw new ArgumentNullException(nameof(Entropy));
if (Entropy.Length <= 0)
throw new ArgumentException("The entropy length was 0.", nameof(Entropy));
byte[] inBuffer = new byte[Length];
byte[] outBuffer;
// Read the encrypted data from a stream.
if (S.CanRead)
{
S.Read(inBuffer, 0, Length);
outBuffer = ProtectedData.Unprotect(inBuffer, Entropy, Scope);
}
else
{
throw new IOException("Could not read the stream.");
}
// Return the decrypted data
return outBuffer;
}
}
C# Usage
pass = EncryptionManagement.LoadSecureStringFromRegistry("DefenderForEndpoint", "key1");
EncryptionManagement.SaveSecureStringToRegistry("example key", "DefenderForEndpoint", "key1", "Read only defender secret");
Python Code
import base64
import winreg
import win32crypt #actual module to install pypiwin32 or pywin32
reg_root_path = r"SOFTWARE\MIKE"
additional_entropy = bytes([77,33,22,44,66,12,46,74,22,11,45,23,11,34,66])
def save_secure_data_to_registry(name, key_name, key_value, description, reg_root_path, additional_entropy):
# Encrypt the key_value
key_value = key_value.encode('utf-8')
to_encrypt = win32crypt.CryptProtectData(key_value, None, additional_entropy , None, None, 0)
to_encrypt = base64.b64encode(to_encrypt).decode('ascii')
reg_path = f"{reg_root_path}\\{name}"
# Create registry key if it doesn't exist
try:
winreg.CreateKey(winreg.HKEY_CURRENT_USER, reg_path)
except FileExistsError:
print("error writing to reg")
# Set registry values
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, reg_path, 0, winreg.KEY_WRITE) as reg_key:
winreg.SetValueEx(reg_key, key_name, 0, winreg.REG_SZ, to_encrypt)
winreg.SetValueEx(reg_key, f"{key_name}_Description", 0, winreg.REG_SZ, description)
def load_secure_data_from_registry(name, key_name, reg_root_path, additional_entropy):
reg_path = f"{reg_root_path}\\{name}"
additional_entropy_bytes = bytes(additional_entropy)
try:
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, reg_path) as key:
encrypted_base64_string, _ = winreg.QueryValueEx(key, key_name)
encoded_bytes = encrypted_base64_string.encode('ascii')
#print(f"base64secret: {encoded_bytes}")
decoded_data = base64.decodebytes(encoded_bytes)
#print(f"secret: {decoded_data}")
decrypted_data_result = win32crypt.CryptUnprotectData(decoded_data, additional_entropy_bytes, None, None, 0)
decrypted_data = decrypted_data_result[1].decode('ascii')
return decrypted_data
except FileNotFoundError:
return ""
Python Usage
save_secure_data_to_registry("DefenderForEndpoint1", "DefenderApiKey", apikey, "defender key", reg_root_path, additional_entropy)
apikey1 = load_secure_data_from_registry("DefenderForEndpoint", "DefenderApiKey", reg_root_path, additional_entropy)
print (f"apikey1 {apikey1}")