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