CryptEncode / CryptDecode / BCrypt implementations in other programming languages

 

When it comes to cryptographic algorithms in MQL there is a lot of scattered information and it is difficult to put it together, especially if you want to implement an analogous function in another programming language. I thought it was worth starting a thread where we can share implementations.

I'm going to start with a library in PowerShell + OpenSSL. Comments are always welcome.

--EDIT: simplified input parameters

# PowerShell + OpenSSL implementation of native MQL4/MQL5 CryptEncode and CryptDecode functions for the CRYPT_AES256 mode
# For the proper operation you need to have OpenSSL installed
# OpenSSL for Windows can be downloaded from https://slproweb.com/products/Win32OpenSSL.html
# Licensed under the MIT https://opensource.org/license/mit/

function CryptMQL {
    param (
        [Parameter(Mandatory = $true)]
        [string]$text,
        [Parameter(Mandatory = $true)]
        [string]$key,
        [Parameter(Mandatory = $false)]
        [switch]$encrypt,
        [Parameter(Mandatory = $false)]
        [switch]$keyIsHex,
        [Parameter(Mandatory = $false)]
        [switch]$encryptedIsHex
    )
    $DebugPreference = 'Continue'

    # Add zero padding to the plaintext or unpad the decrypted string
    $text = PadOrUnpadZero $text 16 $encrypt

    # Convert key to Hexadecimal if not already
    if (!$keyIsHex) { $key = StringToHex $key }

    # Convert input from Hexadecimal if necessary
    if (!$encrypt -and $encryptedIsHex) { $text = HexToBinary $text }

    # Define the command for openssl - if you don't have openssl in your PATH, you can specify the full path here
    $command = "openssl.exe"

    # Define the arguments for openssl
    $arguments = 'enc', '-aes-256-ecb', ($encrypt -eq $true ? '' : '-d'), '-K', $key, '-a', '-A', '-nopad', '-in', '-'

    # Start a new process for openssl
    $process = New-Object System.Diagnostics.Process
    $process.StartInfo.FileName = $command
    $process.StartInfo.Arguments = $arguments -join ' '
    $process.StartInfo.RedirectStandardInput = $true
    $process.StartInfo.RedirectStandardOutput = $true
    $process.StartInfo.UseShellExecute = $false

    # Start the process
    $process.Start() | Out-Null

    # Write text to the standard input of the process
    $process.StandardInput.Write($text)
    $process.StandardInput.Close()

    # Read the standard output of the process
    $outputString = $process.StandardOutput.ReadToEnd()
    $process.WaitForExit()

    # Convert output to hex if necessary
    if ($encrypt -and $encryptedIsHex) { $outputString = BinaryToHex $outputString }

    return $outputString
}

# Function to convert a string to hexadecimal
function StringToHex($string) {
    # Create a new StringBuilder object
    $hex = New-Object System.Text.StringBuilder

    # Loop through each character in the input string
    foreach ($char in $string.ToCharArray()) {
        # Append the hexadecimal representation of the current character to the StringBuilder
        [void]$hex.AppendFormat('{0:X}', [int]$char)
    }

    # Return the final hexadecimal string
    return $hex.ToString()
}

# Function to convert a binary string to a hexadecimal string
function BinaryToHex($binaryString) {
    # Initialize an empty string
    $res = ""

    # Convert the binary string to an array of bytes
    $byteArray = [System.Convert]::FromBase64String($binaryString)

    # Transform each byte in the array to a two-digit hexadecimal string and append it to res
    foreach ($byte in $byteArray) {
        $res += "{0:X2}" -f $byte
    }

    # Return the resulting string
    return $res
}

# Function to convert a hexadecimal string to a binary string
function HexToBinary($hexString) {
    # Initialize an empty byte array
    $byteArray = @()

    # Convert each two-digit hexadecimal string in the input to a byte and append it to byteArray
    for ($i = 0; $i -lt $hexString.Length; $i += 2) {
        $byteArray += [Convert]::ToByte($hexString.Substring($i, 2), 16)
    }

    # Convert the byte array to a binary string
    $binaryString = [System.Convert]::ToBase64String($byteArray)

    # Return the resulting string
    return $binaryString
}

