c# - suma - Buena práctica de vectores de inicialización AES
vectores c# (6)
por mi pregunta Cifrado de Aes ... me falta una pieza importante , ahora he aprendido que mi suposición de crear una encriptación reversible en una cadena estaba un poco desajustada. Ahora tengo
public static byte[] EncryptString(string toEncrypt, byte[] encryptionKey)
{
var toEncryptBytes = Encoding.UTF8.GetBytes(toEncrypt);
using (var provider = new AesCryptoServiceProvider())
{
provider.Key = encryptionKey;
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using (var encryptor = provider.CreateEncryptor(provider.Key, provider.IV))
{
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(toEncryptBytes, 0, toEncryptBytes.Length);
cs.FlushFinalBlock();
}
return ms.ToArray();
}
}
}
}
y esto produce resultados consistentes; sin embargo, no podré descifrar sin conocer / configurar el vector de inicialización. Realmente no quiero pasar tres valores en este método (activado para el IV), lo que me deja con el hardcoding IV o derivarlo de la clave. Me gustaría saber si esta es una buena práctica, o si hará que el valor encriptado sea vulnerable al ataque de alguna manera ... ¿o estoy realmente pensando demasiado y debería codificar el IV?
ACTUALIZAR Por sugerencia de Iridium, intenté algo como esto en su lugar:
public static byte[] EncryptString(string toEncrypt, byte[] encryptionKey)
{
if (string.IsNullOrEmpty(toEncrypt)) throw new ArgumentException("toEncrypt");
if (encryptionKey == null || encryptionKey.Length == 0) throw new ArgumentException("encryptionKey");
var toEncryptBytes = Encoding.UTF8.GetBytes(toEncrypt);
using (var provider = new AesCryptoServiceProvider())
{
provider.Key = encryptionKey;
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using (var encryptor = provider.CreateEncryptor(provider.Key, provider.IV))
{
using (var ms = new MemoryStream())
{
ms.Write(provider.IV, 0, 16);
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(toEncryptBytes, 0, toEncryptBytes.Length);
cs.FlushFinalBlock();
}
return ms.ToArray();
}
}
}
}
public static string DecryptString(byte[] encryptedString, byte[] encryptionKey)
{
using (var provider = new AesCryptoServiceProvider())
{
provider.Key = encryptionKey;
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
using (var ms = new MemoryStream(encryptedString))
{
byte[] buffer;
ms.Read(buffer, 0, 16);
provider.IV = buffer;
using (var decryptor = provider.CreateDecryptor(provider.Key, provider.IV))
{
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
byte[] decrypted = new byte[encryptedString.Length];
var byteCount = cs.Read(decrypted, 0, encryptedString.Length);
return Encoding.UTF8.GetString(decrypted, 0, byteCount);
}
}
}
}
}
sin embargo, esto muestra algo extraño en mi prueba de unidad:
[TestMethod]
public void EncryptionClosedLoopTest()
{
var roundtrip = "This is the data I am encrypting. There are many like it but this is my encryption.";
var encrypted = Encryption.EncryptString(roundtrip, encryptionKey);
var decrypted = Encryption.DecryptString(encrypted, encryptionKey);
Assert.IsTrue(roundtrip == decrypted);
}
mi texto descifrado aparece como "92ʪ F" , hpv0 Estoy encriptando. Hay muchos similares, pero esta es mi encriptación ", que parece casi correcta, pero por supuesto totalmente incorrecta. Parece que estoy cerca. ¿Me falta un desplazamiento en la secuencia de la memoria?
Al cifrar, genere su IV y colóquela previamente en el texto de cifrado (algo como esto)
using (var aes= new AesCryptoServiceProvider()
{
Key = PrivateKey,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
})
{
var input = Encoding.UTF8.GetBytes(originalPayload);
aes.GenerateIV();
var iv = aes.IV;
using (var encrypter = aes.CreateEncryptor(aes.Key, iv))
using (var cipherStream = new MemoryStream())
{
using (var tCryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
using (var tBinaryWriter = new BinaryWriter(tCryptoStream))
{
//Prepend IV to data
//tBinaryWriter.Write(iv); This is the original broken code, it encrypts the iv
cipherStream.Write(iv); //Write iv to the plain stream (not tested though)
tBinaryWriter.Write(input);
tCryptoStream.FlushFinalBlock();
}
string encryptedPayload = Convert.ToBase64String(cipherStream.ToArray());
}
}
Al descifrar esto, obtenga los primeros 16 bytes y utilícelos en la transmisión criptográfica
var aes= new AesCryptoServiceProvider()
{
Key = PrivateKey,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
};
//get first 16 bytes of IV and use it to decrypt
var iv = new byte[16];
Array.Copy(input, 0, iv, 0, iv.Length);
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, aes.CreateDecryptor(aes.Key, iv), CryptoStreamMode.Write))
using (var binaryWriter = new BinaryWriter(cs))
{
//Decrypt Cipher Text from Message
binaryWriter.Write(
input,
iv.Length,
input.Length - iv.Length
);
}
return Encoding.Default.GetString(ms.ToArray());
}
El IV debe ser aleatorio y exclusivo para cada ejecución de su método de cifrado. Derivarlo de la clave / mensaje o de la codificación no es lo suficientemente seguro. El IV se puede generar dentro de este método, en lugar de pasarse a él, y escribirse en la secuencia de salida antes de los datos cifrados.
Al descifrar, el IV se puede leer desde la entrada antes de los datos cifrados.
En mi caso, para generar el IV, uso algo como esto
/// <summary>
/// Derives password bytes
/// </summary>
/// <param name="Password">password</param>
/// <returns>derived bytes</returns>
private Rfc2898DeriveBytes DerivePass(string Password)
{
byte[] hash = CalcHash(Password);
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(Password, hash, _KEY_ITER);
return pdb;
}
/// <summary>
/// calculates the hash of the given string
/// </summary>
/// <param name="buffer">string to hash</param>
/// <returns>hash value (byte array)</returns>
private byte[] CalcHash(string buffer)
{
RIPEMD160 hasher = RIPEMD160.Create();
byte[] data = Encoding.UTF8.GetBytes(buffer);
return hasher.ComputeHash(data);
}
es decir, calculo el hash de contraseña usando RIPEMD160 y lo uso para generar los bytes derivados, en ese punto, cuando se trata de inicializar el cifrado / descifrado, simplemente uso algo como esto
Rfc2898DeriveBytes pdb = DerivePass(Password);
SymmetricAlgorithm alg = _engine;
alg.Key = pdb.GetBytes(_keySize);
alg.IV = pdb.GetBytes(_IVSize);
No sé si es "correcto" (probablemente los gurús de criptografía aquí me dispararán: D), pero, al menos, me da una IV decente y no tengo que almacenarla "en algún lugar" desde que ingresé al la contraseña correcta devolverá el valor IV necesario; como nota, la _engine en el ejemplo anterior se declara como "SymmetricAlgorithm" y se inicializa usando algo como esto
_engine = Rijndael.Create();
_keySize = (_engine.KeySize / 8);
_IVSize = (_engine.BlockSize / 8);
que crea los objetos de criptografía deseados e inicializa la clave y los tamaños IV
Modifiqué tu método de descifrado de la siguiente manera y funciona:
public static string DecryptString(byte[] encryptedString, byte[] encryptionKey)
{
using (var provider = new AesCryptoServiceProvider())
{
provider.Key = encryptionKey;
using (var ms = new MemoryStream(encryptedString))
{
// Read the first 16 bytes which is the IV.
byte[] iv = new byte[16];
ms.Read(iv, 0, 16);
provider.IV = iv;
using (var decryptor = provider.CreateDecryptor())
{
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
using (var sr = new StreamReader(cs))
{
return sr.ReadToEnd();
}
}
}
}
}
}
El problema con su implementación es que está leyendo demasiados bytes en CryptoStream
. Realmente necesitas leer encryptedText.Length - 16
. El uso de StreamReader
simplifica esto, ya que ya no necesita preocuparse por las compensaciones en ningún lado.
Para generar IV aleatorio, necesitarías un número verdaderamente aleatorio. Cualquiera que sea la API específica del idioma que use para generar el número aleatorio, debe generar un número aleatorio verdadero. Tanto Android como iOS tienen apis que generan números aleatorios basados en datos del sensor.
Recientemente implementé AES 256 con IV aleatorio (generado con números realmente aleatorios) y clave hash. Para una implementación más segura (aleatorio IV + clave hash) multiplataforma (android, ios, c #) de AES, consulte mi respuesta aquí - https://.com/a/24561148/2480840
Para resolver la configuración de IV en el proveedor (como señaló Iridium):
ms.Read(provider.IV, 0, 16);
Agregué lo siguiente a tu código:
var iv = new byte[provider.IV.Length];
memoryStream.Read(iv, 0, provider.IV.Length);
using (var decryptor = provider.CreateDecryptor(key, iv);
concedido, el proveedor no establece mi clave en cada ejecución. Lo generé una vez y luego lo almacené. El IV se genera al azar fuera del proveedor para cada encriptación.