English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano
MQL5 kodunun güvenliğini sağlama: Parola Koruması, Anahtar Oluşturucular, Zaman Sınırları, Uzaktan Lisanslar ve Gelişmiş EA Lisans Anahtarı Şifreleme Teknikleri

MQL5 kodunun güvenliğini sağlama: Parola Koruması, Anahtar Oluşturucular, Zaman Sınırları, Uzaktan Lisanslar ve Gelişmiş EA Lisans Anahtarı Şifreleme Teknikleri

MetaTrader 5Örnekler | 16 Aralık 2021, 16:08
107 0
investeo
investeo

Giriş

Çoğu geliştiricinin kodlarının güvenliğini sağlaması gerekir. Bu makale, MQL5 yazılımını korumanın birkaç farklı yolunu sunacaktır. Makaledeki tüm örnekler Uzman Danışmanlara atıfta bulunacaktır ancak aynı kurallar Komut Dosyalarına ve Göstergelere de uygulanabilir. Makale basit parola koruması ile başlar ve anahtar oluşturucular, belirli bir aracı hesabını lisanslama ve zaman sınırı koruması ile devam eder. Daha sonra bir uzaktan lisans sunucusu konseptini tanıtır. MQL5-RPC çerçevesi hakkındaki son makalem, MetaTrader 5'ten herhangi bir XML-RPC sunucusuna Uzaktan Yordam Çağrılarını açıklamaktadır.

Bir uzak lisans örneği sağlamak için bu çözümden faydalanacağım. Ayrıca bu çözümün base64 kodlaması ile nasıl geliştirileceğini açıklayacağım ve MQL5 Uzman Danışmanlar ve Göstergeler için ultra güvenli koruma sağlamak amacıyla PGP desteğine önerilerde bulunacağım. MetaQuotes Yazılım Şirketi'nin kodu doğrudan MQL5.com Market kısmından lisanslamak için bazı seçenekler sunduğunun farkındayım. Bu, tüm geliştiriciler için gerçekten iyi ve bu makalede sunulan fikirleri geçersiz kılmıyor. Beraber kullanılan her iki çözüm, yazılım hırsızlığına karşı korumayı yalnızca daha güçlü ve daha güvenli hale getirebilir.


1. Şifre koruması

Basit bir şeyle başlayalım. Bilgisayar yazılımı koruması için en çok kullanılan ilk çözüm, parola veya lisans anahtarı korumasıdır. Kurulumdan sonraki ilk çalıştırma sırasında kullanıcı, bir yazılım kopyasıyla (Microsoft Windows veya Microsoft Office seri anahtarı gibi) bağlı bir parola eklemesi için bir iletişim kutusuyla sorgulanır ve girilen parola eşleşirse kullanıcının bir yazılımın tek bir kayıtlı kopyasını kullanmasına izin verilir. Kodu girmek için bir giriş değişkeni veya doğrudan bir metin kutusu kullanabiliriz. Örnek bir saplama (stub) kodu aşağıda gösterilmektedir.

Aşağıdaki kod, bir parola eklemek için kullanılan bir CChartObjectEdit alanını başlatır. Bir kullanıcının eklediği bir parolayla eşleşen önceden tanımlanmış bir izin verilen parola dizisi vardır. CHARTEVENT_OBJECT_ENDEDIT olayı alındıktan sonra, parola OnChartEvent() yönteminde kontrol edilir.

//+------------------------------------------------------------------+
//|                                          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]); 
      }
  }
//+------------------------------------------------------------------+

Bu yöntem basittir ancak birisinin parolayı ele geçirilmiş seri numaralarına sahip bir web sitesi üzerinde yayınlamasına karşı savunmasızdır. Yeni bir Uzman Danışman yayınlanana ve çalınan parola kara listeye alınana kadar EA yazarı hiçbir şey yapamaz.


2. Anahtar oluşturucu

Anahtar oluşturucular, önceden tanımlanan kurallara dayanarak bir dizi parolanın kullanılmasına izin veren bir mekanizmadır. Aşağıdaki anahtar oluşturucu için bir saplama (stub) sağlayarak genel bir bakış sunacağım. Aşağıda sunulan örnekte, anahtarın iki tire ile ayrılmış üç sayıdan oluşması gerekir. Bu nedenle bir parola için izin verilen biçim XXXXX-XXXXX-XXXXX'tir.

Birinci sayı 3'e, ikinci sayı 4'e ve üçüncü sayı 5'e bölünebilmelidir. Bu nedenle izin verilen parolalar 3-4-5, 18000-20000-20000 veya daha karmaşık olan 3708-102792-2844770 olabilir.

//+------------------------------------------------------------------+
//|                                      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]); 
      }
  }
