1

I need to correct an encryption method written in C#.

First, a little background: I am taking charge of an existing web application with an ecma6 / html frontend and c# web api .net standard 4.6 backend.

It has many integrations with different customers for user identification. Some of the integrations simply navigate to a customers URL to do a login process on the infrastructure of the customer, and then return to the app with an encripted user token in the URL's query string.

This token is encripted using AES256 encryption.

The backend is correctly decrypting the tokens, but when I tried to use the encryption routine to build a unit test, I discovered something is wrong. When I encrypt and then decrypt a message, the decryption routine throws the following error:

Unhandled Exception:
System.Security.Cryptography.CryptographicException: Length of the data to decrypt is invalid.

Input message is "key1=value1;key2=value2" (without the quotes)

Encrypted message I get is NzcrOTc3Kzk3Nys5NzcrOTc3Kzk3Nys5NzcrOVpsVHZ2NzF3NzcrOUZ6UVlRZ3Z2djcxSVlPKy92U0V6NzcrOVNqZFY3Nys5VHpBZA==

I need to correct the implementation error in the encryption method. The implementation of the decryption method shows expected behavior, and you'll notice a double Base64 decoding done on the encrypted string: this is given, as we are integrated with an already developed encryption routine done by a customer in PERL which we detected did double encoding.

I inspected the order of operations to see a mismatch among the encryption and decryption and I was unable to detect an inconsistency, so the need to ask for help.

The code I synthesized for this test is:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

class MainClass {
  public static void Main (string[] args) {
    var secretKey = "This is my secret key";
    var secretIV = "This is my secret iv";
    var originalMessage = "key1=value1;key2=value2";
    var userToken = Cryptography.EncryptAES256CBCBase64x2(originalMessage, secretKey, secretIV);
    Console.WriteLine(userToken);
    var unencryptedToken = Cryptography.DecryptAES256CBCBase64x2(userToken, secretKey, secretIV);
    if (originalMessage == unencryptedToken)
      Console.WriteLine("All fine!");
    else
      Console.WriteLine("Error!");
  }
}

public static class Cryptography
{
  public static string DecryptAES256CBCBase64x2(string base64EncryptedString, string secretKey, string secretIV)
  {
      base64EncryptedString = SaveBase64String(base64EncryptedString);
      var keyBytes = Encoding.UTF8.GetBytes(secretKey);
      var ivBytes = Encoding.UTF8.GetBytes(secretIV);
      var hash = SHA256.Create();
      var keyHash = hash.ComputeHash(keyBytes);
      Array.Resize(ref keyHash, 32);
      var keyHashString = string.Empty;
      foreach (byte x in keyHash)
        keyHashString += string.Format("{0:x2}", x);
      keyHash = Encoding.UTF8.GetBytes(keyHashString.Substring(0, 32));
      var ivHash = hash.ComputeHash(ivBytes);
      Array.Resize(ref ivHash, 16);
      var ivHashString = string.Empty;
      foreach (byte x in ivHash)
        ivHashString += string.Format("{0:x2}", x);
      ivHash = Encoding.UTF8.GetBytes(ivHashString.Substring(0, 16));
      // Create an RijndaelManaged object
      // with the specified key and IV.
      using (var rijAlg = new RijndaelManaged())
      {
        rijAlg.Padding = PaddingMode.PKCS7;
        rijAlg.Mode = CipherMode.CBC;
        rijAlg.Key = keyHash;
        rijAlg.IV = ivHash;
        var encryptedBytes =
          Convert.FromBase64String(
          Encoding.UTF8.GetString(
          Convert.FromBase64String(base64EncryptedString)));
        // Create a decryptor to perform the stream transform.
        var decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
        // Create the streams used for decryption.
        using (var msDecrypt = new MemoryStream(encryptedBytes))
        {
            using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
            {
              using (var srDecrypt = new StreamReader(csDecrypt))
              {
                  // Read the decrypted bytes from the decrypting stream
                  // and place them in a string.
                  return srDecrypt.ReadToEnd();
              }
            }
        }
      }
  }

