Download MetaTrader 5

Securing MQL5 code: Password Protection, Key Generators, Time-limits, Remote Licenses and Advanced EA License Key Encryption Techniques

17 February 2012, 09:11
investeo
9
35 291

Introduction

Most developers need to have their code secured. This article will present a few different ways to protect MQL5 software. All examples in the article will refer to Expert Advisors but the same rules can be applied to Scripts and Indicators. The article starts with simple password protection and follows with key generators, licensing a given brokers account and time-limit protection. Then it introduces a remote license server concept. My last article on MQL5-RPC framework described Remote Procedure Calls from MetaTrader 5 to any XML-RPC server.

I will make use of this solution to provide an example of a remote license. I will also describe how to enhance this solution with base64 encoding and provide advice for PGP support to make ultra-secure protection for MQL5 Expert Advisors and Indicators. I am aware that MetaQuotes Software Corp. is providing some options for licensing the code directly from the MQL5.com Market section. This is really good for all developers and will not invalidate ideas presented in this article. Both solutions used together can only make the protection stronger and more secure against software theft.


1. Password protection

Let's start with something simple. The first most used solution for a protection of computer software is password or license key protection. During first run after installation the user is queried with a dialog box to insert a password tied with a software copy (like the Microsoft Windows or Microsoft Office serial key) and if the entered password matches the user is allowed to use a single registered copy of a software. We can use an input variable or a direct textbox to enter the code. An example stub code is shown below.

The code below initializes a CChartObjectEdit field that is be used to insert a password. There is a predefined array of allowed passwords that is matched against a password inserted by a user. Password is checked in OnChartEvent() method after receiving CHARTEVENT_OBJECT_ENDEDIT event.

//+------------------------------------------------------------------+
//|                                          PasswordProtectedEA.mq5 |
//|                                      Copyright 2012, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"

#include <ChartObjects/ChartObjectsTxtControls.mqh>

CChartObjectEdit password_edit;

const string allowed_passwords[] = { "863H-6738725-JG76364",
                             "145G-8927523-JG76364",
                             "263H-7663233-JG76364" };
                             
int    password_status = -1;
string password_message[] = { "WRONG PASSWORD. Trading not allowed.",
                         "EA PASSWORD verified." };

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   password_edit.Create(0, "password_edit", 0, 10, 10, 260, 25);
   password_edit.BackColor(White);
   password_edit.BorderColor(Black);
   password_edit.SetInteger(OBJPROP_SELECTED, 0, true);
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   password_edit.Delete();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  if (password_status>0) 
  {
    // password correct
  } 
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if (id == CHARTEVENT_OBJECT_ENDEDIT && sparam == "password_edit" )
      {
         password_status = -1;
         
         for (int i=0; i<ArraySize(allowed_passwords); i++)
            if (password_edit.GetString(OBJPROP_TEXT) == allowed_passwords[i]) 
            {
               password_status = i;
               break;
            }
            
         if (password_status == -1) 
            password_edit.SetString(OBJPROP_TEXT, 0, password_message[0]);
         else 
            password_edit.SetString(OBJPROP_TEXT, 0, password_message[1]); 
      }
  }
//+------------------------------------------------------------------+

This method is simple but is vurnelable for someone to publish the password on a website with hacked serial numbers. EA author cannot do anything until a new Expert Advisor is released and the stolen password is blacklisted.


2. Key generator

Key generators are a mechanism that allows to use a set of passwords based on predefined rules. I will give an overview by providing a stub for a keygenerator below. In the example presented below the key must consist of three numbers separated by two hyphens. Therefore the allowed format for a password is XXXXX-XXXXX-XXXXX.

First number must be divisible by 3, second number must be divisible by 4 and third number must be divisible by 5. Therefore the allowed passwords may be 3-4-5, 18000-20000-20000 or the more complicated one 3708-102792-2844770.

//+------------------------------------------------------------------+
//|                                      KeyGeneratorProtectedEA.mq5 |
//|                                      Copyright 2012, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"

#include <ChartObjects/ChartObjectsTxtControls.mqh>
#include <Strings/String.mqh>

CChartObjectEdit password_edit;
CString user_pass;

const double divisor_sequence[] = { 3.0, 4.0, 5.0 };
                             