//+------------------------------------------------------------------+

Tabi ki bir sayıdaki basamak sayısı belirli bir değere ayarlanabilir ve hesaplamalar daha karmaşık olabilir. Ayıca, hesaplamaya HDD seri numarası veya CPU kimliği eklenerek yalnızca belirli bir donanımla geçerli olan bir değişken eklemek de mümkündür. Böyle bir durumda, EA'yı çalıştıracak kişinin, donanıma dayalı olarak hesaplanan ilave bir oluşturucuyu çalıştırması gerekecektir.

Çıktı, bir keygen için bir girdi olacaktır ve oluşturulan parola yalnızca belirli bir donanım için geçerli olacaktır. Bu durumda, birinin bilgisayar donanımını değiştirmesi veya EA'yı çalıştırmak için VPS kullanması gibi bir sınırlandırma vardır, ancak bu iki veya üç geçerli parola verilerek çözülebilir. Bu, MQL5 web sitesinin Market bölümünde de geçerlidir.


3. Tek Hesap Lisansı

Herhangi bir aracının terminal Hesap numarası tek olduğu için, bu EA'nın bir veya bir dizi hesap numarasında kullanılmasına izin vermek için kullanılabilir. Böyle bir durumda, hesap verilerini almak ve bunları önceden derlenen izin verilmiş değerlerle karşılaştırmak için AccountInfoString(ACCOUNT_COMPANY) ve AccountInfoInteger(ACCOUNT_LOGIN) yöntemlerini kullanmak yeterlidir:

//+------------------------------------------------------------------+
//|                                           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
  } 
  }

Bu basit fakat oldukça güçlü bir korumadır. Dezavantajı ise, hesap veritabanına eklenen her yeni hesap numarası için EA'yı yeniden derlemenin gerekli olmasıdır.


4. Zaman-Sınırlı Koruma

Zaman-sınırlı koruma, örneğin yazılımın deneme sürümünün kullanılması gibi lisansın geçici olarak verildiği durumlarda veya lisansın aylık veya yıllık olarak verildiği durumlarda faydalıdır. Bunun Uzman Danışmanlar ve Göstergeler tarafından uygulanabileceği açıktır.

İlk fikir, sunucu saatini kontrol etmek ve buna dayanarak kullanıcının belirli bir süreliğine Göstergeyi ve Uzman Danışmanı kullanmasına izin vermektir. Süresi sona erdikten sonra, lisans veren, bunun işlevselliğini lisans sahibi için kısmen veya tamamen devre dışı bırakabilmektedir.

//+------------------------------------------------------------------+
//|                                         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."); 
  }

Tek dezavantajı, çözümün her lisans sahibi için ayrı ayrı derlenmesinin gerekmesidir.


5. Uzaktan lisanslar

Lisansı devre dışı bırakmak veya deneme süresini kullanıcı temelli olarak uzatmak için tam bir kontrole sahip olmak iyi olmaz mıydı? Bu, hesap adıyla bir sorgu gönderecek ve komut dosyasını deneme modunda çalıştırıp çalıştırmamaya veya devre dışı bırakmaya ilişkin değeri alacak olan MQL5-RPC çağrısını kullanarak yapılabilir.

Örnek bir uygulama için lütfen aşağıdaki koda bakın:

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()  

Bu, MetaTrader 5 için iki önceden tanımlanmış lisansla Python'da uygulanan basit bir XML-RPC sunucusudur. Lisanslar, 1024221 ve 1024223 hesap numaralarıyla varsayılan MetaQuotes demo sunucusunda (access.metatrader5.com:443) çalışan "RemoteProtectedEA" uzman danışmanı için ayarlanmıştır. Endüstriyel bir çözümün Postgresql'deki bir lisans veritabanını veya herhangi başka bir veritabanını kullanması muhtemeldir ancak yukarıdaki örnek uzak lisansları çok iyi yönettiği için bu makale için fazlasıyla yeterlidir.

Python'un nasıl kurulacağına dair kısa bir açıklamaya ihtiyaç duruyorsanız, lütfen "MQL5-RPC. MQL5'ten Uzaktan Yordam Çağrıları: Web Hizmeti Erişimi ve Eğlence ve Kar Amaçlı XML-RPC ATC Analizörü" yazısını okuyun.

Uzak lisansı kullanan EA'nın, lisansın geçerli olup olmadığına bağlı olarak "true" veya "false" Boole değer dönüşü yapan isValid() yöntemine bir uzaktan MQL5-RPC çağrısı hazırlaması yeterlidir. Aşağıdaki örnek, hesap korumasına dayanan örnek bir EA'yı göstermektedir.

//+------------------------------------------------------------------+
//|                                            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
     }
  }
