¿Por qué una contraseña incorrecta causa que "el relleno no es válido y no se puede eliminar"?


Necesitaba un cifrado de cadena simple, así que escribí el siguiente código (con una gran cantidad de "inspiración" de aquí):

    // 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 excepción criptográfica - "El relleno no es válido y no se puede eliminar" - en el cs.Cerrar() línea 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 de esperar? Hubiera pensado que descifrar con el mal la contraseña solo resultaría en una salida sin sentido, en lugar de una excepción.

Author: jbtule, 2008-08-15

9 answers

Aunque esto ya ha sido contestado, 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 cryptoatacks. Por ejemplo, normalmente en RSA se usa el esquema de relleno OAEP que evita algunos tipos de ataques (como un ataque de texto plano elegido o cegamiento).

Un esquema de relleno añade algunos (normalmente) basura aleatoria al mensaje m antes de que se envíe el mensaje. En el método OAEP, por ejemplo, se utilizan dos oráculos (esta es una explicación simplista):

  1. Dado el tamaño del módulo que padd k1 bits con 0 y k0 bits con un número aleatorio.
  2. Luego, al aplicar alguna transformación al mensaje, se obtiene el mensaje acolchado que se encripta y se envía.

Que le proporciona una aleatorización para los mensajes y una forma de probar si el mensaje es basura o no. Como el esquema de relleno es reversible, cuando descifra el mensaje, mientras que no puede decir nada sobre la integridad del mensaje en sí, puede, de hecho, hacer alguna afirmación sobre el relleno y, por lo tanto, puede saber si el mensaje se ha descifrado correctamente o si está haciendo algo mal (es decir, alguien ha manipulado el mensaje o está utilizando la clave incorrecta)

 26
Author: Jorge Córdoba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-25 15:46:00

Experimenté un "Padding" similar no es válido y no se puede eliminar."excepción, pero en mi caso la clave IV y el relleno eran correctos.

Resultó que el lavado de la corriente crypto es todo lo que faltaba.

Así:

            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();
 16
Author: Yaniv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-07 11:44:56

Si desea que su uso sea correcto, debe agregar authentication a su texto cifrado para que pueda verificar que es la palabra clave correcta o que el texto cifrado no se ha modificado. El relleno que está utilizando ISO10126 solo lanzará una excepción si el último byte no se descifra como uno de los 16 valores válidos para el relleno (0x01-0x10). Así que usted tiene un 1/16 de oportunidad de NO lanzar la excepción con la contraseña incorrecta, donde si autenticar una forma determinista de saber si su descifrado es válido.

El uso de crypto api aunque aparentemente fácil, en realidad es más bien fácil de cometer errores. Por ejemplo, utiliza una sal fija para su clave y derivación iv, eso significa que cada texto cifrado cifrado con la misma contraseña reutilizará su IV con esa clave, que rompe la seguridad semántica con el modo CBC, el IV debe ser impredecible y único para una clave dada.

Por esa razón de fácil cometer errores, tengo un código fragmento, que trato de mantener revisado y actualizado (comentarios, problemas bienvenidos):

Ejemplos Modernos de Cifrado Simétrico Autenticado de una cadena C#.

Si utiliza it's AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password) cuando se utiliza la contraseña incorrecta, null se devuelve, si el texto cifrado o iv ha sido modificado post encryption null se devuelve, nunca obtendrá datos no deseados, o una excepción de relleno.

 5
Author: jbtule,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:11

Sí, esto es de esperar, o al menos, es exactamente lo que sucede cuando nuestras rutinas criptográficas obtienen datos no descifrables

 3
Author: David Wengier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-14 23:25:22

Si has descartado la falta de coincidencia de teclas, entonces además de FlushFinalBlock() (ver la respuesta de Yaniv), llamar Close() en el CryptoStream también será suficiente.

Si está limpiando recursos estrictamente con using bloques, asegúrese de anidar el bloque para el propio CryptoStream:

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();
}

He sido mordido por 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!
 3
Author: Marc L.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-13 10:40:01

Otra razón de la excepción podría ser una condición de carrera entre varios subprocesos usando lógica de descifrado - las implementaciones nativas de ICryptoTransform son no thread-safe (por ejemplo, SymmetricAlgorithm), por lo que debe ser puesto a la sección exclusiva, por ejemplo, usando lock. Por favor refiérase aquí para más detalles: http://www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety /

 2
Author: Denis Tikhomirov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-10 10:43:12

Puede haber algunos bytes no leídos en el CryptoStream. Cerrar antes de leer la secuencia completamente estaba causando el error en mi programa.

 1
Author: R D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-04 09:16:27

Tuve un problema similar, el problema en el método de descifrado fue inicializar un flujo de memoria vacía. cuando funcionó cuando lo inicialicé con la matriz de bytes de texto cifrado como esta:

MemoryStream ms = new MemoryStream(cipherText)
 0
Author: Mina Samy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-02 12:13:46

La respuesta actualizada por el usuario "atconway" funcionó para mí.

El problema no era con el relleno, sino con la clave, que era diferente durante el cifrado y el descifrado. La clave y el iv deben ser los mismos durante la codificación y descifrado del mismo valor.

 -2
Author: RoopzD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-15 20:12:47