int    password_status = -1;
string password_message[] = { "WRONG PASSWORD. Trading not allowed.",
                         "EA PASSWORD verified." };

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   password_edit.Create(0, "password_edit", 0, 10, 10, 260, 25);
   password_edit.BackColor(White);
   password_edit.BorderColor(Black);
   password_edit.SetInteger(OBJPROP_SELECTED, 0, true);
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   password_edit.Delete();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  if (password_status==3) 
  {
    // password correct
  } 
  }
  
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if (id == CHARTEVENT_OBJECT_ENDEDIT && sparam == "password_edit" )
      {
         password_status = 0;
         
         user_pass.Assign(password_edit.GetString(OBJPROP_TEXT));

         int hyphen_1 = user_pass.Find(0, "-");
         int hyphen_2 = user_pass.FindRev("-");
         
         if (hyphen_1 == -1 || hyphen_2 == -1 || hyphen_1 == hyphen_2) {
            password_edit.SetString(OBJPROP_TEXT, 0, password_message[0]);
            return;
         } ;     
         
         long pass_1 = StringToInteger(user_pass.Mid(0, hyphen_1));
         long pass_2 = StringToInteger(user_pass.Mid(hyphen_1 + 1, hyphen_2));
         long pass_3 = StringToInteger(user_pass.Mid(hyphen_2 + 1, StringLen(user_pass.Str())));
         
         // PrintFormat("%d : %d : %d", pass_1, pass_2, pass_3);
         
         if (MathIsValidNumber(pass_1) && MathMod((double)pass_1, divisor_sequence[0]) == 0.0) password_status++;
         if (MathIsValidNumber(pass_2) && MathMod((double)pass_2, divisor_sequence[1]) == 0.0) password_status++;
         if (MathIsValidNumber(pass_3) && MathMod((double)pass_3, divisor_sequence[2]) == 0.0) password_status++;
            
         if (password_status != 3) 
            password_edit.SetString(OBJPROP_TEXT, 0, password_message[0]);
         else
            password_edit.SetString(OBJPROP_TEXT, 0, password_message[1]); 
      }
  }
//+------------------------------------------------------------------+

Of course the number of digits in a number can be set to a given value and the calculations can be more complicated. One might also add a variable that is valid only with a given hardware by adding HDD serial number or CPU ID to calculation. In such case person to run the EA would have to run additional generator calculated on basis of the hardware.

The output would be an input to a keygen and the generated password would be valid only for a given hardware. This has a limitation of someone changing computer hardware or using VPS for running EA but this could be resolved by giving away two or three valid passwords. This is also the case in the Market section of MQL5 website.


3. Single Account License

Since the Account number of the terminal of any given broker is unique this can be used to allow usage of the EA on one or a set of account numbers. In such case it is enough to use AccountInfoString(ACCOUNT_COMPANY) and AccountInfoInteger(ACCOUNT_LOGIN) methods to fetch the account data and compare it against precompiled allowed values:

//+------------------------------------------------------------------+
//|                                           AccountProtectedEA.mq5 |
//|                                      Copyright 2012, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"


const string allowed_broker = "MetaQuotes Software Corp.";
const long allowed_accounts[] = { 979890, 436290, 646490, 225690, 279260 };
                             
int password_status = -1;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   string broker = AccountInfoString(ACCOUNT_COMPANY);
   long account = AccountInfoInteger(ACCOUNT_LOGIN);
   
   printf("The name of the broker = %s", broker);
   printf("Account number =  %d", account);
   
   if (broker == allowed_broker) 
      for (int i=0; i<ArraySize(allowed_accounts); i++)
       if (account == allowed_accounts[i]) { 
         password_status = 1;
         Print("EA account verified");
         break;
       }
   if (password_status == -1) Print("EA is not allowed to run on this account."); 
    
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---  
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  if (password_status == 1) 
  {
    // password correct
  } 
  }

This is a simple but quite powerful protection. The disadvantage is that it is necessary to recompile the EA for each new account number added to account database.


4. Time-limit Protection

Time-limit protection is useful when the license is granted on temporary basis, for example using trial version of the software or when the license is granted on monthly or yearly basis. Since this is obvious that this can be applied for Expert Advisors and Indicators.

First idea is to check server time and based on that let the user use the Indicator or Expert Advisor within given period of time. After it expires the licensor is able to partially or totally disable its functionality to the licensee.