# Function to pad or unpad a string with zeros until its length is a multiple of a given block size
function PadOrUnpadZero($text, $blockSize, $pad) {
    # If padding is required
    if ($pad) {

        # Calculate the number of zeros needed to make the length of the text a multiple of the block size
        $pad = $blockSize - ($text.Length % $blockSize)

        # Add the calculated number of zeros to the end of the text
        $text = $text + ("`0" * $pad)
    }
    else {
        # If unpadding is required, remove all trailing zeros from the text
        $text = $text.TrimEnd("`0")
    }

    # Return the padded/unpadded text
    return $text
}

And here are some examples of use:

#  Define plaintext, keystring and keyhex
$plaintext = 'Mother Tell Your Children Not To Walk My Way';
$keystring = 'thisisyoursecretkeydonttellnoone';
$bytes = [System.Text.Encoding]::UTF8.GetBytes($keystring)
$keyhex = [System.BitConverter]::ToString($bytes) -replace '-',''

Write-Output "plaintext: $plaintext";
Write-Output "keystring: $keystring";
Write-Output "keyhex: $keyhex";
Write-Output "";

# First 2 examples are compatible with the MQL library Bcrypt https://www.mql5.com/en/code/16378

Write-Output "1. Encrypt string and output the result as HEX"
$encrypted = CryptMQL -text $plaintext -key $keystring -encrypt -encryptedIsHex
Write-Output "encrypted: $encrypted`n"

Write-Output "2. Decrypt HEX input"
$decrypted = CryptMQL -key $keystring -text $encrypted  -encryptedIsHex
Write-Output "decrypted: $decrypted`n"

Write-Output "3. Encrypt string and output the result as Base64"
$encrypted = CryptMQL -key $keystring -text $plaintext -encrypt
Write-Output "encrypted: $encrypted`n"

Write-Output "4. Decrypt Base64 input"
$decrypted = CryptMQL -key $keystring -text $encrypted
Write-Output "decrypted: $decrypted`n"

Write-Output "5. Encrypt using HEX key"
$encrypted = CryptMQL -key $keyhex -text $plaintext -encrypt -keyIsHex
Write-Output "encrypted: $encrypted`n"

Write-Output " 6. Decrypt using HEX key"
$decrypted = CryptMQL -key $keyhex -text $encrypted -keyIsHex
Write-Output "decrypted: $decrypted`n"
 

Here is the equivalent in PHP

<?php
// Define plaintext, keystring and keyhex
$plaintext = 'Mother Tell Your Children Not To Walk My Way';
$keystring = 'thisisyoursecretkeydonttellnoone';
$keyhex = bin2hex($keystring);

echo "<pre>";

// Dump variables for debugging purposes
dump($plaintext);
dump($keystring);
dump($keyhex);

echo "1. Encrypt string and output the result as HEX\n";
$encrypted = CryptMQL($plaintext, $keystring, true, false, true);
dump($encrypted);

echo "2. Decrypt HEX input\n";
$decrypted = CryptMQL($encrypted, $keystring, false, false, true);
dump($decrypted);

echo "3. Encrypt string and output the result as Base64\n";
$encrypted = CryptMQL($plaintext, $keystring, true, false, false);
dump($encrypted);

echo "4. Decrypt Base64 input\n";
$decrypted = CryptMQL($encrypted, $keystring, false, false, false);
dump($decrypted);

echo "5. Encrypt using HEX key\n";
$encrypted = CryptMQL($plaintext, $keyhex, true, true, true);
dump($encrypted);

echo "6. Decrypt using HEX key\n";
$decrypted = CryptMQL($encrypted, $keyhex, false, true, true);
dump($decrypted);

echo "</pre>";

