Skip to content

Instantly share code, notes, and snippets.

@abodalevsky
Created January 27, 2020 11:16
Show Gist options
  • Save abodalevsky/766f2fcc2fa2124d7458292a58666f3c to your computer and use it in GitHub Desktop.
Save abodalevsky/766f2fcc2fa2124d7458292a58666f3c to your computer and use it in GitHub Desktop.
AES encrypt/decrypt compatible with redux-persist-transform-encrypt (crypto.js) lib
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace Common.OpenSsl
{
/// <summary>
/// Implements OpenSSL compatible AES standard encrypt / decrypt algorithm
/// Mode = CipherMode.CBC,
/// KeySize = 256,
/// BlockSize = 128,
/// Padding = PaddingMode.PKCS7
/// </summary>
public static class AES
{
private static readonly string SaltedHeader = "Salted__";
/// <summary>
/// Decrypts data with passphrase. Expected that data to decrypt is text.
/// </summary>
/// <param name="encrypted">data that should be decrypted in BASE64 format.</param>
/// <param name="passphrase">PassPhrase that was used during encryption.</param>
/// <returns>Decrypted data (text).</returns>
public static string Decrypt(string encrypted, string passphrase)
{
var encryptedBytesWithSalt = Convert.FromBase64String(encrypted);
// extract salt (first 8 bytes of encrypted)
var salt = new byte[8];
var encryptedBytes = new byte[encryptedBytesWithSalt.Length - salt.Length - SaltedHeader.Length];
Buffer.BlockCopy(encryptedBytesWithSalt, SaltedHeader.Length, salt, 0, salt.Length);
Buffer.BlockCopy(encryptedBytesWithSalt, salt.Length + SaltedHeader.Length, encryptedBytes, 0, encryptedBytes.Length);
// get key and iv
byte[] key, iv;
DeriveKeyAndIV(passphrase, salt, out key, out iv);
return DecryptStringFromBytesAes(encryptedBytes, key, iv);
}
/// <summary>
/// Encrypts data with passphrase.
/// </summary>
/// <param name="text">Data (text) to be encrypted.</param>
/// <param name="passphrase">PassPhrase that will be used during decryption.</param>
/// <returns>Encrypted data in BASE64 format.</returns>
public static string Encrypt(string text, string passphrase)
{
// generate salt
byte[] key, iv;
var salt = new byte[8];
var rng = new RNGCryptoServiceProvider();
rng.GetNonZeroBytes(salt);
DeriveKeyAndIV(passphrase, salt, out key, out iv);
// encrypt bytes
var encryptedBytes = EncryptStringToBytesAes(text, key, iv);
// add salt as first 8 bytes
var encryptedBytesWithSalt = new byte[salt.Length + encryptedBytes.Length + SaltedHeader.Length];
Buffer.BlockCopy(Encoding.ASCII.GetBytes(SaltedHeader), 0, encryptedBytesWithSalt, 0, SaltedHeader.Length);
Buffer.BlockCopy(salt, 0, encryptedBytesWithSalt, SaltedHeader.Length, salt.Length);
Buffer.BlockCopy(encryptedBytes, 0, encryptedBytesWithSalt, salt.Length + SaltedHeader.Length, encryptedBytes.Length);
// base64 encode
return Convert.ToBase64String(encryptedBytesWithSalt);
}
private static void DeriveKeyAndIV(string passphrase, byte[] salt, out byte[] key, out byte[] iv)
{
// generate key and iv
var concatenatedHashes = new List<byte>(48);
var password = Encoding.UTF8.GetBytes(passphrase);
var currentHash = new byte[0];
using (var md5 = MD5.Create())
{
var enoughBytesForKey = false;
// See http://www.openssl.org/docs/crypto/EVP_BytesToKey.html#KEY_DERIVATION_ALGORITHM
while (!enoughBytesForKey)
{
var preHashLength = currentHash.Length + password.Length + salt.Length;
var preHash = new byte[preHashLength];
Buffer.BlockCopy(currentHash, 0, preHash, 0, currentHash.Length);
Buffer.BlockCopy(password, 0, preHash, currentHash.Length, password.Length);
Buffer.BlockCopy(salt, 0, preHash, currentHash.Length + password.Length, salt.Length);
currentHash = md5.ComputeHash(preHash);
concatenatedHashes.AddRange(currentHash);
if (concatenatedHashes.Count >= 48)
enoughBytesForKey = true;
}
key = new byte[32];
iv = new byte[16];
concatenatedHashes.CopyTo(0, key, 0, 32);
concatenatedHashes.CopyTo(32, iv, 0, 16);
}
}
private static string DecryptStringFromBytesAes(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");
}
// Create a RijndaelManaged object with the specified key and IV.
using (var aes = new RijndaelManaged { Mode = CipherMode.CBC, KeySize = 256, BlockSize = 128, Key = key, IV = iv, Padding = PaddingMode.PKCS7 })
{
// Create a decrytor to perform the stream transform.
var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
// Create the streams used for decryption.
using (var msDecrypt = new MemoryStream(cipherText))
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
using (var srDecrypt = new StreamReader(csDecrypt))
{
return srDecrypt.ReadToEnd();
}
}
}
private static byte[] EncryptStringToBytesAes(string plainText, byte[] key, byte[] iv)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
{
throw new ArgumentNullException("plainText");
}
if (key == null || key.Length <= 0)
{
throw new ArgumentNullException("key");
}
if (iv == null || iv.Length <= 0)
{
throw new ArgumentNullException("iv");
}
// Create a RijndaelManaged object with the specified key and IV.
using (var aes = new RijndaelManaged { Mode = CipherMode.CBC, KeySize = 256, BlockSize = 128, Key = key, IV = iv, Padding = PaddingMode.PKCS7 })
{
// Create an encryptor to perform the stream transform.
var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
// Create the streams used for encryption.
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
using (var swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
swEncrypt.Flush();
}
return msEncrypt.ToArray();
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment