c# - ¿Por qué una contraseña incorrecta causa "el relleno no es válido y no se puede eliminar"?
.net exception (9)
Necesitaba un cifrado simple de cadenas, así que escribí el siguiente código (con una gran cantidad de "inspiración" a partir de here ):
// create and initialize a crypto algorithm
private static SymmetricAlgorithm getAlgorithm(string password) {
SymmetricAlgorithm algorithm = Rijndael.Create();
Rfc2898DeriveBytes rdb = new Rfc2898DeriveBytes(
password, new byte[] {
0x53,0x6f,0x64,0x69,0x75,0x6d,0x20, // salty goodness
0x43,0x68,0x6c,0x6f,0x72,0x69,0x64,0x65
}
);
algorithm.Padding = PaddingMode.ISO10126;
algorithm.Key = rdb.GetBytes(32);
algorithm.IV = rdb.GetBytes(16);
return algorithm;
}
/*
* encryptString
* provides simple encryption of a string, with a given password
*/
public static string encryptString(string clearText, string password) {
SymmetricAlgorithm algorithm = getAlgorithm(password);
byte[] clearBytes = System.Text.Encoding.Unicode.GetBytes(clearText);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, algorithm.CreateEncryptor(), CryptoStreamMode.Write);
cs.Write(clearBytes, 0, clearBytes.Length);
cs.Close();
return Convert.ToBase64String(ms.ToArray());
}
/*
* decryptString
* provides simple decryption of a string, with a given password
*/
public static string decryptString(string cipherText, string password) {
SymmetricAlgorithm algorithm = getAlgorithm(password);
byte[] cipherBytes = Convert.FromBase64String(cipherText);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, algorithm.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipherBytes, 0, cipherBytes.Length);
cs.Close();
return System.Text.Encoding.Unicode.GetString(ms.ToArray());
}
El código parece funcionar bien, excepto que al descifrar datos con una clave incorrecta, obtengo una CryptographicException - "El relleno no es válido y no se puede eliminar" - en la línea cs.Close () en decryptString.
código de ejemplo:
string password1 = "password";
string password2 = "letmein";
string startClearText = "The quick brown fox jumps over the lazy dog";
string cipherText = encryptString(startClearText, password1);
string endClearText = decryptString(cipherText, password2); // exception thrown
Mi pregunta es, ¿es esto esperado? Hubiera pensado que descifrar con la contraseña incorrecta solo daría como resultado una salida sin sentido, en lugar de una excepción.
Aunque esto ya se ha respondido, creo que sería una buena idea explicar por qué es de esperar.
Generalmente, se aplica un esquema de relleno porque la mayoría de los filtros criptográficos no son semánticamente seguros y para evitar algunas formas de criptoataques. Por ejemplo, generalmente en RSA se usa el esquema de relleno OAEP que previene algunos tipos de ataques (como un ataque de texto plano o blinding ).
Un esquema de relleno agrega algo de basura (normalmente) al azar al mensaje m antes de enviar el mensaje. En el método OAEP, por ejemplo, se usan dos oráculos (esta es una explicación simplista):
- Dado el tamaño del módulo, rellena k1 bits con 0 y k0 bits con un número aleatorio.
- Luego, aplicando alguna transformación al mensaje, obtienes el mensaje acolchado que se cifra y se envía.
Eso le proporciona una asignación al azar para los mensajes y una forma de comprobar si el mensaje es basura o no. Como el esquema de relleno es reversible, cuando descifras el mensaje mientras que no puedes decir nada sobre la integridad del mensaje en sí mismo, puedes, de hecho, hacer una afirmación sobre el relleno y así puedes saber si el mensaje ha sido descifrado correctamente. o estás haciendo algo mal (es decir, alguien ha manipulado el mensaje o estás usando la clave incorrecta)
Experimenté un similar "El relleno no es válido y no se puede eliminar". excepción, pero en mi caso la clave IV y el relleno fueron correctos.
Resultó que enjuagar la secuencia de cifrado es todo lo que faltaba.
Me gusta esto:
MemoryStream msr3 = new MemoryStream();
CryptoStream encStream = new CryptoStream(msr3, RijndaelAlg.CreateEncryptor(), CryptoStreamMode.Write);
encStream.Write(bar2, 0, bar2.Length);
// unless we flush the stream we would get "Padding is invalid and cannot be removed." exception when decoding
encStream.FlushFinalBlock();
byte[] bar3 = msr3.ToArray();
La respuesta actualizada por el usuario "atconway" funcionó para mí.
El problema no fue con el relleno, sino con la clave, que fue diferente durante el cifrado y el descifrado. La clave y iv deberían ser las mismas durante la codificación y el descifrado del mismo valor.
Otra razón de la excepción podría ser una condición de carrera entre varios subprocesos utilizando la lógica de descifrado: las implementaciones nativas de ICryptoTransform no son seguras para subprocesos (por ejemplo, SymmetricAlgorithm), por lo que se deben poner en una sección exclusiva, por ejemplo, mediante el bloqueo . Consulte aquí para obtener más detalles: http://www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/
Puede haber algunos bytes no leídos en CryptoStream. Cerrar antes de leer la secuencia completamente estaba causando el error en mi programa.
Sí, esto es de esperar, o al menos, es exactamente lo que sucede cuando nuestras rutinas criptográficas obtienen datos no descifrables
Si desea que su uso sea correcto, debe agregar authentication a su texto cifrado para que pueda verificar que sea la contraseña correcta o que el texto cifrado no se haya modificado. El relleno que está utilizando ISO10126 solo generará una excepción si el último byte no se descifra como uno de los 16 valores válidos para el relleno (0x01-0x10). Entonces, tienes una oportunidad 1/16 de NO lanzar la excepción con la contraseña incorrecta, donde si la autenticas tienes una forma determinística de saber si tu descifrado es válido.
Usar crypto api mientras parece fácil, en realidad es bastante fácil cometer errores. Por ejemplo, utiliza una sal fija para su clave y derivación iv, lo que significa que cada texto cifrado con la misma contraseña reutilizará IV con esa clave, que rompe la seguridad semántica con el modo CBC, el IV debe ser impredecible y exclusivo para una clave dada.
Por ese motivo de errores fáciles de cometer, tengo un fragmento de código, que trato de mantener revisado y actualizado (comentarios, preguntas bienvenidas):
Ejemplos modernos de encriptación autenticada simétrica de una cadena C #.
Si usa su AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password)
cuando se usa la contraseña incorrecta, se devuelve null
, si el texto cifrado o iv se ha modificado después de que se devuelva el cifrado null
, nunca obtendrá datos basura, o una excepción de relleno.
Si ha descartado el desajuste de clave, además de FlushFinalBlock()
(vea la respuesta de Yaniv), también será suficiente con llamar a Close()
en CryptoStream
.
Si está limpiando recursos estrictamente con el using
bloques, asegúrese de anidar el bloque para CryptoStream
sí:
using (MemoryStream ms = new MemoryStream())
using (var enc = RijndaelAlg.CreateEncryptor())
{
using (CryptoStream encStream = new CryptoStream(ms, enc, CryptoStreamMode.Write))
{
encStream.Write(bar2, 0, bar2.Length);
} // implicit close
byte[] encArray = ms.ToArray();
}
Me ha mordido esto (o similar):
using (MemoryStream ms = new MemoryStream())
using (var enc = RijndaelAlg.CreateEncryptor())
using (CryptoStream encStream = new CryptoStream(ms, enc, CryptoStreamMode.Write))
{
encStream.Write(bar2, 0, bar2.Length);
byte[] encArray = ms.ToArray();
} // implicit close -- too late!
Tuve un problema similar, el problema en el método de descifrado era inicializar una secuencia de memoria vacía. cuando funcionó cuando lo inicialicé con la matriz de bytes de texto de cifrado como esta:
MemoryStream ms = new MemoryStream(cipherText)