//+------------------------------------------------------------------+
//|                                         TimeLimitProtectedEA.mq5 |
//|                                      Copyright 2012, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"
                           
datetime allowed_until = D'2012.02.11 00:00'; 
                             
int password_status = -1;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   printf("This EA is valid until %s", TimeToString(allowed_until, TIME_DATE|TIME_MINUTES));
   datetime now = TimeCurrent();
   
   if (now < allowed_until) 
         Print("EA time limit verified, EA init time : " + TimeToString(now, TIME_DATE|TIME_MINUTES));
   
    
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  if (TimeCurrent() < allowed_until) 
    {        
    }
   else Print("EA expired."); 
  }

The only drawback is that that the solution would need to be have to be compiled separately for each licensee.


5. Remote licenses

Wouldn't it be good to have a total control wheter to disable the license or extend the trial period on per user basis? This can simply be done using MQL5-RPC call that would send a query with the account name and receive the value whether to run the script in trial mode or disable it.

Please see the code below for a sample implementation:

from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler

class RequestHandler( SimpleXMLRPCRequestHandler ):
    rpc_path = ( '/RPC2', )
    

class RemoteLicenseExample(SimpleXMLRPCServer):

    def __init__(self):
        SimpleXMLRPCServer.__init__( self, ("192.168.2.103", 9099), requestHandler=RequestHandler, logRequests = False)
        
        self.register_introspection_functions()
        self.register_function( self.xmlrpc_isValid, "isValid" )        
        
        self.licenses = {} 
        
    def addLicense(self, ea_name, broker_name, account_number):
        if ea_name in self.licenses:
            self.licenses[ea_name].append({ 'broker_name': broker_name, 'account_number' : account_number })
        else:
            self.licenses[ea_name] = [ { 'broker_name': broker_name, 'account_number' : account_number } ]
             
    def listLicenses(self):
        print self.licenses
        
    def xmlrpc_isValid(self, ea_name, broker_name, account_number):
        isValidLicense = False
        
        ea_name = str(ea_name)
        broker_name = str(broker_name)
        
        print "Request for license", ea_name, broker_name, account_number
        
        try:
            account_number = int(account_number)
        except ValueError as error:
            return isValidLicense
    
        if ea_name in self.licenses:
            for license in self.licenses[ea_name]:
                if license['broker_name'] == broker_name and license['account_number'] == account_number:
                    isValidLicense = True
                    break
                
        print "License valid:", isValidLicense
        
        return isValidLicense
    
if __name__ == '__main__':
    server = RemoteLicenseExample()
    server.addLicense("RemoteProtectedEA", "MetaQuotes Software Corp.", 1024221)
    server.addLicense("RemoteProtectedEA", "MetaQuotes Software Corp.", 1024223)
    
    server.listLicenses()
    server.serve_forever()  

This is a simple XML-RPC server implemented in Python with two predefined licenses for MetaTrader 5. The licenses are set for "RemoteProtectedEA" expert advisor running on default MetaQuotes demo server (access.metatrader5.com:443) with account numbers 1024221 and 1024223. An industrial solution would probable make use of a license database in Postgresql or any other database but the example above is more than enough for this article since it handles the remote licenses very well.

If you need a short explanation on how to install Python please read "MQL5-RPC. Remote Procedure Calls from MQL5: Web Service Access and XML-RPC ATC Analyzer for Fun and Profit".

The EA that uses the remote license simply needs to prepare a remote MQL5-RPC call to isValid() method that returns true or false boolean values depending if the license is valid. The example below shows a sample EA that is based on account protection:

//+------------------------------------------------------------------+
//|                                            RemoteProtectedEA.mq5 |
//|                                      Copyright 2012, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"

#include <MQL5-RPC.mqh>
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayInt.mqh>
#include <Arrays\ArrayString.mqh>
#include <Arrays\ArrayBool.mqh>

