Friday, October 18, 2013

Simple encryption and decryption in AX2012

I had a need for hiding some data in the database and I came across this example of encryption and decryption using standard .Net libraries. This is sort of security by obscurity, because any seasoned developer with adequate access can break it, but it is better than nothing if you don't want to store some strings in clear text in the database.

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