I have searched for a good plain text file encryption in C# and came across a post on SO and have been using it for a while now.
I don't remember the original post I got the code from; but here's the copy of the original code, slightly modified for my own use:
public static class StringCipher
{
private const int Keysize = 256;
private const int DerivationIterations = 1000;
public static string Encrypt(string plainText, string fileName)
{
try
{
var saltStringBytes = Generate256BitsOfRandomEntropy();
var ivStringBytes = Generate256BitsOfRandomEntropy();
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
using (var password = new Rfc2898DeriveBytes(Machine.Udid, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = 256;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
var cipherTextBytes = saltStringBytes;
cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
memoryStream.Close();
cryptoStream.Close();
return Convert.ToBase64String(cipherTextBytes);
}
}
}
}
}
}
catch (Exception exception)
{
throw new Exception("Failed to encrypt " + fileName + " - " + exception.Message, exception);
}
}
public static string Decrypt(string cipherText, string fileName)
{
try
{
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();
using (var password = new Rfc2898DeriveBytes(Machine.Udid, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = 256;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream(cipherTextBytes))
{
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
var plainTextBytes = new byte[cipherTextBytes.Length];
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
memoryStream.Close();
cryptoStream.Close();
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
}
}
}
}
}
catch (Exception exception)
{
throw new Exception("Failed to decrypt " + fileName + " - " + exception.Message, exception);
}
}
private static byte[] Generate256BitsOfRandomEntropy()
{
var randomBytes = new byte[32];
using (var rngCsp = new RNGCryptoServiceProvider())
{
rngCsp.GetBytes(randomBytes);
}
return randomBytes;
}
}
Every now and then, my app randomly throws this exception:
Padding is invalid and cannot be removed.
And this is my stack trace:
at System.Security.Cryptography.RijndaelManagedTransform.DecryptData(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount, Byte[]& outputBuffer, Int32 outputOffset, PaddingMode paddingMode, Boolean fLast)
at System.Security.Cryptography.RijndaelManagedTransform.TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)
at System.Security.Cryptography.CryptoStream.FlushFinalBlock()
at System.Security.Cryptography.CryptoStream.Dispose(Boolean disposing)
at System.IO.Stream.Close()
at System.IO.Stream.Dispose()
at DisplayMapper.Lib.StringCipher.Decrypt(String cipherText, String fileName) in C:\Users\Latheesan\Desktop\MyApp\StringCipher.cs:line 82
The line 82 is referring to this:
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
I have been reading up on this error and many post point to saying that the same key/password used to encrypt must be used to decrypt etc...
That is what I am doing in my implementation, I use a static password generated from a hash of the cpu/motherboard/hdd serial numbers with some random strings (which comes from Machine.Udid.
So, I am not sure what's causing this error. I haven't been able to re-produce it as the error occurs randomly. Any ideas?
Related
I am trying to implement a .net core version of our existing encryption mechanism. my existing encryption is implemented in the below way which is not compatible with .net core
byte[] bytesToBeDecrypted = Convert.FromBase64String(dataToDecrypt);
byte[] saltBytes = new byte[16];
using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
AES.KeySize = 256;
AES.BlockSize = 256;
var key = new Rfc2898DeriveBytes(GetHashKeyWithSHA256(), saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
AES.Mode = CipherMode.CBC;
AES.Padding = PaddingMode.PKCS7;
using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
cs.Close();
}
return UTF8Encoding.UTF8.GetString(ms.ToArray());
}
}
But there is no direct method to implement this into .net core so i have used AES but it is giving always a portion of expected output
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
byte[] saltBytes = new byte[16];
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();
using (var password = new Rfc2898DeriveBytes(GetHashKeyWithSHA256(), saltBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
var engine = new RijndaelEngine(256);
var blockCipher = new CbcBlockCipher(engine);
var cipher = new PaddedBufferedBlockCipher(blockCipher, new Pkcs7Padding());
var keyParam = new KeyParameter(keyBytes);
var keyParamWithIV = new ParametersWithIV(keyParam, ivStringBytes, 0, 32);
cipher.Init(false, keyParamWithIV);
var comparisonBytes = new byte[cipher.GetOutputSize(cipherTextBytes.Length)];
var length = cipher.ProcessBytes(cipherTextBytes, comparisonBytes, 0);
cipher.DoFinal(comparisonBytes, length);
var nullIndex = comparisonBytes.Length - 1;
while (comparisonBytes[nullIndex] == (byte)0)
nullIndex--;
comparisonBytes = comparisonBytes.Take(nullIndex + 1).ToArray();
var result = Encoding.UTF8.GetString(comparisonBytes, 0, comparisonBytes.Length);
return result;
}
private static byte[] GetHashKeyWithSHA256()
{
string key = "2893562938q562bdx3whegujfgwehjgfewygfr3287t4238rb2332r7y234723byr54h";
byte[] passwordBytes = Encoding.UTF8.GetBytes(key);
return SHA256.Create().ComputeHash(passwordBytes);
}
Test Input : SxXrRvUEqYvRATVswgrJVQ9EtS6AHJVo6wV+mhQpW+t9a0GPDxvatC9JUYEJ5z/vrPD0MBxyW3dBfrlgec2LqA==
Appreciate if somebody helps me to identify the issue
In the 2nd snippet there are the following issues:
dataToDecrypt contains only the ciphertext but not salt and IV. Therefore no separation is necessary.
Key and (especially) IV must be derived using the key derivation as in the 1st snippet.
The returned length in the DoFinal() call must be taken into account and used in the later GetString() call.
A possible implementation is:
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Paddings;
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.Security.Cryptography;
using System.Text;
...
string dataToDecrypt = "SxXrRvUEqYvRATVswgrJVQ9EtS6AHJVo6wV+mhQpW+t9a0GPDxvatC9JUYEJ5z/vrPD0MBxyW3dBfrlgec2LqA==";
var cipherTextBytes = Convert.FromBase64String(dataToDecrypt);
byte[] saltBytes = new byte[16];
int DerivationIterations = 1000;
using (var key = new Rfc2898DeriveBytes(GetHashKeyWithSHA256(), saltBytes, DerivationIterations))
{
var keyBytes = key.GetBytes(256 / 8);
var ivBytes = key.GetBytes(256 / 8);
var engine = new RijndaelEngine(256);
var blockCipher = new CbcBlockCipher(engine);
var cipher = new PaddedBufferedBlockCipher(blockCipher, new Pkcs7Padding());
var keyParam = new KeyParameter(keyBytes);
var keyParamWithIV = new ParametersWithIV(keyParam, ivBytes, 0, 32);
cipher.Init(false, keyParamWithIV);
var comparisonBytes = new byte[cipher.GetOutputSize(cipherTextBytes.Length)];
var length = cipher.ProcessBytes(cipherTextBytes, comparisonBytes, 0);
length += cipher.DoFinal(comparisonBytes, length);
Console.WriteLine(Encoding.UTF8.GetString(comparisonBytes, 0, length)); // hello .thanks for helping me to resolve the issue
}
With this a decryption is possible with the posted test data: hello .thanks for helping me to resolve the issue
Keep in mind that a static salt is insecure. Instead, a random salt should be generated for each encryption. The salt is not secret and is passed along with the ciphertext to the decrypting side, typically concatenated.
Edit:
As requested in the comment, the related encryption is:
string dataToEncrypt = "hello .thanks for helping me to resolve the issue ";
var plainTextBytes = Encoding.UTF8.GetBytes(dataToEncrypt);
byte[] saltBytes = new byte[16];
int DerivationIterations = 1000;
using (var key = new Rfc2898DeriveBytes(GetHashKeyWithSHA256(), saltBytes, DerivationIterations))
{
var keyBytes = key.GetBytes(256 / 8);
var ivBytes = key.GetBytes(256 / 8);
var engine = new RijndaelEngine(256);
var blockCipher = new CbcBlockCipher(engine);
var cipher = new PaddedBufferedBlockCipher(blockCipher, new Pkcs7Padding());
var keyParam = new KeyParameter(keyBytes);
var keyParamWithIV = new ParametersWithIV(keyParam, ivBytes, 0, 32);
cipher.Init(true, keyParamWithIV);
var ciphertextBytes = new byte[cipher.GetOutputSize(plainTextBytes.Length)];
var length = cipher.ProcessBytes(plainTextBytes, ciphertextBytes, 0);
cipher.DoFinal(ciphertextBytes, length);
Console.WriteLine(Convert.ToBase64String(ciphertextBytes)); // SxXrRvUEqYvRATVswgrJVQ9EtS6AHJVo6wV+mhQpW+t9a0GPDxvatC9JUYEJ5z/vrPD0MBxyW3dBfrlgec2LqA==
}
You can try this:
private const string Key = "ffs-Jks*ca#cyY<xHR][Ycx{dDL,B_nJeOgFa'G_q^Hv.yDWq.dsbE'1af^xdeP";
private static readonly byte[] IV = { 11, 123, 1, 44, 78, 22, 54, 12 };
#endregion
#region methods
/// <summary>
/// Creates a symmetric decryptor object with the current Key property and initialization vector (IV).
/// </summary>
/// <param name="cipherText">The encrypted string.</param>
/// <returns>The Plaintext of an encrypted string.</returns>
public static string Decrypt(string cipherText)
{
try
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
{
//TODO:LogErrors.Error(new ArgumentNullException("cipherText"));
return null;
}
if (Key == null || Key.Length <= 0)
{
//TODO:LogErrors.Error(new ArgumentNullException("Key"));
return null;
}
if (IV == null || IV.Length <= 0)
{
//TODO:LogErrors.Error(new ArgumentNullException("IV"));
return null;
}
cipherText = cipherText.Replace(" ", "+");
var cipherBytes = Convert.FromBase64String(cipherText);
using var encryptor = Aes.Create();
var pdb = new Rfc2898DeriveBytes(Key, IV);
encryptor.Mode = CipherMode.CBC;
encryptor.KeySize = 128;
encryptor.BlockSize = 128;
encryptor.FeedbackSize = 128;
encryptor.Padding = PaddingMode.PKCS7;
encryptor.Key = pdb.GetBytes(32);
encryptor.IV = pdb.GetBytes(16);
using var ms = new MemoryStream();
using (var cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(cipherBytes, 0, cipherBytes.Length);
cs.Close();
}
cipherText = Encoding.Unicode.GetString(ms.ToArray());
cipherText = cipherText.TrimEnd(char.Parse("\0"));
return cipherText;
}
catch (ArgumentNullException ex)
{
//TODO:LogErrors.Error(ex);
}
catch (ArgumentException ex)
{
//TODO:LogErrors.Error(ex);
}
catch (FormatException ex)
{
//TODO:LogErrors.Error(ex);
}
catch (CryptographicException ex)
{
//TODO:LogErrors.Error(ex);
}
catch (NotSupportedException ex)
{
//TODO:LogErrors.Error(ex);
}
return null;
}
/// <summary>
/// Creates a symmetric encryptor object with the current Key property and initialization vector (IV).
/// </summary>
/// <param name="clearText">The plaintext or string to be encrypted.</param>
/// <returns>The encrypted string.</returns>
public static string Encrypt(string clearText)
{
try
{
// Check arguments.
if (clearText == null || clearText.Length <= 0)
{
//TODO:LogErrors.Error(new ArgumentNullException("plainText"));
return null;
}
if (Key == null || Key.Length <= 0)
{
//TODO:LogErrors.Error(new ArgumentNullException("Key"));
return null;
}
if (IV == null || IV.Length <= 0)
{
//TODO:LogErrors.Error(new ArgumentNullException("IV"));
return null;
}
var clearBytes = Encoding.Unicode.GetBytes(clearText);
using var encryptor = Aes.Create();
var pdb = new Rfc2898DeriveBytes(Key, IV);
encryptor.Mode = CipherMode.CBC;
encryptor.KeySize = 128;
encryptor.BlockSize = 128;
encryptor.FeedbackSize = 128;
encryptor.Padding = PaddingMode.PKCS7;
encryptor.Key = pdb.GetBytes(32);
encryptor.IV = pdb.GetBytes(16);
using var ms = new MemoryStream();
using (var cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(clearBytes, 0, clearBytes.Length);
cs.Close();
}
clearText = Convert.ToBase64String(ms.ToArray());
return clearText;
}
catch (EncoderFallbackException ex)
{
//TODO:LogErrors.Error(ex);
}
catch (ArgumentNullException ex)
{
//TODO:LogErrors.Error(ex);
}
catch (ArgumentException ex)
{
//TODO:LogErrors.Error(ex);
}
catch (CryptographicException ex)
{
//TODO:LogErrors.Error(ex);
}
catch (NotSupportedException ex)
{
//TODO:LogErrors.Error(ex);
}
catch (OverflowException ex)
{
//TODO:LogErrors.Error(ex);
}
return null;
}
I'd start much closer to your original code with,
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public static class Crypto
{
public static string Decrypt(string dataToDecrypt)
{
byte[] bytesToBeDecrypted = Convert.FromBase64String(dataToDecrypt);
byte[] saltBytes = new byte[16];
using (MemoryStream ms = new MemoryStream())
{
using (Aes AES = Aes.Create())
{
AES.KeySize = 256;
AES.BlockSize = 256;
var key = new Rfc2898DeriveBytes(GetHashKeyWithSHA256(), saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
AES.Mode = CipherMode.CBC;
AES.Padding = PaddingMode.PKCS7;
using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
cs.Close();
}
return UTF8Encoding.UTF8.GetString(ms.ToArray());
}
}
}
}
First get it work, then improve it.
I'm in the middle of porting an older application to .Net 6, and have hit a stumbling block of the encryption / decryption method is now failing. It still works perfectly fine under .Net 4.x.x.
The error being thrown is,
"Padding is invalid and cannot be removed."
Code: - Updated to actual original code. This code worked fine when targeting .Net 4.7.2, however after moving the code to .Net 6.0 RC2, it started to lose anything greater than 32 chars of the decrypted string, which lead to errors elsewhere as the strings weren't complete.
For context. This was running on a webhost & a desktop client, to encrypt messages in transit. The webhost has been updated and validated to be sending the correct encrypted value (decrypting the message using the .Net 4 client is successful). However, the .Net 6 desktop client isn't decrypting it correctly and is losing characters in the decrypted string.
#region Encrypt method(s)
private const int Keysize = 256;
private const int Blocksize = 128;
private const int DerivationIterations = 1000;
public async Task<string> EncryptStringWithValidatedPadding(string plainText, string passPhrase)
{
string encrypted = null;
bool valid = false;
while (!valid)
{
encrypted = await Encrypt(plainText, passPhrase);
if (!string.IsNullOrEmpty(await Decrypt(encrypted, passPhrase)))
{
valid = true;
}
}
return encrypted;
}
private async Task<string> Encrypt(string plainText, string passPhrase)
{
var saltStringBytes = GenerateRandomEntropy(32); // 256 bits
var ivStringBytes = GenerateRandomEntropy(16); // 128 bits
byte[] plainTextBytes = Convert.FromBase64String(plainText);
using (var password = new Rfc2898DeriveBytes(Convert.FromBase64String(passPhrase), saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = new AesManaged())
{
symmetricKey.KeySize = Keysize;
symmetricKey.BlockSize = Blocksize;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
var cipherTextBytes = saltStringBytes;
cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
memoryStream.Close();
cryptoStream.Close();
var encrypted64String = Convert.ToBase64String(cipherTextBytes);
return encrypted64String;
}
}
}
}
}
}
private static byte[] GenerateRandomEntropy(int byteSize)
{
var randomBytes = new byte[byteSize];
using (var rngCsp = new RNGCryptoServiceProvider())
{
rngCsp.GetBytes(randomBytes);
}
return randomBytes;
}
#endregion
#region Decrypt method
public static async Task<string> Decrypt(string cipherText, string passPhrase)
{
try
{
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Blocksize / 8).ToArray();
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) + Blocksize / 8).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) + Blocksize / 8)).ToArray();
using (var password = new Rfc2898DeriveBytes(Convert.FromBase64String(passPhrase), saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = new AesManaged())
{
symmetricKey.KeySize = 256;
symmetricKey.BlockSize = Blocksize;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream(cipherTextBytes))
{
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
var plainTextBytes = new byte[cipherTextBytes.Length];
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
memoryStream.Close();
cryptoStream.Close();
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return null;
}
#endregion
This is called with,
encryptedString = await new EncryptDecrypt().EncryptStringWithValidatedPadding(b64String, Convert.ToBase64String(Encoding.UTF8.GetBytes(passPhrase)));
I am assuming that saving the IV should solve this, but I'm wondering if there are any obvious flaws here that I'm just not seeing.
Can anyone explain it?
Update: As suggested I've refactored the code to the below. I've also stripped it right back for the minute to ensure the underlying algo's work.
Ref: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.aes?view=net-6.0
namespace Encryption_Helper
{
public class EncryptDecrypt
{
#region Encrypt method(s)
private static byte[] bytes = new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 };
private const int Keysize = 256;
private const int Blocksize = 128;
private const int DerivationIterations = 1000;
public static async Task<string> EncryptStringWithValidatedPadding(string plainText, string passPhrase)
{
string encrypted = null;
bool valid = false;
while (!valid)
{
encrypted = await Encrypt(plainText, passPhrase);
if (!string.IsNullOrEmpty(await Decrypt(encrypted, passPhrase)))
{
valid = true;
}
}
return encrypted;
}
private static async Task<string> Encrypt(string plainText, string passPhrase)
{
using (var password = new Rfc2898DeriveBytes(Convert.FromBase64String(passPhrase), bytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
var ivBytes = password.GetBytes(Blocksize / 8);
using (var aes = Aes.Create())
{
aes.Key = keyBytes;
aes.IV = ivBytes;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
plainText = Convert.ToBase64String(msEncrypt.ToArray());
}
}
}
return plainText;
}
}
#endregion
#region Decrypt method
public static async Task<string> Decrypt(string cipherText, string passPhrase)
{
try
{
using (var password = new Rfc2898DeriveBytes(Convert.FromBase64String(passPhrase), bytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
var ivBytes = password.GetBytes(Blocksize / 8);
using (var aes = Aes.Create())
{
aes.Key = keyBytes;
aes.IV = ivBytes;
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using (var memoryStream = new MemoryStream(Convert.FromBase64String(cipherText)))
{
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(cryptoStream))
{
cipherText = srDecrypt.ReadToEnd();
}
}
}
return cipherText;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return null;
}
}
#endregion
}
}
It is still throwing a padding error!
Solved it!
public class EncryptDecrypt
{
#region Encrypt method(s)
private const int Keysize = 256;
private const int Blocksize = 128;
private const int DerivationIterations = 1000;
public static async Task<string> EncryptStringWithValidatedPadding(string plainText, string passPhrase)
{
string encrypted = null;
bool valid = false;
while (!valid)
{
encrypted = await Encrypt(plainText, passPhrase);
if (!string.IsNullOrEmpty(await Decrypt(encrypted, passPhrase)))
{
valid = true;
}
}
return encrypted;
}
private static async Task<string> Encrypt(string plainText, string passPhrase)
{
var saltStringBytes = GenerateRandomEntropy(Keysize / 8); // 256 bits
var ivStringBytes = GenerateRandomEntropy(Blocksize / 8); // 128 bits
byte[] plainTextBytes = Convert.FromBase64String(plainText);
using (var password = new Rfc2898DeriveBytes(Convert.FromBase64String(passPhrase), saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var aes = Aes.Create())
{
aes.KeySize = Keysize;
aes.BlockSize = Blocksize;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, aes.CreateEncryptor(keyBytes, ivStringBytes), CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
var cipherTextBytes = saltStringBytes;
cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
memoryStream.Close();
cryptoStream.Close();
var encrypted64String = Convert.ToBase64String(cipherTextBytes);
return encrypted64String;
}
}
}
}
}
private static byte[] GenerateRandomEntropy(int byteSize)
{
var randomBytes = new byte[byteSize];
using (var rngCsp = new RNGCryptoServiceProvider())
{
rngCsp.GetBytes(randomBytes);
}
return randomBytes;
}
#endregion
#region Decrypt method
public static async Task<string> Decrypt(string cipherText, string passPhrase)
{
try
{
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Blocksize / 8).ToArray();
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) + Blocksize / 8).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) + Blocksize / 8)).ToArray();
using (var password = new Rfc2898DeriveBytes(Convert.FromBase64String(passPhrase), saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var aes = Aes.Create())
{
aes.KeySize = Keysize;
aes.BlockSize = Blocksize;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using (var ms = new MemoryStream(cipherTextBytes))
{
using (var cs = new CryptoStream(ms, aes.CreateDecryptor(keyBytes, ivStringBytes), CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(cs))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
cipherText = srDecrypt.ReadToEnd();
}
}
}
return cipherText;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return null;
}
#endregion
After updating the decryption method, all's good in the world again.
It appears to me that .Net 6 broke the nested using loops, closing the stream before the return value was completely set.
i need to port the following C# function to Node
public static string Decrypt(string CipherText, string Password, string Salt, string InitialVector, int PasswordIterations, string HashAlgorithm = "SHA1", int KeySize = 256)
{
var InitialVectorBytes = Encoding.ASCII.GetBytes(InitialVector);
var SaltValueBytes = Encoding.ASCII.GetBytes(Salt);
var CipherTextBytes = Convert.FromBase64String(CipherText);
var DerivedPassword = new PasswordDeriveBytes(Password, SaltValueBytes, HashAlgorithm, PasswordIterations);
var KeyBytes = DerivedPassword.GetBytes(KeySize / 8);
var SymmetricKey = new RijndaelManaged();
SymmetricKey.Mode = CipherMode.CBC;
var PlainTextBytes = new byte[CipherTextBytes.Length];
var ByteCount = 0;
using (var Decryptor = SymmetricKey.CreateDecryptor(KeyBytes, InitialVectorBytes))
{
using (var MemStream = new MemoryStream(CipherTextBytes))
{
using (var CryptoStream = new CryptoStream(MemStream, Decryptor, CryptoStreamMode.Read))
{
ByteCount = CryptoStream.Read(PlainTextBytes, 0, PlainTextBytes.Length);
MemStream.Close();
CryptoStream.Close();
}
}
}
SymmetricKey.Clear();
return Encoding.UTF8.GetString(PlainTextBytes, 0, ByteCount);
}
I have been trying so far with various Node modules like mcrypt, aes-js, js-rijndael, rinjndael-js, crypto, crypto-js, cryptojs etc, but i am unable to find a way or understand how i need to approach this.
What confuses me specially is how to do the password iterations step. Can anyone suggest a node module that supports it or a way to implement it?
I am using a Rijndeal algorithm to encrypt and decrypt a string value. This method works fine in most of the cases, but in same cases on one machine I'm missing characters after decryption.
Here is my sample code
public static string Decrypt(string cipherText)
{
try
{
string incoming = cipherText.Replace('_', '/').Replace('-', '+');
switch (cipherText.Length % 4)
{
case 2: incoming += "=="; break;
case 3: incoming += "="; break;
}
byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
byte[] cipherTextBytes = Convert.FromBase64String(incoming);
PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, null);
byte[] keyBytes = password.GetBytes(keysize / 8);
RijndaelManaged symmetricKey = new RijndaelManaged();
symmetricKey.Mode = CipherMode.CBC;
ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes);
MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
byte[] plainTextBytes = new byte[cipherTextBytes.Length];
int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
memoryStream.Close();
cryptoStream.Close();
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
catch (Exception ex)
{
return "Exception";
}
}
I'm facing a issue now which need you guys help.
I use c# to do some encryption. Then need to use node.js to decry-pt it. But I just found that I can't do it correctly base on my c# encryption algorithm. If you guys have any solution, please help me.
Here is my c# encryption code:
public static string Encrypt(string text, String password, string salt, string hashAlgorithm, int passwordIterations, string initialVector, int keySize)
{
if (string.IsNullOrEmpty(text))
return "";
var initialVectorBytes = Encoding.ASCII.GetBytes(initialVector);
var saltValueBytes = Encoding.ASCII.GetBytes(salt);
var plainTextBytes = Encoding.UTF8.GetBytes(text);
var derivedPassword = new PasswordDeriveBytes(password, saltValueBytes, hashAlgorithm, passwordIterations);
var keyBytes = derivedPassword.GetBytes(keySize / 8);
var symmetricKey = new RijndaelManaged();
symmetricKey.Mode = CipherMode.CBC;
byte[] cipherTextBytes = null;
using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, initialVectorBytes))
{
using (var memStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
cipherTextBytes = memStream.ToArray();
memStream.Close();
cryptoStream.Close();
}
}
}
symmetricKey.Clear();
return Convert.ToBase64String(cipherTextBytes);
}
public static string Decrypt(string text, String password, string salt, string hashAlgorithm, int passwordIterations, string initialVector, int keySize)
{
if (string.IsNullOrEmpty(text))
return "";
var initialVectorBytes = Encoding.ASCII.GetBytes(initialVector);
var saltValueBytes = Encoding.ASCII.GetBytes(salt);
var cipherTextBytes = Convert.FromBase64String(text);
var derivedPassword = new PasswordDeriveBytes(password, saltValueBytes, hashAlgorithm, passwordIterations);
var keyBytes = derivedPassword.GetBytes(keySize / 8);
var symmetricKey = new RijndaelManaged();
symmetricKey.Mode = CipherMode.CBC;
var plainTextBytes = new byte[cipherTextBytes.Length];
var byteCount = 0;
using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, initialVectorBytes))
{
using (var memStream = new MemoryStream(cipherTextBytes))
{
using (var cryptoStream = new CryptoStream(memStream, decryptor, CryptoStreamMode.Read))
{
byteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
memStream.Close();
cryptoStream.Close();
}
}
}
symmetricKey.Clear();
return Encoding.UTF8.GetString(plainTextBytes, 0, byteCount);
}
If anyone can give me a same function for nodejs, that's will be really help. Anyway, thanks for read this post.
First, we'll do the PasswordDeriveBytes function with Node.js. This is crypto.pbkdf2:
// assumes HMAC-SHA1
crypto.pbkdf2(password, salt, iterations, keySize / 8, function(err, key) {
if(err) /* handle error */
// ...
});
Next, use crypto.createDecipheriv to create a decryptor:
// find algorithm from the available ciphers; see crypto.getCiphers()
var decipher = crypto.createDecipheriv(/* algorithm */, key, initialVector);
Then use decipher.update and decipher.final to feed it the data. They will return portions of decrypted data to you.
The problem is, that PasswordDeriveBytes does not implement pbkdf2 but a modified version of pbkdf1 (PKCS #5 v2.1). See this
what-is-the-algorithm-behind-passwordderivebytes
for more information.
Note: The defaults of the PasswordDeriveBytes constructor are iterations = 100 and hashAlgorithm = "sha1"
Here is my approach to implement this algorithm in javascript/typescript:
export function deriveBytesFromPassword(password: string, salt: Buffer, iterations: number, hashAlgorithm: string, keyLength: number) {
if (keyLength < 1) throw new Error("keyLength must be greater than 1")
if (iterations < 2) throw new Error("iterations must be greater than 2")
const passwordWithSalt = Buffer.concat([Buffer.from(password, "utf-8"), salt])
const hashMissingLastIteration = hashKeyNTimes(passwordWithSalt, iterations - 1, hashAlgorithm)
let result = hashKeyNTimes(hashMissingLastIteration, 1, hashAlgorithm)
result = extendResultIfNeeded(result, keyLength, hashMissingLastIteration, hashAlgorithm)
return result.slice(0, keyLength)
}
function hashKeyNTimes(key: Buffer, times: number, hashAlgorithm: string): Buffer {
let result = key
for (let i = 0; i < times; i++) {
result = crypto.createHash(hashAlgorithm).update(result).digest()
}
return result
}
function extendResultIfNeeded(result: Buffer, keyLength: number, hashMissingLastIteration: Buffer, hashAlgorithm: string): Buffer {
let counter = 1
while (result.length < keyLength) {
result = Buffer.concat([result, calculateSpecialMicrosoftHash(hashMissingLastIteration, counter, hashAlgorithm)])
counter++
}
return result
}
function calculateSpecialMicrosoftHash(hashMissingLastIteration: Buffer, counter: number, hashAlgorithm: string): Buffer {
// Here comes the magic: Convert an integer that increases from call to call to a string
// and convert that string to utf-8 bytes. These bytes are than used to slightly modify a given base-hash.
// The modified hash is than piped through the hash algorithm.
// Note: The PasswordDeriveBytes algorithm converts each character to utf-16 and then drops the second byte.
const prefixCalculatedByCounter = Buffer.from(counter.toString(), "utf-8")
const inputForAdditionalHashIteration = Buffer.concat([prefixCalculatedByCounter, hashMissingLastIteration])
return crypto.createHash(hashAlgorithm).update(inputForAdditionalHashIteration).digest()
}
Having the exact same scenario I was able to get to a successful "C# encrypt => Node decrypt" solution using the code provided by #icktoofay above, but with the PasswordDeriveBytes replaced with Rfc2898DeriveBytes
My code is roughly:
C#
private byte[] saltBytes = ASCIIEncoding.ASCII.GetBytes(salt);
public string Encrypt<T>(string value, string password) where T: SymmetricAlgorithm, new() {
byte[] valueBytes = UTF8Encoding.UTF8.GetBytes(value);
byte[] encrypted = null;
using (T cipher = new T()) {
var db = new Rfc2898DeriveBytes(password, saltBytes);
db.IterationCount = iterationsConst;
var key = db.GetBytes(keySizeConst / 8);
cipher.Mode = CipherMode.CBC;
using (ICryptoTransform encryptor = cipher.CreateEncryptor(key, vectorBytes)) {
using (MemoryStream ms = new MemoryStream()) {
using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) {
cs.Write(valueBytes, 0, valueBytes.Length);
cs.FlushFinalBlock();
encrypted = ms.ToArray();
}
}
}
cipher.Clear();
}
return Convert.ToBase64String(encrypted);
}
JavaScript:
var crypto = require('crypto');
var base64 = require('base64-js');
var algorithm = 'AES-256-CBC';
[...]
var saltBuffer = new Buffer(salt);
var passwordBuffer = new Buffer(password);
[...]
var encodedBuffer = new Buffer(base64.toByteArray(encryptedStringBase64Encoded));
crypto.pbkdf2(passwordBuffer, saltBuffer, iterations, keySize / 8, function(err, key) {
var decipher = crypto.createDecipheriv(algorithm, key, iv);
var dec = Buffer.concat([decipher.update(encodedBuffer), decipher.final()]);
return dec;
});
and is actually a combination of a few examples I found on the Internet.
Because I had a problem with the Buffer's implementation of Base64 in some specific cases ('+' sign at the beginning of the encoded string), I used base64-js from https://github.com/beatgammit/base64-js, which seems to work fine.