bool license_status=false;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
/* License proxy server */
   CXMLRPCServerProxy s("192.168.2.103:9099");
   if(s.isConnected()==true)
     {

      CXMLRPCResult *result;

/* Get account data */
      string broker= AccountInfoString(ACCOUNT_COMPANY);
      long account = AccountInfoInteger(ACCOUNT_LOGIN);

      printf("The name of the broker = %s",broker);
      printf("Account number =  %d",account);

/* Get remote license status */
      CArrayObj* params= new CArrayObj;
      CArrayString* ea = new CArrayString;
      CArrayString* br = new CArrayString;
      CArrayInt *ac=new CArrayInt;

      ea.Add("RemoteProtectedEA");
      br.Add(broker);
      ac.Add((int)account);

      params.Add(ea); params.Add(br); params.Add(ac);

      CXMLRPCQuery query("isValid",params);

      result=s.execute(query);

      CArrayObj *resultArray=result.getResults();
      if(resultArray!=NULL && resultArray.At(0).Type()==TYPE_BOOL)
        {
         CArrayBool *stats=resultArray.At(0);

         license_status=stats.At(0);
        }
      else license_status=false;

      if(license_status==true) printf("License valid.");
      else printf("License invalid.");

      delete params;
      delete result;
     }
   else Print("License server not connected.");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(license_status==true)
     {
      // license valid
     }
  }
//+------------------------------------------------------------------+

If you execute both scripts you should be able to add a remote license for your account number. The remote license can be used as well for time-limit license or a password license that can be remotely deactivated after a trial period. For example you would give an EA for someone for 10 days testing, if he does not satisifed with the product you deactivate the license or in the case he is satisfied you can activate the license for any given period of time.


6. Secure License Encryption

The ideas presented in the last paragraph used Remote Procedure Calls to exchange information between license server and client terminal. This could be possibly hacked by using sniffer packages on a registered copy of the EA. By using sniffer application hacker is able to capture all TCP packets that are sent between two machines. We will overcome this problem by using base64 encoding for sending account data and receive encrypted message.

For a skilled person it would be also possible to use PGP and/or put all the code in a DLL for further protection. I came up with the idea that the message will be in fact another RPC message (like in Russian Matryoshka doll) that will be further converted into MQL5 data.

The first step is to add base64 encoding and decoding support for MQL5-RPC. Luckily this was already done for MetaTrader 4 at https://www.mql5.com/en/code/8098 by Renat therefore I only needed to convert it to MQL5.

//+------------------------------------------------------------------+
//|                                                       Base64.mq4 |
//|                      Copyright © 2006, MetaQuotes Software Corp. |
//|                                  MT5 version © 2012, Investeo.pl |
//|                                        http://www.metaquotes.net |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2006, MetaQuotes Software Corp."
#property link      "http://www.metaquotes.net"
 
static uchar ExtBase64Encode[64]={ 'A','B','C','D','E','F','G','H','I','J','K','L','M',
                                 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
                                 'a','b','c','d','e','f','g','h','i','j','k','l','m',
                                 'n','o','p','q','r','s','t','u','v','w','x','y','z',
                                 '0','1','2','3','4','5','6','7','8','9','+','/'      };
                                 
static uchar ExtBase64Decode[256]={
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  62,  -1,  -1,  -1,  63,
                    52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  -1,  -1,  -1,  -2,  -1,  -1,
                    -1,   0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,
                    15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  -1,  -1,  -1,  -1,  -1,
                    -1,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
                    41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
                    -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1 };
                               

void Base64Encode(string in,string &out)
  {
   int i=0,pad=0,len=StringLen(in);

   while(i<len)
     {
      
      int b3,b2,b1=StringGetCharacter(in,i);
      i++;
      if(i>=len) { b2=0; b3=0; pad=2; }
      else
        {
         b2=StringGetCharacter(in,i);
         i++;
         if(i>=len) { b3=0; pad=1; }
         else       { b3=StringGetCharacter(in,i); i++; }
        }
      //----
      int c1=(b1 >> 2);
      int c2=(((b1 & 0x3) << 4) | (b2 >> 4));
      int c3=(((b2 & 0xf) << 2) | (b3 >> 6));
      int c4=(b3 & 0x3f);
 
      out=out+CharToString(ExtBase64Encode[c1]);
      out=out+CharToString(ExtBase64Encode[c2]);
      switch(pad)
        {
         case 0:
           out=out+CharToString(ExtBase64Encode[c3]);
           out=out+CharToString(ExtBase64Encode[c4]);
           break;
         case 1:
           out=out+CharToString(ExtBase64Encode[c3]);
           out=out+"=";
           break;
         case 2:
           out=out+"==";
           break;
        }
     }
//----
  }