// Function to encrypt or decrypt data based on parameters
function CryptMQL($data, $key, $encrypt = true, $keyIsHex = true, $encryptedIsHex = true)
{
    // Convert key from HEX to binary if needed
    $key = $keyIsHex ? hex2bin($key) : $key;
    
    if ($encrypt) {
        // Pad data with zeros before encryption
        $data = padUnpadZero($data);
        // Encrypt data using AES-256-ECB and zero padding
        $result = openssl_encrypt($data, "AES-256-ECB", $key, OPENSSL_ZERO_PADDING);
        // Convert result to HEX if needed
        $result = $encryptedIsHex ? strtoupper(bin2hex(base64_decode($result))) : $result;

    } else {
        // Convert data from HEX to binary if needed
        $data = $encryptedIsHex ? base64_encode(hex2bin(strtolower($data))) : $data;
        // Decrypt data using AES-256-ECB and zero padding
        $result = openssl_decrypt($data, "AES-256-ECB", $key, OPENSSL_ZERO_PADDING);
        // Unpad data after decryption
        $result = padUnpadZero($result, false);
    }
    return $result;
}

// Function to pad or unpad data with zeros
function padUnpadZero($data, $pad = true, $blocksize = 16)
{
    if ($pad) {
        // Calculate number of zeros needed for padding
        $pad = $blocksize - (strlen($data) % $blocksize);
        // Add zeros to the end of data
        return $data . str_repeat("\0", $pad);
    } else {
        // Remove zeros from the end of data
        return rtrim($data, "\0");
    }
}

// Function to dump variable for debugging purposes
function dump($var)
{
    $backtrace = debug_backtrace();
    $file = file($backtrace[0]['file']);
    $line = $file[$backtrace[0]['line'] - 1];
    preg_match("#\\$(\w+)#", $line, $match);

    echo "<pre>";
    echo "$match[1]: ";
    var_dump($var);
    echo "</pre>";
}
 

Here is simpler PowerShell implementation based on native methods, no more OpenSSL required.

# Pure PowerShell implementation of native MQL4/MQL5 CryptEncode and CryptDecode functions for the CRYPT_AES256 mode

function CryptMQL {
    param (
        [Parameter(Mandatory = $true)]
        [string]$text,  # Text to be encrypted or decrypted
        [Parameter(Mandatory = $true)]
        [string]$key,  # Key for encryption or decryption
        [Parameter(Mandatory = $false)]
        [switch]$encrypt,  # Switch to determine if we are encrypting or decrypting
        [Parameter(Mandatory = $false)]
        [switch]$keyIsHex,  # Switch to determine if key is in hexadecimal format
        [Parameter(Mandatory = $false)]
        [switch]$encryptedIsHex  # Switch to determine if encrypted text is in hexadecimal format
    )

    # Convert key to byte array depending on whether it's in hexadecimal format or not
    $byteKey = $keyIsHex ? (ConvertHexToBinaryArr $key) : ([System.Text.Encoding]::UTF8.GetBytes($key))

    # Convert text to byte array depending on whether we are encrypting or decrypting
    if ($encrypt) {
        $bytes = [System.Text.Encoding]::UTF8.GetBytes($text)
    }
    else {
        $bytes = $encryptedIsHex ? (ConvertHexToBinaryArr $text) : ([Convert]::FromBase64String($text))
    }

    # Create an AESManaged object with ECB cipher mode, zero padding and a key size of 256
    $aesManaged = New-Object System.Security.Cryptography.AesManaged
    $aesManaged.Mode = [System.Security.Cryptography.CipherMode]::ECB
    $aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
    $aesManaged.KeySize = 256
    $aesManaged.Key = $byteKey
    
    # Create an encryptor or decryptor based on the value of $encrypt
    $transform = $encrypt ? ($aesManaged.CreateEncryptor()) : ($aesManaged.CreateDecryptor())

    # Actual encryption or decryption
    $outputBytes = $transform.TransformFinalBlock($bytes, 0, $bytes.Length)

    # If we are decrypting, convert the output bytes to a string and return it
    if (-not $encrypt) {
        $outputString = [System.Text.Encoding]::UTF8.GetString($outputBytes)
        return $outputString
    }

    # If we are encrypting, convert the output bytes to a hexadecimal or base64 string depending on $encryptedIsHex
    $outputString = $encryptedIsHex ? ([BitConverter]::ToString($outputBytes).Replace("-", "")) : ([Convert]::ToBase64String($outputBytes))

    # Return the encrypted string
    return $outputString
}