//+------------------------------------------------------------------+

Her iki komut dosyasını da çalıştırırsanız, hesap numaranız için bir uzaktan lisans ekleyebiliyor olmanız gerekir. Uzaktan lisans, zaman sınırlamalı lisans ve bir deneme süresinden sonra uzaktan devre dışı bırakılabilen bir parola lisansı için de kullanılabilir. Örneğin, birisine 10 günlük deneme için bir EA verirsiniz, üründen memnun kalmazsa lisansı devre dışı bırakırsınız veya memnun kalması durumunda lisansı herhangi bir süre için etkinleştirebilirsiniz.


6. Güvenli Lisans Şifrelemesi

Son paragrafta sunulan fikirler, lisans sunucusu ve istemci terminali arasında bilgi alışverişi yapmak için Uzaktan Yordam Çağrılarını kullandı. Bunun, EA'nın kayıtlı bir kopyası üzerinde algılayıcı (sniffer) paketleri kullanılarak ele geçirilmesi muhtemeldir. Bilgisayar korsanı, algılayıcı (sniffer) uygulamasını kullanarak iki makine arasında gönderilen tüm TCP paketlerini yakalayabilir. Hesap verilerini göndermek ve şifreli mesajı almak için base64 kodlamasını kullanarak bu sorunun üstesinden geleceğiz.

Uzman bir kişi için, PGP kullanmak veya daha fazla koruma için tüm kodu bir DLL'ye koymak da mümkün olacaktır. Mesajın aslında daha sonra MQL5 verisine dönüştürülecek olan başka bir RPC mesajı ( Rus Matruşka oyuncağında olduğu gibi) olacağı fikri aklıma geldi.

Birinci adım, MQL5-RPC için base64 kodlama ve kod çözme desteği eklemektedir. Neyse ki, bu daha önce zaten MetaTrader 4 için https://www.mql5.com/tr/code/8098'de Renat tarafından yapılmıştı, bu nedenle benim yalnızca bunu MQL5'e dönüştürmem gerekiyordu.

//+------------------------------------------------------------------+
//|                                                       Base64.mq4 |
//|                      Copyright © 2006, MetaQuotes Software Corp. |
//|                                  MT5 version © 2012, Investeo.pl |
//|                                        https://www.metaquotes.net |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2006, MetaQuotes Software Corp."
#property link      "https://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++;
     }
//----
  }
//+------------------------------------------------------------------+

base64 kodlamasının ayrıntılı bir açıklaması için, bir Wikipedia makalesini ziyaret etmek isteyebilirsiniz.

MQL5 base64 kodlama ve kod çözme komut dosyasının örnek bir testi aşağıda sunulmuştur:

//+------------------------------------------------------------------+
//|                                                   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);
   
  }
//+------------------------------------------------------------------+

Komut dosyası, aşağıdaki sonucu üretmektedir.

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>

Kodlamanın geçerliliği, 4 satır kodla Python'da kolayca kontrol edilebilir:

import base64

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

<test>Abrakadabra</test>

İkinci adım, XMLRPC sonucunu base64'te şifrelemektir (diğer adıyla Matruşka tekniği):

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()        

Lisans şifrelendikten sonra, şifresi çözülen mesajı tekrar MQL5 verisine dönüştürmek için MQL5-RPC yöntemini kullanabiliriz:

//+------------------------------------------------------------------+
//|                                      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
     }
  }
//+------------------------------------------------------------------+

RemoteLicenseExampleBase64 sunucusunun çalışıyor olması koşuluyla komut dosyasını çalıştırmanın sonucu aşağıdaki gibidir:

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.

Görebileceğiniz gibi, XML-RPC yükü, aslında base64 tarafından kodlanmış olan bir XML-RPC mesajı olan bir dize içermektedir. Bu base64 ile kodlanmış mesajın kodu, XML dizesine çözülür ve daha sonra MQL5 verisine çözülür.


7. Gelişmiş Kaynak Koda Dönüştürmeyi Önleme Kılavuzu

MQL5 kodu kaynak koda dönüştürülür dönüştürülmez, uzman tersine mühendisliklere maruz kalan en güvenli korumalar bile geçilmeye karşı savunmasız olacaktır. Google'da biraz araştırdıktan sonra, MQL5 kaynak koda dönüştürücü sunan bir web sitesi buldum ancak bunun, birinin kodunu çalmak isteyen çaylak insanlardan para almak için yapılmış sahte bir site olduğundan şüpheleniyorum. Her neyse, denemedim ve yanılıyor olabilirim. Böyle bir çözüm olsa bile, şifrelenmiş EA/gösterge giriş parametreleri veya geçen nesne dizinleri göndererek daha güçlü bir koruma yapabiliyor olmanız gerekir.