void Base64Decode(string in,string &out)
  {
   int i=0,len=StringLen(in);
   int shift=0,accum=0;

   while(i<len)
     {
      int value=ExtBase64Decode[StringGetCharacter(in,i)];
      if(value<0 || value>63) break;
      
      accum<<=6;
      shift+=6;
      accum|=value;
      if(shift>=8)
        {
         shift-=8;
         value=accum >> shift;
         out=out+CharToString((uchar)(value & 0xFF));
        } 
      i++;
     }
//----
  }
//+------------------------------------------------------------------+

For a detailed description of base64 encoding you may want to visit a Wikipedia article.

A sample test of MQL5 base64 coding and decoding script is presented below:

//+------------------------------------------------------------------+
//|                                                   Base64Test.mq5 |
//|                                      Copyright 2012, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"

#include <Base64.mqh>

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   string to_encode = "<test>Abrakadabra</test>";
   
   string encoded;
   string decoded;
   
   Base64Encode(to_encode, encoded);
   
   Print(encoded);
   
   Base64Decode(encoded, decoded);
   
   Print(decoded);
   
  }
//+------------------------------------------------------------------+

The script produces the following result.

DK      0       Base64Test (EURUSD,H1)  16:21:13        Original string: <test>Abrakadabra</test>
PO      0       Base64Test (EURUSD,H1)  16:21:13        Base64 encoded string: PHRlc3Q+QWJyYWthZGFicmE8L3Rlc3Q+
FM      0       Base64Test (EURUSD,H1)  16:21:13        Base64 decoded string: <test>Abrakadabra</test>

The validity of encoding can be simply checked in Python in 4 lines of code:

import base64

encoded = 'PHRlc3Q+QWJyYWthZGFicmE8L3Rlc3Q+'
decoded = base64.b64decode(encoded)
print decoded

<test>Abrakadabra</test>

The seconds step is to encrypt XMLRPC result in base64 (aka Matryoshka technique):

import base64
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler

class RequestHandler( SimpleXMLRPCRequestHandler ):
    rpc_path = ( '/RPC2', )
    

class RemoteLicenseExampleBase64(SimpleXMLRPCServer):

    def __init__(self):
        SimpleXMLRPCServer.__init__( self, ("192.168.2.103", 9099), requestHandler=RequestHandler, logRequests = False)
        
        self.register_introspection_functions()
        self.register_function( self.xmlrpc_isValid, "isValid" )        
        
        self.licenses = {} 
        
    def addLicense(self, ea_name, broker_name, account_number):
        if ea_name in self.licenses:
            self.licenses[ea_name].append({ 'broker_name': broker_name, 'account_number' : account_number })
        else:
            self.licenses[ea_name] = [ { 'broker_name': broker_name, 'account_number' : account_number } ]
             
    def listLicenses(self):
        print self.licenses
        
    def xmlrpc_isValid(self, ea_name, broker_name, account_number):
        isValidLicense = False
        
        ea_name = str(ea_name)
        broker_name = str(broker_name)
        
        print "Request for license", ea_name, broker_name, account_number
        
        try:
            account_number = int(account_number)
        except ValueError as error:
            return isValidLicense
    
        if ea_name in self.licenses:
            for license in self.licenses[ea_name]:
                if license['broker_name'] == broker_name and license['account_number'] == account_number:
                    isValidLicense = True
                    break
                
        print "License valid:", isValidLicense
        
        # additional xml encoded with base64
        xml_response = "<?xml version='1.0'?><methodResponse><params><param><value><boolean>%d</boolean></value></param></params></methodResponse>"
        
        retval = xml_response % int(isValidLicense)
        
        return base64.b64encode(retval)
    
if __name__ == '__main__':
    server = RemoteLicenseExampleBase64()
    server.addLicense("RemoteProtectedEA", "MetaQuotes Software Corp.", 1024221)
    server.addLicense("RemoteProtectedEA", "MetaQuotes Software Corp.", 1024223)
    
    server.listLicenses()
    server.serve_forever()        

After the license is encrypted we can use MQL5-RPC method for converting decrypted message back into MQL5 data:

//+------------------------------------------------------------------+
//|                                      RemoteProtectedEABase64.mq5 |
//|                                      Copyright 2012, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"

#include <MQL5-RPC.mqh>
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayInt.mqh>
#include <Arrays\ArrayString.mqh>
#include <Arrays\ArrayBool.mqh>
#include <Base64.mqh>

