Question
· Nov 4, 2022

Difference of $SYSTEM.Encryption.AESCBCEncrypt with C# implementation

Hi All,

Hopefully  someone can help me with this case. I need to encrypt a text(querystring) with an AES265 encryption. An other vendor is decrypting this information. I have a working class in C#. I've tried to build the same in Objectscript for the encrypt part but there's a missing link somewhere.  What's the difference between the C# and Objectscript implementation?

Objectscript code (until now):

Class TEST.ENCRYPT
{

// Symmetric Keys sample to encrypt

ClassMethod DoAESCBCEncrypt() As %Status
{
	set key="pZR8qfrz7t47G+dboyJCH4NnJRrF+dJbvxq37y/cLUo="
	set iv=##class(%PopulateUtils).StringMin(16,16)	
	Write "Key="_key,!
	Write "IV="_iv,!
	Set ivBase64 = $SYSTEM.Encryption.Base64Encode(iv)
	Write "IVBase64="_ivBase64,!
	
	set plaintext="This is just an encryption test with AES256, blocksize 128, padding PKCS7, mode, CBC with an IV of 16 bytes"
	Set text=$ZCONVERT(plaintext,"O","UTF8")
	Write "Text UTF-8: "_text,!
	
	Set encrypted=$SYSTEM.Encryption.AESCBCEncrypt(text,key,iv)
	Set EncryptedBase64=$SYSTEM.Encryption.Base64Encode(encrypted)
	Write "EncryptedBase64: "_EncryptedBase64,!
	
	Set encryptedComplete = ivBase64_EncryptedBase64
	Write "EncryptedBase64WithIV: "_encryptedComplete,!
	
	Set ciphertext = $$$URLENCODE(encryptedComplete)

	write "URL Encoded:"_ciphertext,!
	return $$$OK
}

}

This give this as example output:

URL Encoded: ZlR3ckxhVmxBZXJKZlpLQg%3D%3D8TvyqZNAFsv7Nb0HYkGIfrPmUOkeQ2r%2BKZ%2BThSzVd1tqoyziSpRJsjQP/pEnAzRY5HzybCdFBDtb%0D%0AD8wNTNv1OFcXsHqk/GOhce1xdPlyJFg85llLUfBIG74lhAjtvb%2BN4eMY6jg05HkGejXlIoEo8Q%3D%3D

When decrypting with the C# code, I get the following eror.

I see a few things. When running the decrypting code the IV looks fine, also the "To Decrypt without IV" decrypt looks fine (it matches the output of the Objectscript implementation). So I think there's a difference in implementation of the encryption itself but how to find the difference?

See complete enrypt/decrypt C# code below.

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

