2010-01-22 88 views
15

Sé que se han formulado otras preguntas al respecto, pero hasta ahora no se ha proporcionado ninguna solución o el problema es el que tengo.RijndaelManaged "El relleno no es válido y no se puede eliminar" que solo se produce al descifrar en producción

La clase a continuación maneja el cifrado y descifrado de cadenas, la clave y el vector pasado son SIEMPRE iguales.

Las cadenas que se cifran y se descifran son siempre números, la mayoría funciona, pero la falla ocasional al descifrar (pero solo en el servidor de producción). Debo mencionar que los entornos locales y de producción están en IIS6 en Windows Server 2003, el código que usa la clase se encuentra en un controlador .ashx. El ejemplo que se produce un error en el servidor de producción es "0000232668"

El mensaje de error es

System.Security.Cryptography.CryptographicException: El relleno es válido y no se puede quitar. en System.Security.Cryptography.RijndaelManagedTransform.DecryptData (byte [] INPUTBUFFER, Int32 inputOffset, Int32 inputCount, byte [] & OutputBuffer, Int32 outputOffset, paddingMode PaddingMode, Boolean flast)

Y para el código

public class Aes 
    { 
     private byte[] Key; 
     private byte[] Vector; 

     private ICryptoTransform EncryptorTransform, DecryptorTransform; 
     private System.Text.UTF8Encoding UTFEncoder; 

     public Aes(byte[] key, byte[] vector) 
     { 
      this.Key = key; 
      this.Vector = vector; 

      // our encyption method 
      RijndaelManaged rm = new RijndaelManaged(); 

      rm.Padding = PaddingMode.PKCS7; 

      // create an encryptor and decyptor using encryption method. key and vector 
      EncryptorTransform = rm.CreateEncryptor(this.Key, this.Vector); 
      DecryptorTransform = rm.CreateDecryptor(this.Key, this.Vector); 

      // used to translate bytes to text and vice versa 
      UTFEncoder = new System.Text.UTF8Encoding(); 
     } 

     /// Encrypt some text and return a string suitable for passing in a URL. 
     public string EncryptToString(string TextValue) 
     { 
      return ByteArrToString(Encrypt(TextValue)); 
     } 

     /// Encrypt some text and return an encrypted byte array. 
     public byte[] Encrypt(string TextValue) 
     { 
      //Translates our text value into a byte array. 
      Byte[] bytes = UTFEncoder.GetBytes(TextValue); 
      Byte[] encrypted = null; 

      //Used to stream the data in and out of the CryptoStream. 
      using (MemoryStream memoryStream = new MemoryStream()) 
      {     
       using (CryptoStream cs = new CryptoStream(memoryStream, EncryptorTransform, CryptoStreamMode.Write)) 
       { 
        cs.Write(bytes, 0, bytes.Length);      
       } 

       encrypted = memoryStream.ToArray();     
      } 

      return encrypted; 
     } 

     /// The other side: Decryption methods 
     public string DecryptString(string EncryptedString) 
     { 
      return Decrypt(StrToByteArray(EncryptedString)); 
     } 

     /// Decryption when working with byte arrays.  
     public string Decrypt(byte[] EncryptedValue) 
     { 
      Byte[] decryptedBytes = null; 

      using (MemoryStream encryptedStream = new MemoryStream()) 
      { 
       using (CryptoStream decryptStream = new CryptoStream(encryptedStream, DecryptorTransform, CryptoStreamMode.Write)) 
       { 
        decryptStream.Write(EncryptedValue, 0, EncryptedValue.Length); 
       } 

       decryptedBytes = encryptedStream.ToArray(); 
      } 

      return UTFEncoder.GetString(decryptedBytes); 
     } 

     /// Convert a string to a byte array. NOTE: Normally we'd create a Byte Array from a string using an ASCII encoding (like so). 
     //  System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding(); 
     //  return encoding.GetBytes(str); 
     // However, this results in character values that cannot be passed in a URL. So, instead, I just 
     // lay out all of the byte values in a long string of numbers (three per - must pad numbers less than 100). 
     public byte[] StrToByteArray(string str) 
     { 
      if (str.Length == 0) 
       throw new Exception("Invalid string value in StrToByteArray"); 

      byte val; 
      byte[] byteArr = new byte[str.Length/3]; 
      int i = 0; 
      int j = 0; 
      do 
      { 
       val = byte.Parse(str.Substring(i, 3)); 
       byteArr[j++] = val; 
       i += 3; 
      } 
      while (i < str.Length); 
      return byteArr; 
     } 

     // Same comment as above. Normally the conversion would use an ASCII encoding in the other direction: 
     //  System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding(); 
     //  return enc.GetString(byteArr);  
     public string ByteArrToString(byte[] byteArr) 
     { 
      byte val; 
      string tempStr = ""; 
      for (int i = 0; i <= byteArr.GetUpperBound(0); i++) 
      { 
       val = byteArr[i]; 
       if (val < (byte)10) 
        tempStr += "00" + val.ToString(); 
       else if (val < (byte)100) 
        tempStr += "0" + val.ToString(); 
       else 
        tempStr += val.ToString(); 
      } 
      return tempStr; 
     } 

EDIT: Gracias por toda su ayuda, sin embargo, sus respuestas no resuelven el problema, que resultó ser algo estúpidamente simple. Estaba generando una cadena encriptada en un servidor y entregándola a un controlador en otro servidor para su desclasificación y procesamiento, pero resulta que los resultados del cifrado difieren cuando se ejecutan en diferentes servidores, por lo tanto, el servidor receptor no pudo descifrarlo. Una de las respuestas tropezó con la sugerencia de esto por accidente, y es por eso que la acepté

Respuesta

11

A veces recibirá un mensaje sobre relleno no válido cuando el cifrado y el descifrado por cualquier motivo no han utilizado la misma clave o vector de inicialización. El relleno es una cantidad de bytes añadidos al final de su texto sin formato para completar un número completo de bloques para que el cifrado funcione. En el relleno de PKCS7, cada byte es igual al número de bytes agregados, por lo que siempre se puede eliminar después del descifrado. Su descifrado ha conducido a una cadena donde los últimos n bytes no son iguales al valor n del último byte (espero que la oración tenga sentido). Entonces verificaría todas tus llaves.

Como alternativa, en su caso, le sugiero que se asegure de crear y eliminar una instancia de RijndaelManagedTransform para cada operación de cifrado y descifrado, inicializándola con la clave y el vector. Este problema podría deberse a la reutilización de este objeto de transformación, lo que significa que después del primer uso ya no se encuentra en el estado inicial correcto.

+0

Err, sí, lo siento por los comentarios, es "código prestado" de otra pregunta SO. "¿De dónde vino?" ¿La cadena o el código? –

+0

La cadena. Me alegra que sea un código prestado, no tengo que pisar tan cuidadosamente ... –

+0

¿Por qué funcionaría en el entorno local si ese es el caso? No cuestionar su respuesta, simplemente parece extraño –

2

estos resultados en los valores de caracteres que no se pueden pasar en una dirección URL

¿Hay razón por la que está utilizando su propia codificación, StrToByteArray, en lugar de Base64 codificación?

Si realiza estos cambios:

public string EncryptToString(string TextValue) 
{ 
    return Convert.ToBase64String(Encrypt(TextValue)); 
} 

public string DecryptToString(string TextValue) 
{ 
    return Decrypt(Convert.FromBase64String(TextValue)); 
} 

entonces las cosas deben trabajar mucho mejor.

Editar:
Respecto problema ToBase64String y cadena de consulta:
Si usted hace su propia cadena de consulta analizar entonces usted necesita para asegurarse de que sólo Split, en la primera = -sign.

var myURL = "http://somewhere.com/default.aspx?encryptedID=s9W/h7Sls98sqw==&someKey=someValue"; 
var myQS = myURL.SubString(myURL.IndexOf("?") + 1); 
var myKVPs = myQS.Split("&"); 
foreach (var kvp in myKVPs) { 
    // It is important you specify a maximum number of 2 elements 
    // since the Base64 encoded string might contain =-signs. 
    var keyValue = kvp.Split("=", 2); 
    var key = keyValue[0]; 
    var value = keyValue[1]; 
    if (key == "encryptedID") 
    var decryptedID = myAES.DecryptToString(value); 
} 

De esta manera no es necesario para reemplazar los caracteres en su cadena de consulta cuando está codificado en Base64.

+0

Es porque necesito pasar la cadena cifrada como un parámetro querystring. Estaba usando ToBase64String y haciendo string replace en los caracteres "/", "=", y el otro que se me escapa en este momento, el formato de número largo parecía más simple –

+0

No debería necesitar hacer ningún reemplazo cuando use el resultado en una cadena de consulta. He estado usando ToBase64String durante muchos años sin ningún problema en las cadenas de búsqueda. –

+0

Umm No veo cómo podría pasar y =, + o/caracteres en los valores de la cadena de consulta y no romper la URL –

12

Tiendo a llamar explícitamente al método FlushFinalBlock en CryptoStream antes de cerrarlo. Eso significaría hacer lo siguiente en su método de cifrar:

using (CryptoStream cs = new CryptoStream(memoryStream, EncryptorTransform, CryptoStreamMode.Write)) 
{ 
    cs.Write(bytes, 0, bytes.Length); 
    cs.FlushFinalBlock();   
} 

Si no lo hace, puede ser que los datos cifrados se trunca - esto resultaría en un escenario "de relleno no válido". El relleno siempre está presente cuando se utiliza PKCS7, incluso si los datos que se cifran están alineados con la longitud del bloque del cifrado.

+1

Se llama a FlushFinalBlock() cuando se elimina CryptoStream, lo que ocurre automáticamente con una instrucción using –

+1

. Tal vez corrigieron ese defecto, pero ciertamente fue una vez que CryptoStream.Dispose NO llamó a FlushFinalBlock. http://bytes.com/topic/c-sharp/answers/255847-encrypt-string-string-vice-versa –

+0

Parece que lo arreglaron en .NET 2.0, pero definitivamente sugeriría adquirir el hábito de enjuagar el Bloque final usted mismo. –

Cuestiones relacionadas