Bir bilgisayar korsanının, korunan EA için doğru giriş parametrelerini elde etmesi veya korunan göstergenin doğru giriş değerlerini görmesi çok zor olacaktır, bu da onu işe yaramaz bir hale sokacaktır. Ayrıca hesap kimliği eşleşirse doğru parametreleri göndermek veya hesap kimliği geçerli değilse şifrelenmemiş sahte parametreleri göndermek de mümkündür. Bu çözüm için, PGP (Oldukça İyi gizlilik) kullanılmak istenebilir. Kod, kaynak koda dönüştürülse bile, veri özel PGP anahtarı ile şifrelenerek gönderilecek ve EA parametrelerinin şifresi yalnızca hesap kimliği ve PGP anahtarı eşleştiğinde çözülecektir.


Sonuç

Bu makalede, MQL5 kodunu korumanın birkaç yolunu sundum. Ayrıca MQL5-RPC çağrısı yoluyla uzak lisans konseptini tanıttım ve base64 kodlama desteği ekledim. Umarım bu makale, MQL5 kodunun nasıl güvenli hale getirileceği konusunda daha fazla fikir için bir temel oluşturur. Tüm kaynak kodları, makaleye eklenmiştir.


MetaQuotes Ltd tarafından İngilizceden çevrilmiştir.
Orijinal makale: https://www.mql5.com/en/articles/359

EX5 Kitaplıklarını Kullanarak Geliştirme Projelerinizi Öne Çıkarın EX5 Kitaplıklarını Kullanarak Geliştirme Projelerinizi Öne Çıkarın
Bir .ex5 dosyasında sınıfların/fonksiyonların uygulama ayrıntılarının gizlenmesi, teknik bilgi algoritmalarınızı diğer geliştiricilerle paylaşmanıza, ortak projeler oluşturmanıza ve bunları Web'de öne çıkarmanıza olanak sağlayacaktır. Ve MetaQuotes ekibi, ex5 kitaplık sınıflarının doğrudan kalıtım olasılığını gerçekleştirmek için elinden geleni yaparken, bunu hemen şimdi uygulayacağız.
Trademinator 3: Alım Satım Makinelerinin Yükselişi Trademinator 3: Alım Satım Makinelerinin Yükselişi
“Dr. Tradelove..." makalesinde, önceden seçilmiş bir alım satım sisteminin parametrelerini bağımsız olarak optimize eden bir Uzman Danışman oluşturduk. Ayrıca, yalnızca EA’nın altında yatan bir alım satım sisteminin parametrelerini optimize etmekle kalmayıp aynı zamanda birkaç alım satım sistemi arasından en iyisini seçebilen bir Uzman Danışman oluşturmaya karar verdik. Bakalım buradan neler çıkacak...
AutoElliottWaveMaker - Elliott Waves Yarı Otomatik Analizi İçin MetaTrader 5 Aracı AutoElliottWaveMaker - Elliott Waves Yarı Otomatik Analizi İçin MetaTrader 5 Aracı
Makale, MetaTrader 5'teki Elliott Wave analizi için manuel ve otomatik dalga etiketlemenin bir kombinasyonunu temsil eden ilk geliştirme olan AutoElliottWaveMaker'ın bir incelemesini sunar. Dalga analizi aracı, yalnızca MQL5'te yazılmıştır ve harici dll kitaplıklarını içermez. Bu, MQL5'te sofistike ve ilginç programların geliştirilebileceğinin (ve geliştirilmesi gerektiğinin) bir başka kanıtıdır.
Uzman Danışman Görsel Sihirbazı’nı Kullanarak Uzman Danışmanlar Oluşturma Uzman Danışman Görsel Sihirbazı’nı Kullanarak Uzman Danışmanlar Oluşturma
MetaTrader 5 için Uzman Danışman Görsel Sihirbazı, dakikalar içinde Uzman Danışmanlar tasarlamanıza olanak tanıyan kapsamlı bir önceden tanımlanmış alım satım blokları seti ile son derece kolay anlaşılır bir grafik ortamı sağlar. Uzman Danışman Görsel Sihirbazı'nın tıkla, sürükle ve bırak yaklaşımı, kalem ve kağıtla yaptığınız gibi forex alım satım stratejilerinin ve sinyallerinin görsel temsillerini oluşturmanıza imkan verir. Bu alım satım diyagramları, bunları kullanıma hazır Uzman Danışmanlara dönüştüren Molanis’ MQL5 kod oluşturucusu tarafından otomatik olarak analiz edilir. İnteraktif grafik ortamı tasarım sürecini kolaylaştırır ve MQL5 kodu yazma ihtiyacını ortadan kaldırır.