Since X++ in theory should be able to do almost anything you can do in .Net, I wanted to give it a go. I had to change some stuff and make some shortcuts, but it worked. It is rough and I will be change things to improve usability, error handling and performance, and of course programmer documentation. At its current state it is a nice example for this "short" blog post.
I start with this little base class which holds two methods, encrypt and decrypt, both taking a string and returning a string. I haven't so far thought of limiting the length of the string. I leave that open for now.
abstract class AdBaseCryptography
{
}
abstract str encrypt(str _plainText)
{
}
abstract str decrypt(str _cipherText)
{
}
This is my class declaration, holding both some fixed settings for my encryption and instance variables:
public class AdBaseCryptography_Rijndael extends AdBaseCryptography
{
#define.fixedPassPhrase('Pas5pr@se')
#define.fixedSaltValue('s@1tValue')
#define.fixedPasswordIterations(2)
#define.fixedInitVector('@1B2c3D4e5F6g7H8')
#define.fixedKeySize(32)
System.String passPhrase;
System.String saltValue;
System.Int32 passwordIterations;
System.String initVector;
System.Int32 keySize;
System.Text.Encoding asciiEncoding;
System.Text.Encoding utf8Encoding;
System.Byte[] initVectorBytes;
System.Byte[] saltValueBytes;
System.Byte[] plainTextBytes;
System.Byte[] keyBytes;
System.Byte[] cipherTextBytes;
System.Security.Cryptography.Rfc2898DeriveBytes password;
System.Security.Cryptography.RijndaelManaged symmetricKey;
System.Security.Cryptography.ICryptoTransform encryptor;
System.Security.Cryptography.ICryptoTransform decryptor;
System.IO.MemoryStream memoryStream;
System.Security.Cryptography.CryptoStream cryptoStream;
System.String plainText, cipherText;
System.Exception e;
}
And a protected little initializer. This can be further enhanced by pulling settings from either some provider or table:
protected void init()
{
passPhrase = #fixedPassPhrase; // can be any string
saltValue = #fixedSaltValue; // can be any string
passwordIterations = #fixedPasswordIterations; // can be any number
initVector = #fixedInitVector; // must be 16 bytes
keySize = #fixedKeySize; // 32 = 256b, 24=192b or 16=128b
}
This is more or less a rewritten copy of my inspiration:
protected System.String encryptRijndael(
str _plainText,
System.String _passPhrase,
System.String _saltValue,
System.Int32 _passwordIterations,
System.String _initVector,
System.Int32 _keySize)
{
try
{
new InteropPermission(InteropKind::ClrInterop).assert();
asciiEncoding = System.Text.Encoding::get_ASCII();
utf8Encoding = System.Text.Encoding::get_UTF8();
initVectorBytes = asciiEncoding.GetBytes(_initVector);
saltValueBytes = asciiEncoding.GetBytes(_saltValue);
plainTextBytes = utf8Encoding.GetBytes(_plainText);
password = new System.Security.Cryptography.Rfc2898DeriveBytes(
_passPhrase,
saltValueBytes,
_passwordIterations);
keyBytes = password.GetBytes(_keySize);
symmetricKey = new System.Security.Cryptography.RijndaelManaged();
symmetricKey.set_Mode(System.Security.Cryptography.CipherMode::CBC);
encryptor = symmetricKey.CreateEncryptor(
keyBytes,
initVectorBytes);
memoryStream = new System.IO.MemoryStream();
cryptoStream = new System.Security.Cryptography.CryptoStream(memoryStream,
encryptor,
System.Security.Cryptography.CryptoStreamMode::Write);
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.get_Length());
cryptoStream.FlushFinalBlock();
cipherTextBytes = memoryStream.ToArray();
cipherText = System.Convert::ToBase64String(cipherTextBytes);
memoryStream.Close();
cryptoStream.Close();
}
catch (Exception::CLRError)
{
e = CLRInterop::getLastException();
while( e )
{
info( e.get_Message() );
e = e.get_InnerException();
}
}
return cipherText;
}
And here is my implementation of the encrypt method to wrap it up:
public str encrypt(str _plainText)
{
System.String cipherValue;
this.init();
cipherValue = this.encryptRijndael(
_plainText,
passPhrase,
saltValue,
passwordIterations,
initVector,
keySize);
return cipherValue;
}
And for decryption, we need this method, again inspired by the original post:
protected System.String decryptRijndael(
str _cipherText,
System.String _passPhrase,
System.String _saltValue,
System.Int32 _passwordIterations,
System.String _initVector,
System.Int32 _keySize)
{
System.Int32 plainTextBytesLength, cipherTextBytesLength, decryptedByteCount;
try
{
new InteropPermission(InteropKind::ClrInterop).assert();
asciiEncoding = System.Text.Encoding::get_ASCII();
utf8Encoding = System.Text.Encoding::get_UTF8();
initVectorBytes = asciiEncoding.GetBytes(_initVector);
saltValueBytes = asciiEncoding.GetBytes(_saltValue);
cipherTextBytes = System.Convert::FromBase64String(_cipherText);
password = new System.Security.Cryptography.Rfc2898DeriveBytes(
_passPhrase,
saltValueBytes,
_passwordIterations);
keyBytes = password.GetBytes(_keySize);
symmetricKey = new System.Security.Cryptography.RijndaelManaged();
symmetricKey.set_Mode(System.Security.Cryptography.CipherMode::CBC);
decryptor = symmetricKey.CreateDecryptor(
keyBytes,
initVectorBytes);
memoryStream = new System.IO.MemoryStream(cipherTextBytes);
cryptoStream = new System.Security.Cryptography.CryptoStream(
memoryStream,
decryptor,
System.Security.Cryptography.CryptoStreamMode::Read);
cipherTextBytesLength = cipherTextBytes.get_Length();
plainTextBytes = System.Convert::FromBase64String(_cipherText);
plainTextBytesLength = plainTextBytes.get_Length();
decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytesLength);
plainText = utf8Encoding.GetString(plainTextBytes, 0, decryptedByteCount);
memoryStream.Close();
cryptoStream.Close();
}
catch (Exception::CLRError)
{
e = CLRInterop::getLastException();
while( e )
{
info( e.get_Message() );
e = e.get_InnerException();
}
}
return plainText;
}
And the implementation of the decrypt method:
public str decrypt(str _cipherText)
{
System.String plainValue;
this.init();
plainValue = this.decryptRijndael(
_cipherText,
passPhrase,
saltValue,
passwordIterations,
initVector,
keySize);
return plainValue;
}
In order to test this, I made this little job - just for fun:
static void AdEncryptionTester(Args _args)
{
AdBaseCryptography_Rijndael cryptography = new AdBaseCryptography_Rijndael();
str encryptedString;
str decryptedString, decryptedString2;
str someOtherTest = 'A1V1nAAMS/N7iCEbiwY54rH5/CH+uRj/l5+l5Qi6EE4=';
encryptedString = cryptography.encrypt("This is @ test!");
decryptedString = cryptography.decrypt(encryptedString);
decryptedString2 = cryptography.decrypt(someOtherTest);
info(strFmt('Encrypted=%1, Decrypted=%2', encryptedString, decryptedString));
info(strFmt('Test 2=%1', decryptedString2));
}
And here is the output:
Now there are other ways to encrypt and decrypt in AX, so take my post as just another example.
EDIT: I've been asked if there are any special considerations for where to run this code in regards to client- or server tier. I just assumed this would be best deployed for server side execution, and I explicitly marked the classes for "server" in the AOT. I imagine this could be deployed for client side execution if it was built as a managed assembly (dll) and deployed client side.

No comments:
Post a Comment