2008-08-14 18 views
39

Necesitaba un poco de cifrado de cadena sencilla, por lo que escribió el siguiente código (con una gran cantidad de "inspiración" de here):¿Por qué causa una contraseña incorrecta "El relleno no es válido y no se puede eliminar"?

// 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 cuando el descifrado de datos con una incorrecta clave, 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 

está, se espera que esto sea mi pregunta? Hubiera pensado que descifrar con la contraseña incorrecta solo daría como resultado una salida sin sentido, en lugar de una excepción.

+5

Esto me ahorró mucho tiempo con su comentario: '" Parece que el código funciona bien, excepto que al descifrar datos con una clave incorrecta "' _jusque_ había copiado las claves, pero mirando 2x no lo hice.Esperemos que esto ayude a otra persona antes de mirar el mecanismo de relleno o cambiar el código. – atconway

Respuesta

26

Aunque esto ya se han 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 criptoataques. Por ejemplo, generalmente en RSA se utiliza el esquema de relleno OAEP que evita algunos tipos de ataques (como un ataque de texto plano elegido o blinding).

Un esquema de relleno agrega algunos (normalmente) elementos aleatorios al mensaje m antes de enviar 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 le bits de k1 PADD con 0 y K0 bits con un número aleatorio.
  2. Luego, aplicando alguna transformación al mensaje, obtiene el mensaje acolchado que se cifra y se envía.

Eso le proporciona una aleatorización para los mensajes y con 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á haciendo algo mal (es decir, alguien ha manipulado el mensaje o está usando la clave incorrecta)

+0

Jorge, gracias por la explicación. Estoy viendo el mismo comportamiento que el descrito, los datos que se descifran son correctos. ¿Se supone que debo comer esta excepción, o (con suerte) hay algo que estoy haciendo incorrectamente que puedo corregir? ¿Qué está mal cuando se lanza la excepción? todas las publicaciones que he leído parecen escritas por personas que están más interesadas en hacer que la excepción desaparezca. en mi caso, quiero que mi uso sea correcto :) – stuck

+1

Esto no responde a la pregunta del OP, la pregunta es sobre un cifrado de bloque simétrico Rijndael, no un cifrado asimétrico como RSA. Para agregar un relleno de cifrado de bloques, los datos se cifrarán en un múltiplo del tamaño del bloque, generalmente utilizando PKCS # 7 (née PKCS # 5). Con estos esquemas de relleno, los bytes aleatorios son ** no ** agregados. Tenga en cuenta que el relleno de ISO 10126 se retiró y los bytes aleatorios no agregaron ningún valor. La respuesta sería al punto si hablara sobre el relleno de cifrado simétrico. Sugerencia: corrige la respuesta para responder la pregunta. ZOMG, Desarrollador Senior. – zaph

+1

@zaph Solo estoy explicando genéricamente por qué se obtiene una excepción de relleno no válido en lugar de basura cuando se descifra el uso y una contraseña no válida. RSA fue solo un ejemplo, el esquema de relleno específico utilizado fue, por ejemplo. Responde a la pregunta de operación porque dice: Sí, se espera y aquí está el por qué y la explicación acerca de esa extraña referencia de "relleno" que solo puedes entender si entiendes qué es un esquema de relleno y por qué se usa, pero te dejaría desconcertado si no lo hace (¿qué tiene que ver el cifrado con el relleno?) –

3

Sí, esto es de esperar, o por lo menos, es exactamente lo que ocurre cuando nuestras rutinas criptográficas obtienen datos no descodificarse

1

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

5

Si desea que su uso sea correcto, debe agregar authentication a su texto cifrado para que pueda verificar que es la contraseña correcta o que el texto cifrado no se ha modificado. El relleno que está utilizando ISO10126 solo arrojará 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.

El uso de aplicaciones criptográficas aunque parezca 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 esa razón de fácil cometer errores, que tienen un fragmento de código, que trato de mantener revisada y actualizada (comentarios, cuestiones de bienvenida):

Modern Examples of Symmetric Authenticated Encryption of a string C#.

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

16

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

Resultó que la descarga de la secuencia de cifrado es todo lo que faltaba.

De esta manera:

  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(); 
+1

Lo mismo debería hacer 'encStream.Close();'. – sharpener

+1

Tal vez deberías usar 'using' en esos objetos ya que son desechables. Eso sería suficiente. – user2173353

+1

También tenga en cuenta que un simple 'Flush()' que normalmente haría con una secuencia es * not * lo mismo que 'FlushFinalBlock()' requerido para 'CryptoStream'. Eso es ligeramente ambiguo en la descripción que precede al código. –

0

tuve un problema similar, el problema en el método de descifrado se inicializando una corriente de memoria vacía. en el que funcionaba cuando inicializa con la matriz de bytes texto cifrado como esto:

MemoryStream ms = new MemoryStream(cipherText) 
-1

La respuesta actualizada por el usuario "atconway" trabajado 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 iv deben ser las mismas durante la codificación y el descifrado del mismo valor.

-1

También recibía el El relleno no es válido y no se puede eliminar mensaje. Como alguien dijo anteriormente, la causa fueron algunos bytes almacenados en el CryptoStream. Esta es la forma en que se fijó mediante una llamada al método FlushFinalBlock():

using (CryptoStream cryptoStream = new CryptoStream(memoryStream, algorithm.CreateDecryptor(), CryptoStreamMode.Write)) { 
    cryptoStream.Write(bytes, 0, bytes.Length); 
    cryptoStream.FlushFinalBlock(); 
    result = Encoding.UTF8.GetString(memoryStream.ToArray()); 
    return result; 
} 
2

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

3

Si ha descartado clave-desajuste, a continuación, además de FlushFinalBlock() (véase la respuesta de Yaniv), llamando Close() en el CryptoStream también será suficiente.

Si está limpiando los recursos estrictamente using bloques, asegúrese de nido el bloque para el 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 picado esta (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! 
+1

Pero si lo hace 'encStream.FlushFinalBlock()' supondría que no importa dónde haga 'ms.ToArray()' (ya sea dentro o fuera del 'uso' de 'CryptoStream'). – nawfal

+0

El OP sabe que es una discrepancia clave que causa el error y preguntó por qué una falta de coincidencia de las teclas causa este error. – jbtule

Cuestiones relacionadas