bool license_status=false;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
/* License proxy server */
   CXMLRPCServerProxy s("192.168.2.103:9099");

   if(s.isConnected()==true)
     {
      CXMLRPCResult *result;

/* Get account data */
      string broker= AccountInfoString(ACCOUNT_COMPANY);
      long account = AccountInfoInteger(ACCOUNT_LOGIN);

      printf("The name of the broker = %s",broker);
      printf("Account number =  %d",account);

/* Get remote license status */
      CArrayObj* params= new CArrayObj;
      CArrayString* ea = new CArrayString;
      CArrayString* br = new CArrayString;
      CArrayInt *ac=new CArrayInt;

      ea.Add("RemoteProtectedEA");
      br.Add(broker);
      ac.Add((int)account);

      params.Add(ea); params.Add(br); params.Add(ac);

      CXMLRPCQuery query("isValid",params);

      result=s.execute(query);

      CArrayObj *resultArray=result.getResults();
      if(resultArray!=NULL && resultArray.At(0).Type()==TYPE_STRING)
        {
         CArrayString *stats=resultArray.At(0);

         string license_encoded=stats.At(0);

         printf("encoded license: %s",license_encoded);

         string license_decoded;

         Base64Decode(license_encoded,license_decoded);

         printf("decoded license: %s",license_decoded);

         CXMLRPCResult license(license_decoded);
         resultArray=license.getResults();

         CArrayBool *bstats=resultArray.At(0);

         license_status=bstats.At(0);
        }
      else license_status=false;

      if(license_status==true) printf("License valid.");
      else printf("License invalid.");

      delete params;
      delete result;
     }
   else Print("License server not connected.");

//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(license_status==true)
     {
      // license valid
     }
  }
//+------------------------------------------------------------------+

The result of running the script provided that RemoteLicenseExampleBase64 server is running is as follows:

KI  0  RemoteProtectedEABase64 (EURUSD,H1) 19:47:57  The name of the broker = MetaQuotes Software Corp.
GP  0  RemoteProtectedEABase64 (EURUSD,H1) 19:47:57  Account number =  1024223
EM  0  RemoteProtectedEABase64 (EURUSD,H1) 19:47:57  <?xml version='1.0'?><methodResponse><params><param><value><string>PD94bWwgdmVyc2lvbj0nMS4wJz8+PG1ldGhvZFJlc3BvbnNlPjxwYXJhbXM+PHBhcmFtPjx2YWx1ZT48Ym9vbGVhbj4xPC9ib29sZWFuPjwvdmFsdWU+PC9wYXJhbT48L3BhcmFtcz48L21ldGhvZFJlc3BvbnNlPg==</string></value></param></params></methodResponse>
DG  0  RemoteProtectedEABase64 (EURUSD,H1) 19:47:57  encoded license: PD94bWwgdmVyc2lvbj0nMS4wJz8+PG1ldGhvZFJlc3BvbnNlPjxwYXJhbXM+PHBhcmFtPjx2YWx1ZT48Ym9vbGVhbj4xPC9ib29sZWFuPjwvdmFsdWU+PC9wYXJhbT48L3BhcmFtcz48L21ldGhvZFJlc3BvbnNlPg==
FL  0  RemoteProtectedEABase64 (EURUSD,H1) 19:47:57  decoded license: <?xml version='1.0'?><methodResponse><params><param><value><boolean>1</boolean></value></param></params></methodResponse>
QL  0  RemoteProtectedEABase64 (EURUSD,H1) 19:47:57  License valid.

As you can see, the XML-RPC payload contains a string that is in fact a XML-RPC message encoded by base64. This base64 encoded message is decoded into XML string and later decoded into MQL5 data.


7. Advanced Anti-Decompilation Guidelines

As soon as MQL5 code gets decompiled even the most secure protections that are exposed to skilled reverse-engineer will be vulnerable for being cracked. After some googling I found a website that offers MQL5 decompiler but I simply suspect that this is a fake one made to take away money from naive people that would like to steal someone's code. Anyway, I did not try it and I might be wrong. Even if such solution existed you should be able to make a stronger protection by sending encrypted EA/indicator input parameters or object indexes passing.

