Created
January 27, 2020 11:16
-
-
Save abodalevsky/766f2fcc2fa2124d7458292a58666f3c to your computer and use it in GitHub Desktop.
AES encrypt/decrypt compatible with redux-persist-transform-encrypt (crypto.js) lib
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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