# Function to convert a hexadecimal string to a binary array
function ConvertHexToBinaryArr ($hexString) {
    # Initialize a byte array of half the length of the hex string
    $binary = new-object byte[] ($hexString.Length / 2)
    # Loop through the hex string two characters at a time
    for ($i = 0; $i -lt $hexString.Length; $i += 2) {
        # Convert each pair of characters to a byte and add it to the array
        $binary[$i / 2] = [Convert]::ToByte($hexString.Substring($i, 2), 16)
    }
    # Return the binary array
    return $binary
}

Examples of function calls same as in first post

 

Time for Python

import subprocess
import sys
from typing import Union
import binascii
import base64

# Check if the pycryptodome library is installed and install it if not - not best practice but works for this demo
try:
    from Crypto.Cipher import AES
except ImportError:
    subprocess.check_call([sys.executable, "-m", "pip", "install", "pycryptodome"])
    from Crypto.Cipher import AES

# Little helper function to pad the text with zeros to make it a multiple of the block size
def zero_pad(text, block_size):
    num_zeros = (-len(text)) % block_size
    return text + b"\0" * num_zeros

# Main function to encrypt/decrypt a string using AES ECB mode compatible with MQL4/5
def CryptMQL(
    text: Union[str, bytes],
    key: Union[str, bytes],
    encrypt=True,
    keyIsHex=False,
    encryptedIsHex=False,
):
    # convert the key to bytes
    key = binascii.unhexlify(key)[:32] if keyIsHex else key.encode()[:32]

    # create a new AES cipher object with the key
    cipher = AES.new(key, AES.MODE_ECB)

    if encrypt:
        # convert the string to bytes, pad it with zeros and then encrypt
        encrypted_string = cipher.encrypt(zero_pad(text.encode(), AES.block_size))

        # return the encrypted string in desired format
        return (
            binascii.hexlify(encrypted_string).upper().decode()
            if encryptedIsHex
            else base64.b64encode(encrypted_string).decode()
        )
    else:
        # convert the text to bytes
        text = binascii.unhexlify(text) if encryptedIsHex else base64.b64decode(text)

        # decrypt the string and return
        return cipher.decrypt(text).decode()


# 2 wrapper functions to encrypt/decrypt compatible with Bcrypt library https://www.mql5.com/en/code/16378
def EncryptMQL(plaintext, keystring):
    return CryptMQL(text=plaintext,key=keystring,encrypt=True,keyIsHex=False,encryptedIsHex=True)

def DecryptMQL(ciphertext, keystring):
    return CryptMQL(text=ciphertext,key=keystring,encrypt=False,keyIsHex=False,encryptedIsHex=True) 

# Demo

keystring = "thisisyoursecretkeydonttellnoone"
plaintext = "Mother Tell Your Children Not To Walk My Way"
keyhex = binascii.hexlify(keystring.encode()).upper().decode()

print("\nplaintext:  ", plaintext)
print("keystring:  ", keystring)
print("keyhex:     ", keyhex, "\n")

print("1. Encrypt string and output the result as HEX")
encrypted = EncryptMQL(plaintext, keystring)
print(f"encrypted: {encrypted}\n")

print("2. Decrypt HEX input")
decrypted = DecryptMQL(encrypted, keystring)
print(f"decrypted: {decrypted}\n")

print("3. Encrypt string and output the result as Base64")
encrypted = CryptMQL(plaintext, keystring, encrypt=True, encryptedIsHex=False)
print(f"encrypted: {encrypted}\n")

print("4. Decrypt Base64 input")
decrypted = CryptMQL(encrypted, keystring, encrypt=False, encryptedIsHex=False)
print(f"decrypted: {decrypted}\n")

print("5. Encrypt using HEX key")
encrypted = CryptMQL(plaintext, keyhex, encrypt=True, keyIsHex=True)
print(f"encrypted: {encrypted}\n")

print("6. Decrypt using HEX key")
decrypted = CryptMQL(encrypted, keyhex, encrypt=False, keyIsHex=True)
print(f"decrypted: {decrypted}\n")
Files:
CryptMQL.py  4 kb