It will be very hard for a hacker to obtain correct input parameters for the protected EA or see correct input values of the protected indicator which in turn will make it useless. It is also possible to send correct parameters if account ID match or send unencrypted fake parameters if account ID is not valid. For that solution one may want to use PGP (Pretty Good privacy). Even if the code is decompiled, data will be sent encrypted with private PGP key and EA parameters will be decrypted only when account ID and PGP key match.


Conclusion

In this article I presented a few ways to protect MQL5 code. I also introduced remote license concept via MQL5-RPC call and added base64 encoding support. I hope the article will serve as a basis to further ideas on how to secure MQL5 code. All source code is attached to the article.


Last comments | Go to discussion (9)
Peter Maxwell
Peter Maxwell | 23 Feb 2012 at 15:22
molanisfx:

The article gives some clues on how to deal with the issue. In reality the issue should be solved by the mt5 client. There are many platforms out there that use encription within the client software so the software and not the coder solves the issue.

Even for the MetaQuotes client software to implement that functionality is a difficult problem: the software will have to either contain or download a decryption key, which implies it is possible to compromise said key.  If a tamper-proof hardware device is used then unless the .ex5 is run *on* the tamper-proof hardware, it is still vulnerable as it must be decrypted at some point to be executed.  While I may not be able to implement such attacks, I know a fair number of people that can do it without much trouble at all.

In the threat model of an end user having access to the .ex5 that they can run, there is *no* secure solution.

This is the generic class of problem - in other words content protection - that companies/institutions use thin-client models for: that way the sensitive content cannot be downloaded but only used on the company/institution's servers.


Rashid Umarov
Rashid Umarov | 24 Feb 2012 at 07:44

You should know that all EX5-prorgrams are encrypted by strong keys. It is first point.

And second one - don't forget about Market - A Market of Expert Advisors for MetaTrader 5. All products in Market are encrypted by additional key for buyer. It means that nobody except legal user can launch such EX5-program.

Peter Maxwell
Peter Maxwell | 27 Mar 2012 at 03:43
Rosh:

You should know that all EX5-prorgrams are encrypted by strong keys. It is first point.

And second one - don't forget about Market - A Market of Expert Advisors for MetaTrader 5. All products in Market are encrypted by additional key for buyer. It means that nobody except legal user can launch such EX5-program.

You could also replace your front door with reinforced steel but it won't to anything to prevent entry if you leave the key on the doormat.  The protection you have described is fine to deter the casual coder but certainly not against a motivated attacker.

The question here is not the encryption - that's the easy part - but rather how you handle keys and the decryption process.

In the scenario where an attacker, in the classic parlance we'll call her, Mallory, wishes to copy and distribute an EX5 she has the key to then surely she can do by:

i. running the EX5 with the key;

ii. monitor the process as it is running, say by attaching a DLL to the process, and dumping the contents of the EX5 immediately after it is decrypted;

iii. reassemble the now plaintext EX5 (and potentially disassemble into source if can be bothered).

 

If your first point protection involves signature verification then all that the attacker need do to distribute files is distribute a different MQL executable with relevant public keys replaced.  If MQL5 uses a cryptographic API to verify cert then that can be ripped out the binary. This continues ad infinitum.

This is all before you start considering the nightmare that will be your key management.

 

While this sort of binary analysis is out of my capabilities these days, I know several people that could accomplish it without hassle.  So it is not the strength of the encryption you should be concerned about but rather that you have an invalid security model.  It isn't a particular problem per se, as nobody expects MetaTrader to solve that problem - there are other very usable solutions to this problem, specifically having the owner of MQL5/EX5 code run it on a separate sever they control and license access/signals to buyers.

 

So, I'm at a loss to understand why people are requiring this facility when it is so difficult to implement properly, at least without tamper proof hardware.

 

 

 

 

Joshua Graham
Joshua Graham | 6 Oct 2012 at 09:09
allicient:

You could also replace your front door with reinforced steel but it won't to anything to prevent entry if you leave the key on the doormat.  The protection you have described is fine to deter the casual coder but certainly not against a motivated attacker.

The question here is not the encryption - that's the easy part - but rather how you handle keys and the decryption process.

In the scenario where an attacker, in the classic parlance we'll call her, Mallory, wishes to copy and distribute an EX5 she has the key to then surely she can do by:

i. running the EX5 with the key;

