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

StackOverflow https://stackoverflow.com/questions/11762

  •  08-06-2019
  •  | 
  •  

Pregunta

Necesitaba un cifrado de cadena simple, así que escribí el siguiente código (con mucha "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, aparece 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 de esperar esto?Habría pensado que descifrar con la contraseña incorrecta simplemente daría como resultado resultados sin sentido, en lugar de una excepción.

¿Fue útil?

Solución

Aunque esto ya ha sido respondido, creo que sería una buena idea explicarlo. por qué Es de esperarse.

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, normalmente en RSA el OAEP Se utiliza un esquema de relleno que evita algunos tipos de ataques (como un ataque de texto sin formato elegido o cegador).

Un esquema de relleno agrega algo de basura (generalmente) aleatoria al mensaje m antes de enviarlo.En el método OAEP, por ejemplo, se utilizan dos Oráculos (ésta es una explicación simplista):

  1. Dado el tamaño del módulo, rellena 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 cifra y se envía.

Eso le proporciona una aleatorización de 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, aunque 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 estás haciendo algo mal (es decir, alguien ha alterado el mensaje o estás usando la clave incorrecta)

Otros consejos

Experimenté un "relleno 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 lo único que faltaba era vaciar el flujo criptográfico.

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

Si desea que su uso sea correcto, debe agregar autenticación a su texto cifrado para que pueda verificar que sea la contraseña correcta o que el texto cifrado no haya sido modificado.El relleno que estás usando 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).Por lo tanto, tiene una probabilidad de 1/16 de que NO arroje la excepción con la contraseña incorrecta, donde si la autentica tiene una forma determinista de saber si su descifrado es válido.

Usar API de criptomonedas, aunque parezca fácil, en realidad es bastante fácil cometer errores.Por ejemplo, usa 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, lo 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, es fácil cometer errores, tengo un fragmento de código que trato de mantener revisado y actualizado (comentarios, problemas bienvenidos):

Ejemplos modernos de cifrado autenticado simétrico de una cadena C#.

Si lo usas es AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password) cuando se utiliza la contraseña incorrecta, null se devuelve, si el texto cifrado o iv se ha modificado después del cifrado null se devuelve, nunca recuperará datos basura o una excepción de relleno.

Si ha descartado la discrepancia de claves, además FlushFinalBlock() (ver la respuesta de Yaniv), llamando Close() sobre el CryptoStream también será suficiente.

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

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 picado 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!

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

Otro motivo de la excepción podría ser una condición de carrera entre varios subprocesos que utilizan lógica de descifrado: las implementaciones nativas de ICryptoTransform son no es seguro para subprocesos (p.ej.SymmetricAlgorithm), por lo que debe colocarse en la sección exclusiva, p.usando cerrar.Consulte aquí para obtener más detalles: http://www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/

Es posible que haya algunos bytes sin leer en CryptoStream.Cerrar antes de leer la transmisión por completo estaba causando el error en mi programa.

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

MemoryStream ms = new MemoryStream(cipherText)

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

El problema no estaba en el relleno sino en la clave, que era diferente durante el cifrado y el descifrado.La clave y el iv deben ser los mismos al cifrar y descifrar el mismo valor.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top