  public static string EncryptAES256CBCBase64x2(string baseString, string secretKey, string secretIV)
  {
      var keyBytes = Encoding.UTF8.GetBytes(secretKey);
      var ivBytes = Encoding.UTF8.GetBytes(secretIV);
      var hash = SHA256.Create();
      var keyHash = hash.ComputeHash(keyBytes);
      Array.Resize(ref keyHash, 32);
      var keyHashString = string.Empty;
      foreach (byte x in keyHash)
        keyHashString += string.Format("{0:x2}", x);
      keyHash = Encoding.UTF8.GetBytes(keyHashString.Substring(0, 32));
      var ivHash = hash.ComputeHash(ivBytes);
      Array.Resize(ref ivHash, 16);
      var ivHashString = string.Empty;
      foreach (byte x in ivHash)
        ivHashString += string.Format("{0:x2}", x);
      ivHash = Encoding.UTF8.GetBytes(ivHashString.Substring(0, 16));
      // Create an RijndaelManaged object
      // with the specified key and IV.
      using (var rijAlg = new RijndaelManaged())
      {
        rijAlg.Padding = PaddingMode.PKCS7;
        rijAlg.Mode = CipherMode.CBC;
        rijAlg.Key = keyHash;
        rijAlg.IV = ivHash;
        var encryptedBytes = Encoding.UTF8.GetBytes(baseString);
        // Create a encryptor to perform the stream transform.
        var encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
        // Create the streams used for encryption.
        using (var msEncrypt = new MemoryStream(encryptedBytes))
        {
            using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Read))
            {
              using (var srEncrypt = new StreamReader(csEncrypt))
              {
                  // Read the encrypted bytes from the encrypting stream
                  // and place them in a string.
                  var result = srEncrypt.ReadToEnd();

                  return Convert.ToBase64String(
                  Encoding.UTF8.GetBytes(
                  Convert.ToBase64String(
                  Encoding.UTF8.GetBytes(result))));
              }
            }
        }
      }
  }
  public static string SaveBase64String(string data)
  {
      data = data.Replace("-", "+").Replace("_", "/");
      var mod = data.Length % 4;
      if (mod > 2)
        mod = 1;
      return data + string.Empty.PadRight(mod, '=');
  }
}

At the following link an online example is available for you to try: https://repl.it/@ormasoftchile/Test-encrypt-decrypt

Thank you everyone.

4
  • Convert to hex and then back to ASCII bytes ... why? Commented Jun 11, 2019 at 22:59
  • Key size must be 32 bytes for AES256, so the operation ensures the hash is 32 bytes long. Commented Jun 12, 2019 at 0:07
  • 1
    The output of SHA256 is already ... 256 bits. Commented Jun 12, 2019 at 0:21
  • You're right, thanks. It'll improve performance. I'll use this after achieving the correction. Commented Jun 12, 2019 at 3:05

1 Answer 1

1

In the current code, the ciphertext is stored in a string (StreamReader.ReadToEnd), which generally doesn't work, since the data are corrupted thereby. Instead, the ciphertext should be stored in a byte-array, which can be Base64-encoded if required.

To fix the problem

  • remove the line:

    var encryptedBytes = Encoding.UTF8.GetBytes(baseString);  
    
  • and replace the entire MemoryStream-block by:

    using (var msEncrypt = new MemoryStream())
    {
        using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
        {
            using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
            {
                swEncrypt.Write(baseString);
            }
            var encryptedBytes = msEncrypt.ToArray();
            return Convert.ToBase64String(Encoding.UTF8.GetBytes(Convert.ToBase64String(encryptedBytes)));
        }
    }
    

Another point is the double Base64-encoding/decoding. This makes no sense and is simply redundant and degrades performance. If possible, this should be changed.

Sign up to request clarification or add additional context in comments.

1 Comment

I should have explained that. The double Base64 Encoding was given by the already working version of a customer with a PERL implementation, which for some reason it is double encoded. Will try your suggestion today. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.