ii. monitor the process as it is running, say by attaching a DLL to the process, and dumping the contents of the EX5 immediately after it is decrypted;

iii. reassemble the now plaintext EX5 (and potentially disassemble into source if can be bothered).

 

If your first point protection involves signature verification then all that the attacker need do to distribute files is distribute a different MQL executable with relevant public keys replaced.  If MQL5 uses a cryptographic API to verify cert then that can be ripped out the binary. This continues ad infinitum.

This is all before you start considering the nightmare that will be your key management.

 

While this sort of binary analysis is out of my capabilities these days, I know several people that could accomplish it without hassle.  So it is not the strength of the encryption you should be concerned about but rather that you have an invalid security model.  It isn't a particular problem per se, as nobody expects MetaTrader to solve that problem - there are other very usable solutions to this problem, specifically having the owner of MQL5/EX5 code run it on a separate sever they control and license access/signals to buyers.

 

So, I'm at a loss to understand why people are requiring this facility when it is so difficult to implement properly, at least without tamper proof hardware.

 

 

 

 


allicient brings up a lot of valid points.  For any real protection, I recommend the following:

For basic protection of the source code, stuffing as many functions as possible into a dll should do the trick.  Minimal code to interface between MT5 and programming is ok.  I would not trust the complete source code in ex4/ex5, unless you are giving your software away.  But this article is about protecting your intellectual property for paid/commercial products or free products where source code is not given.

For licensing, the best protection is having separate demo and live versions of the ex4/dll combination. The tradeoff is that it is a bit more of a hassle for the developer and client, as the developer has to maintain/compile two versions of their own software.  But you lessen the chance of someone gaining hacked access to full version of software; they only can use the demo version. A lot depends on the goal of the demo.  If you want to offer the client full functionality, then 

Also, implement licensing that may require that a small piece of essential code be run remotely before the local software can be run.  CNS SaaS (Software as a Service) has actually done just this, and it is called CCoHS (Call code on hosted server) functions.  You can place a limited portion of code needed for your EA to run properly in a hosted fashion.  Then even if someone runs off with the EA, or cracks the licensing dll somehow, it would still render the dll useless.  There is latency between the hosted code server and the EA and/or licensing servers, but you can work around that.  The implementation isn't too difficult, but the user does have to find a way to implement CCoHS on his/her own servers. When you are small, you can do this on a a budget server or wherever, and then scale up when your subscriber base grows.

Hosted functions, while the most expensive of all the options offers real protection in case someone were in fact able to decompile your dll, which is very unlikely.  They would most likely 'crack' the dll to get your software to work.  But without the required hosted functions, it would still be useless.  Unless I missed something here.

Kourosh Davallou
Kourosh Davallou | 29 Jun 2013 at 17:36
Thank for Article
Step on New Rails: Custom Indicators in MQL5 Step on New Rails: Custom Indicators in MQL5

I will not list all of the new possibilities and features of the new terminal and language. They are numerous, and some novelties are worth the discussion in a separate article. Also there is no code here, written with object-oriented programming, it is a too serous topic to be simply mentioned in a context as additional advantages for developers. In this article we will consider the indicators, their structure, drawing, types and their programming details, as compared to MQL4. I hope that this article will be useful both for beginners and experienced developers, maybe some of them will find something new.

Here Comes the New MetaTrader 5 and MQL5 Here Comes the New MetaTrader 5 and MQL5

This is just a brief review of MetaTrader 5. I can't describe all the system's new features for such a short time period - the testing started on 2009.09.09. This is a symbolical date, and I am sure it will be a lucky number. A few days have passed since I got the beta version of the MetaTrader 5 terminal and MQL5. I haven't managed to try all its features, but I am already impressed.

False trigger protection for Trading Robot False trigger protection for Trading Robot

Profitability of trading systems is defined not only by logic and precision of analyzing the financial instrument dynamics, but also by the quality of the performance algorithm of this logic. False trigger is typical for low quality performance of the main logic of a trading robot. Ways of solving the specified problem are considered in this article.

Using text files for storing input parameters of Expert Advisors, indicators and scripts Using text files for storing input parameters of Expert Advisors, indicators and scripts

The article describes the application of text files for storing dynamic objects, arrays and other variables used as properties of Expert Advisors, indicators and scripts. The files serve as a convenient addition to the functionality of standard tools offered by MQL languages.