class Program
{
    static void Main(string[] args)
    {
		string toEncrypt = "This is just an encryption test with AES256, blocksize 128, padding PKCS7, mode, CBC with an IV of 16 bytes";
		string hashedKey = "pZR8qfrz7t47G+dboyJCH4NnJRrF+dJbvxq37y/cLUo=";
		Console.WriteLine("SECRET KEY / HASHED KEY: "+hashedKey);
		Console.WriteLine("");

		Console.WriteLine("What to encrypt/decrypt: " + toEncrypt);
		Console.WriteLine("");

		//Ecnryption Parts
		Console.WriteLine("######## ENCRYPTION START ########");
		
		Console.WriteLine("To Encrypt: " + toEncrypt);

		//create IV
		byte[] IV = new byte[16];
		(new Random()).NextBytes(IV);

		AesCryptoServiceProvider cryptoProvider = CreateCryptoProvider(Convert.FromBase64String(hashedKey),IV);

		ICryptoTransform cryptoTransform = cryptoProvider.CreateEncryptor();
		byte[] bytes = Encoding.UTF8.GetBytes(toEncrypt);
		string encryptedUnformatted = Convert.ToBase64String(cryptoTransform.TransformFinalBlock(bytes, 0, (int)bytes.Length));
		Console.WriteLine("encryptedUnformatted: " + encryptedUnformatted);

		//add Random IV
		string randomIVBase64 = Convert.ToBase64String(IV);
		Console.WriteLine("randomIVBase64: " + randomIVBase64);

		encryptedUnformatted = string.Concat(randomIVBase64, encryptedUnformatted);
		Console.WriteLine("With Random IV: " + encryptedUnformatted);

		//escape string
		string encryptedFormatted = Uri.EscapeDataString(encryptedUnformatted);
		Console.WriteLine("encryptedFormatted: " + encryptedFormatted);

		Console.WriteLine("######## ENCRYPTION END ########");
		Console.WriteLine("");
		Console.WriteLine("#####################################################");
		Console.WriteLine("");
		Console.WriteLine("######## DECRYPTION START ########");

		//Decryption Part
		string toDecrypt = encryptedFormatted;
		//toDecrypt = "ZlR3ckxhVmxBZXJKZlpLQg%3D%3D8TvyqZNAFsv7Nb0HYkGIfrPmUOkeQ2r%2BKZ%2BThSzVd1tqoyziSpRJsjQP/pEnAzRY5HzybCdFBDtb%0D%0AD8wNTNv1OFcXsHqk/GOhce1xdPlyJFg85llLUfBIG74lhAjtvb%2BN4eMY6jg05HkGejXlIoEo8Q%3D%3D";
		Console.WriteLine("To Decrypt: " + toDecrypt);
		
		//de-escape string
		string deEscape=Uri.UnescapeDataString(toDecrypt);
		Console.WriteLine("De-Escaped: " + deEscape);

		//get IV
		string ivString = deEscape.Substring(0, 24);
		Console.WriteLine("IV String: " + ivString);

		byte[] IVDecrypt= Convert.FromBase64String(ivString);
		
		string toDecryptWithoutIV = deEscape.Substring(24);
		Console.WriteLine("To Decrypt without IV: " + toDecryptWithoutIV);

		AesCryptoServiceProvider DecryptProvider = CreateCryptoProvider(Convert.FromBase64String(hashedKey), IVDecrypt);

		ICryptoTransform cryptoTransformDecrypt = DecryptProvider.CreateDecryptor();
		byte[] numArray = Convert.FromBase64String(toDecryptWithoutIV);
		string decrypted = Encoding.UTF8.GetString(cryptoTransformDecrypt.TransformFinalBlock(numArray, 0, (int)numArray.Length));

		Console.WriteLine("Decrypted: " + decrypted);
		Console.WriteLine("######## DECRYPTION END ########");
	}
	static AesCryptoServiceProvider CreateCryptoProvider(byte[] HashedKey, byte[] IV)
	{
		return new AesCryptoServiceProvider()
		{
			KeySize = 256,
			BlockSize = 128,
			Key = HashedKey,
			IV = IV,
			Padding = PaddingMode.PKCS7,
			Mode = CipherMode.CBC
		};
	}
}

Product version: IRIS 2021.2
$ZV: IRIS for Windows (x86-64) 2021.2 (Build 649U) Thu Jan 20 2022 08:46:32 EST [HealthConnect:3.4.0] [HealthConnect:3.4.0]
Discussion (7)0
Log in or sign up to continue

One of the problems could be the Bas64 encoding. This function inserts after each 76th byte a CRLF which possibly confuses the other party. Try with 

Set EncryptedBase64=$SYSTEM.Encryption.Base64Encode(encrypted, 1)

The parameter 1 says, do not insert CRLFs. Also, the text you encrypt must be an ANSI (8bit) text. If you are on a unicode system, you should call

Set encrypted=$SYSTEM.Encryption.AESCBCEncrypt($zcvt(text,"O","UTF8"),key,iv)

Hi Julius,

Thank you for your response. I've tried your suggestions. See modified code below:

Class TEST.ENCRYPT
{

// Symmetric Keys sample to encrypt
ClassMethod DoAESCBCEncrypt() As %Status
{
	set key="pZR8qfrz7t47G+dboyJCH4NnJRrF+dJbvxq37y/cLUo="
	set iv=##class(%PopulateUtils).StringMin(16,16)	
	Write "Key="_key,!
	Write "IV="_iv,!
	Set ivBase64 = $SYSTEM.Encryption.Base64Encode(iv,1)
	Write "IVBase64="_ivBase64,!
	
	set text="This is just an encryption test with AES256, blocksize 128, padding PKCS7, mode, CBC with an IV of 16 bytes"
	Write "Plain Text: "_text,!
	
	Set encrypted=$SYSTEM.Encryption.AESCBCEncrypt($zcvt(text,"O","UTF8"),key,iv)
	Set EncryptedBase64=$SYSTEM.Encryption.Base64Encode(encrypted,1)
	Write "EncryptedBase64: "_EncryptedBase64,!
	
	Set encryptedComplete = ivBase64_EncryptedBase64
	Write "EncryptedBase64WithIV: "_encryptedComplete,!
	
	Set ciphertext = $$$URLENCODE(encryptedComplete)

	write "URL Encoded:"_ciphertext,!
	return $$$OK
}

}

Unfortunately when decrypted with the C# code I'm getting the error ''padding is invalid and cannot be removed". Is this about the PaddingMode.PKCS7?

First, ##class(%PopulateUtils).StringMin(16,16)    generates random printable chars for testing (as replacement for user input) and is not ment to be used to generate cryptographic random chars. If you need cryptographic random chars, use $system.Encryption.GenCryptRand(length). This is just a hint and is not a reason for your current problem.

