Estoy usando una clase de cifrado/descifrado de cadena similar a la proporcionada aquí como solución.
Esto funcionó bien para mí en .Net 5.
Ahora quería actualizar mi proyecto a .Net 6.
Cuando se usa .Net 6, la cadena descifrada se corta en cierto punto dependiendo de la longitud de la cadena de entrada.
▶️ Para facilitar la depuración/reproducción de mi problema, creé un Repositorio público de reproducción aquí .
Ambos están llamando a los métodos de cifrado con exactamente la misma entrada de "12345678901234567890" con la frase de ruta de "nzv86ri4H2qYHqc&m6rL" .
Salida .Net 5: "12345678901234567890"
Salida .Net 6: "1234567890123456"
La diferencia de longitud es 4 .
También miré los cambios importantes para .Net 6 , pero no pude encontrar algo que me guiara a una solución.
Me alegro de cualquier sugerencia con respecto a mi problema, ¡gracias!
Clase de cifrado
public static class StringCipher { // This constant is used to determine the keysize of the encryption algorithm in bits. // We divide this by 8 within the code below to get the equivalent number of bytes. private const int Keysize = 128; // This constant determines the number of iterations for the password bytes generation function. private const int DerivationIterations = 1000; public static string Encrypt(string plainText, string passPhrase) { // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text // so that the same Salt and IV values can be used when decrypting. var saltStringBytes = Generate128BitsOfRandomEntropy(); var ivStringBytes = Generate128BitsOfRandomEntropy(); var plainTextBytes = Encoding.UTF8.GetBytes(plainText); using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) { var keyBytes = password.GetBytes(Keysize / 8); using (var symmetricKey = Aes.Create()) { symmetricKey.BlockSize = 128; symmetricKey.Mode = CipherMode.CBC; symmetricKey.Padding = PaddingMode.PKCS7; using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes)) { using (var memoryStream = new MemoryStream()) { using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) { cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); cryptoStream.FlushFinalBlock(); // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes. var cipherTextBytes = saltStringBytes; cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray(); cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray(); memoryStream.Close(); cryptoStream.Close(); return Convert.ToBase64String(cipherTextBytes); } } } } } } public static string Decrypt(string cipherText, string passPhrase) { // Get the complete stream of bytes that represent: // [32 bytes of Salt] + [16 bytes of IV] + [n bytes of CipherText] var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText); // Get the saltbytes by extracting the first 16 bytes from the supplied cipherText bytes. var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray(); // Get the IV bytes by extracting the next 16 bytes from the supplied cipherText bytes. var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray(); // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string. var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray(); using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) { var keyBytes = password.GetBytes(Keysize / 8); using (var symmetricKey = Aes.Create()) { symmetricKey.BlockSize = 128; symmetricKey.Mode = CipherMode.CBC; symmetricKey.Padding = PaddingMode.PKCS7; using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)) { using (var memoryStream = new MemoryStream(cipherTextBytes)) { using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) { var plainTextBytes = new byte[cipherTextBytes.Length]; var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); memoryStream.Close(); cryptoStream.Close(); return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); } } } } } } private static byte[] Generate128BitsOfRandomEntropy() { var randomBytes = new byte[16]; // 16 Bytes will give us 128 bits. using (var rngCsp = RandomNumberGenerator.Create()) { // Fill the array with cryptographically secure random bytes. rngCsp.GetBytes(randomBytes); } return randomBytes; } }código de llamada
var input = "12345678901234567890"; var inputLength = input.Length; var inputBytes = Encoding.UTF8.GetBytes(input); var encrypted = StringCipher.Encrypt(input, "nzv86ri4H2qYHqc&m6rL"); var output = StringCipher.Decrypt(encrypted, "nzv86ri4H2qYHqc&m6rL"); var outputLength = output.Length; var outputBytes = Encoding.UTF8.GetBytes(output); var lengthDiff = inputLength - outputLength;La razón es este cambio radical :
DeflateStream, GZipStream y CryptoStream divergieron del comportamiento típico de Stream.Read y Stream.ReadAsync de dos maneras:
No completaron la operación de lectura hasta que el búfer pasado a la operación de lectura se llenó por completo o se alcanzó el final de la secuencia.
Y el nuevo comportamiento es:
A partir de .NET 6, cuando se llama a Stream.Read o Stream.ReadAsync en uno de los tipos de flujo afectados con un búfer de longitud N, la operación se completa cuando:
Se ha leído al menos un byte de la secuencia, o La secuencia subyacente que envuelven devuelve 0 de una llamada a su lectura, lo que indica que no hay más datos disponibles.
En su caso, se ve afectado por este código en el método Decrypt :
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) { var plainTextBytes = new byte[cipherTextBytes.Length]; var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); memoryStream.Close(); cryptoStream.Close(); return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); } No verifica cuántos bytes Read realmente Read y si los leyó todos. Podría salirse con la suya en versiones anteriores de .NET porque, como se mencionó, el comportamiento de CryptoStream era diferente de otras transmisiones, y porque la longitud de su búfer es suficiente para contener todos los datos. Sin embargo, este ya no es el caso y debe verificarlo como lo haría con otras transmisiones. O incluso mejor, simplemente use CopyTo :
using (var plainTextStream = new MemoryStream()) { cryptoStream.CopyTo(plainTextStream); var plainTextBytes = plainTextStream.ToArray(); return Encoding.UTF8.GetString(plainTextBytes, 0, plainTextBytes.Length); }O incluso mejor, como sugiere otra respuesta, ya que descifra el texto UTF8:
using (var plainTextReader = new StreamReader(cryptoStream)) { return plainTextReader.ReadToEnd(); }Creo que tu problema está aquí:
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); De los documentos de Stream.Read :
Una implementación es libre de devolver menos bytes de los solicitados incluso si no se ha llegado al final de la transmisión.
Por lo tanto, no se garantiza que esa única llamada a Read lea todos los bytes disponibles (hasta plainTextBytes.Length ; está dentro de sus derechos leer una cantidad menor de bytes.
.NET 6 tiene muchas mejoras de rendimiento y no me sorprendería si este fuera el tipo de compensación que harían en nombre del rendimiento.
Tendrá que ser bueno y seguir llamando a Read hasta que devuelva 0 , lo que indica que no hay más datos para devolver.
Sin embargo, es mucho más fácil usar un StreamReader , que también se encargará de la decodificación UTF-8 por usted.
return new StreamReader(cryptoStream).ReadToEnd();Después de actualizar de .net 2.2 a 6, me enfrentaba exactamente al mismo problema. No lee el búfer completo; en su mayoría solo lee hasta 16 bytes, por lo tanto, simplemente divídalo en bucle hasta un máximo de 16 bytes.
Este código puede ayudar:
int totalRead = 0; int maxRead = 16; while (totalRead < plainTextBytes.Length) { var countLeft = plainTextBytes.Length - totalRead; var count = countLeft < 16 ? countLeft : maxRead; int bytesRead = cryptoStream.Read(plainTextBytes, totalRead, count); totalRead += bytesRead; if (bytesRead == 0) break; }