I have a PowerShell script (below) that successfully encrypt and decrypt string values using a specific key (as a string).
Now I'm trying to write a C# method that can decrypt a value using the same key, but I'm not sure how to use PasswordDeriveBytes correctly.
Also, I'm trying to specify the Mode and Padding for the AesManaged object to match the PowerShell script (I understand that CBC mode is most secured, but I would change it if something else is working better).
Any help or directions would highly appreciated.
Test code (.Net Framework 4.7):
// Note: values are hardcoded based on the results from the PowerShell script
var keyString = "8CBaNtMYwAuu2K/xleoRfgPkURaLK82QidlIyg+nFY4=";
var keyBytes = Convert.FromBase64String(keyString.PadLeft(32)); // Not so sure about this
var ivBytes = Convert.FromBase64String(keyString.PadLeft(16)); // Not so sure about this
var encryptedString = "JW9CDowP0tRGr0Xi7vLxxXv0+fvMzQzopQucLOaeU7s=";
var encryptedByteArray = Convert.FromBase64String(encryptedString);
var test = Cryptography.DecryptStringFromBytes_Aes(encryptedByteArray, keyBytes, ivBytes);
Decryption method (not working):
namespace Test.Security
{
using System;
using System.IO;
using System.Security.Cryptography;
public class Cryptography
{
// Note: this method is a Microsoft example, adding PasswordDeriveBytes and specific settings
// https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.aesmanaged?view=netframework-4.7.2
public static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] key, byte[] iv)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (key == null || key.Length <= 0)
throw new ArgumentNullException("Key");
if (iv == null || iv.Length <= 0)
throw new ArgumentNullException("IV");
string plaintext = null;
using (AesManaged aesAlg = new AesManaged())
{
var passwordDerivedbytes = new PasswordDeriveBytes(key, iv);
aesAlg.Key = passwordDerivedbytes.GetBytes(aesAlg.KeySize / 8);
aesAlg.IV = passwordDerivedbytes.GetBytes(aesAlg.BlockSize / 8);
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.Zeros;
aesAlg.KeySize = 256;
aesAlg.BlockSize = 128;
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
}
}
PowerShell script (PowerShell_AES_Encryption_Example.ps1):
function Create-AesManagedObject($key, $IV) {
$aesManaged = New-Object "System.Security.Cryptography.AesManaged"
$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
$aesManaged.BlockSize = 128
$aesManaged.KeySize = 256
if ($IV) {
if ($IV.getType().Name -eq "String") {
$aesManaged.IV = [System.Convert]::FromBase64String($IV)
}
else {
$aesManaged.IV = $IV
}
}
if ($key) {
if ($key.getType().Name -eq "String") {
$aesManaged.Key = [System.Convert]::FromBase64String($key)
}
else {
$aesManaged.Key = $key
}
}
$aesManaged
}
function Create-AesKey() {
$aesManaged = Create-AesManagedObject
$aesManaged.GenerateKey()
[System.Convert]::ToBase64String($aesManaged.Key)
}
function Encrypt-String($key, $unencryptedString) {
$bytes = [System.Text.Encoding]::UTF8.GetBytes($unencryptedString)
$aesManaged = Create-AesManagedObject $key
$encryptor = $aesManaged.CreateEncryptor()
$encryptedData = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length);
[byte[]] $fullData = $aesManaged.IV + $encryptedData
$aesManaged.Dispose()
[System.Convert]::ToBase64String($fullData)
}
function Decrypt-String($key, $encryptedStringWithIV) {
$bytes = [System.Convert]::FromBase64String($encryptedStringWithIV)
$IV = $bytes[0..15]
$aesManaged = Create-AesManagedObject $key $IV
$decryptor = $aesManaged.CreateDecryptor();
$unencryptedData = $decryptor.TransformFinalBlock($bytes, 16, $bytes.Length - 16);
$aesManaged.Dispose()
[System.Text.Encoding]::UTF8.GetString($unencryptedData).Trim([char]0)
}
cls
<#
# This will generate a new valid AES 256 key if needed:
# $key = Create-AesKey
#>
<#
# This is the hard coded key
#>
$key = "8CBaNtMYwAuu2K/xleoRfgPkURaLK82QidlIyg+nFY4="
Write-Host "key = $key"
$unencryptedString = "blahblahblah"
Write-Host "unencryptedString = $unencryptedString"
$encryptedString = Encrypt-String $key $unencryptedString
Write-Host "encryptedString = $encryptedString "
$backToPlainText = Decrypt-String $key $encryptedString
Write-Host "backToPlainText = $backToPlainText"
<#
# To run this PowerShell script:
#
# In Windows PowerShell:
# .\PowerShell_AES_Encryption_Example.ps1
# C:\Test\PowerShell_AES_Encryption_Example.ps1
#
# In Command Prompt:
# powershell -noexit "& ""C:\Test\PowerShell_AES_Encryption_Example.ps1"""
#>
I found out that I was missing few things, for the Encryption - create a random IV (salt) when encrypting and combining the iv (salt) and the encrypted data, for the decryption - create the IV (salt) based on the encrypted string and trim the zeros at the end (because I'm using PaddingMode.Zeros). Also found some useful examples on: mark-adams/aes_example.cs
Now it works with encrypted values created by the PowerShell script, so, I ended up writing a wrapper class, let me know if you can find anything wrong or any suggestions for improvement:
Test Code
var keyString = "8CBaNtMYwAuu2K/xleoRfgPkURaLK82QidlIyg+nFY4=";
var keyBytes = Convert.FromBase64String(keyString);
var encryptedValue = "JW9CDowP0tRGr0Xi7vLxxXv0+fvMzQzopQucLOaeU7s=";
var testValue = AesWrapper.Decrypt(encryptedValue, keyBytes);
var testEncrypted = AesWrapper.Encrypt(@"Test String", keyBytes);
var testDecrypted = AesWrapper.Decrypt(testEncrypted, keyBytes);
var testReEncrypted = AesWrapper.Encrypt(testEncrypted, keyBytes);
AesWrapper
namespace Yovav.Security
{
using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
/// <summary>
/// AES wrapper implementation by Yovav Gad using the AesManaged algorithm.
/// <para>http://en.wikipedia.org/wiki/Advanced_Encryption_Standard</para>
/// </summary>
public sealed class AesWrapper
{
/// <summary>
/// Create a SymmetricAlgorithm using AesManaged
/// </summary>
/// <param name="key">Byte array representing the key values, please note,
/// for better performance, use Convert.FromBase64String() outside of this method.</param>
/// <param name="blockSize">BlockSize, default is 128</param>
/// <param name="paddingMode">PaddingMode, default is PaddingMode.Zeros</param>
/// <param name="cipherMode">CipherMode, default is CipherMode.CBC</param>
/// <returns></returns>
private static SymmetricAlgorithm CreateCrypto(
byte[] key,
int blockSize = 128,
PaddingMode paddingMode = PaddingMode.Zeros,
CipherMode cipherMode = CipherMode.CBC
)
{
SymmetricAlgorithm crypto = new AesManaged
{
Key = key,
Mode = cipherMode,
Padding = paddingMode,
BlockSize = blockSize
};
crypto.IV = new byte[crypto.IV.Length];
return (crypto);
}
/// <summary>
/// Decrypt an encrypted string using a specific key.
/// </summary>
/// <param name="str">String to decrypt</param>
/// <param name="key">Byte array representing the key values, please note,
/// for better performance, use Convert.FromBase64String() outside of this method.</param>
/// <param name="blockSize">BlockSize, default is 128</param>
/// <param name="paddingMode">PaddingMode, default is PaddingMode.Zeros</param>
/// <param name="cipherMode">CipherMode, default is CipherMode.CBC</param>
/// <returns></returns>
[DebuggerStepThrough()]
public static string Decrypt(
string str,
byte[] key,
int blockSize = 128,
PaddingMode paddingMode = PaddingMode.Zeros,
CipherMode cipherMode = CipherMode.CBC
)
{
if (str == null || str.Length < 1 ||
key == null || key.Length < 1)
{
return null;
}
var result = string.Empty;
using (var crypto = CreateCrypto(key, blockSize, paddingMode, cipherMode))
{
var strCombined = Convert.FromBase64String(str);
var iv = new byte[crypto.BlockSize / 8];
var cipherText = new byte[strCombined.Length - iv.Length];
Array.Copy(strCombined, iv, iv.Length);
Array.Copy(strCombined, iv.Length, cipherText, 0, cipherText.Length);
crypto.IV = iv;
ICryptoTransform decryptor = crypto.CreateDecryptor(key, iv);
using (var msDecrypt = new MemoryStream(cipherText))
{
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (var srDecrypt = new StreamReader(csDecrypt))
{
result = srDecrypt.ReadToEnd();
}
}
}
if (paddingMode == PaddingMode.Zeros)
{
// This is required when using PaddingMode.Zeros for values shorted than the block size.
// Note: using .TrimEnd('\0') to remove nulls and not .TrimEnd("\0") to allow the string values.
result = result.TrimEnd('\0');
}
return (result);
}
}
/// <summary>
/// Encrypt a string using a specific key.
/// </summary>
/// <param name="str">String to encrypt</param>
/// <param name="key">Byte array representing the key values, please note,
/// for better performance, use Convert.FromBase64String() outside of this method.</param>
/// <param name="blockSize">BlockSize, default is 128</param>
/// <param name="paddingMode">PaddingMode, default is PaddingMode.Zeros</param>
/// <param name="cipherMode">CipherMode, default is CipherMode.CBC</param>
/// <returns></returns>
[DebuggerStepThrough()]
public static string Encrypt(
string str,
byte[] key,
int blockSize = 128,
PaddingMode paddingMode = PaddingMode.Zeros,
CipherMode cipherMode = CipherMode.CBC
)
{
if (str == null || str.Length < 1 ||
key == null || key.Length < 1)
{
return null;
}
byte[] encryptedData;
using (SymmetricAlgorithm crypto = CreateCrypto(key, blockSize, paddingMode, cipherMode))
{
byte[] data;
crypto.GenerateIV();
var iv = crypto.IV;
var encryptor = crypto.CreateEncryptor(key, iv);
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter sw = new StreamWriter(cs))
{
sw.Write(str);
}
data = ms.ToArray();
}
}
// Combine the iv (salt) and the encrypted data
encryptedData = new byte[iv.Length + data.Length];
Array.Copy(iv, 0, encryptedData, 0, iv.Length);
Array.Copy(data, 0, encryptedData, iv.Length, data.Length);
}
return Convert.ToBase64String(encryptedData);
}
}
}