Second, despite my very limited C# experience, there is one thing in your code, I do not understand. If the initial vector (IV) is used, then it should be used (for enryption and decryption) the same IV on both sides, but you create an IV on Cache side (with the populate utils) and an other one on the C# side: byte[] IV = new byte[16]; (new Random()).NextBytes(IV);

That your decryption can't work.

To have a successful comunication, agree with the other party on
- a passphrase (the key), padding is done inside the encoding function 
- an IV (or left it empty on both sides)
- and on how the data will be sent:  either as the encoded (binary) data or as a readable (base64) data.ISC's implementation of AESCBCEncrtypt/Decrypt works well. One of my application uses it to communicate with an Windows application written in Delphi, without any problem since more the 15 years (we do not use the IV). So your problem will be some kind of a home-made problem:
- not using the same keys on both sides
- applying some kind of transformation (utf-8, base64, etc.)
- something suchlike

Hi Julius,

Thank you again. I agree with you that $system.Encryption.GenCryptRand(length) should be a nicer option to generate the random IV.

About the IV, this a random IV to make every message unique. The sender will generate the IV and the receiver gets the first 24 characters and this is the IV and will be used in the decryption.

//get IV
string ivString = deEscape.Substring(0, 24);
Console.WriteLine("IV String: " + ivString);

The IV is used in the decryption. The part after the IV is the part thats need to be decrypted:

string toDecryptWithoutIV = deEscape.Substring(24);
Console.WriteLine("To Decrypt without IV: " + toDecryptWithoutIV);

I can't find what's the problem in Objectscript. So i've switched to Python and in Python it works right away. See example code below:

import base64
from base64 import b64encode
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import urllib.parse

keyBase64="pZR8qfrz7t47G+dboyJCH4NnJRrF+dJbvxq37y/cLUo="
key = base64.b64decode(keyBase64)

toEncrypt = "This is just an encryption test with AES256, blocksize 128, padding PKCS7, mode, CBC with an IV of 16 bytes"
toEncryptBytes = str.encode(toEncrypt)

cipher = AES.new(key, AES.MODE_CBC)
ct_bytes = cipher.encrypt(pad(toEncryptBytes, AES.block_size))
iv = b64encode(cipher.iv).decode('utf-8')
ct = b64encode(ct_bytes).decode('utf-8')
complete=iv+ct
complete = urllib.parse.quote(complete)
print(complete)

In our used version of IRIS,  Python is embedded so I think this will be the first functionality where we're going to use Python.

Menno,

In your ObjectScript code you never base64 decode the key:

set key="pZR8qfrz7t47G+dboyJCH4NnJRrF+dJbvxq37y/cLUo="
...
Set encrypted=$SYSTEM.Encryption.AESCBCEncrypt($zcvt(text,"O","UTF8"),key,iv)

But you did this in Python:

keyBase64="pZR8qfrz7t47G+dboyJCH4NnJRrF+dJbvxq37y/cLUo="
key = base64.b64decode(keyBase64)
...
cipher = AES.new(key, AES.MODE_CBC)

Hello Marc,

I saw the same, but after adjusting I get the same error when decrypting (Padding is invalid and cannot be removed)

Latest Objectscript code:

Class TEST.ENCRYPT
{

// Symmetric Keys sample to encrypt
ClassMethod DoAESCBCEncrypt() As %Status
{
	Set key="pZR8qfrz7t47G+dboyJCH4NnJRrF+dJbvxq37y/cLUo="
	Write "Key="_key,!
	Set keyBase64=$SYSTEM.Encryption.Base64Encode(key,1)
	Write "KeyBase64="_keyBase64,!
	
	Set iv=$system.Encryption.GenCryptRand(16)
	Write "IV="_iv,!
	Set ivBase64 = $SYSTEM.Encryption.Base64Encode(iv,1)
	Write "IVBase64="_ivBase64,!
	
	Set text="This is just an encryption test with AES256, blocksize 128, padding PKCS7, mode, CBC with an IV of 16 bytes"
	Write "Plain Text: "_text,!
	
	Set encrypted=$SYSTEM.Encryption.AESCBCEncrypt($zcvt(text,"O","UTF8"),keyBase64,iv)
	Set EncryptedBase64=$SYSTEM.Encryption.Base64Encode(encrypted,1)
	Write "EncryptedBase64: "_EncryptedBase64,!
	
	Set encryptedComplete = ivBase64_EncryptedBase64
	Write "EncryptedBase64WithIV: "_encryptedComplete,!
	
	Set ciphertext = $$$URLENCODE(encryptedComplete)

	write "URL Encoded:"_ciphertext,!
	return $$$OK
}

}