Proteger el código MQL5 Protección con contraseña, generadores de claves, límites de tiempo, licencias remotas y técnicas de encriptación de claves de licencia de asesores expertos avanzadas
Introducción
La mayoría de desarrolladores necesitan tener su código protegido. Este artículo muestra distintas formas de proteger el software MQL5. Todos los ejemplos se refieren a asesores expertos pero las mismas reglas se aplican a los scripts e indicadores. El artículo comienza con una simple protección con contraseña y continúa con los generadores de claves, las licencias de cuentas de brokers y la protección con limitación de tiempo. Posteriormente, introduce el concepto de servidor de licencia remoto. Mi último artículo sobre el esquema conceptual MQL5-RPC describe las llamadas de procedimientos remotas desde Meta Trader 5 a cualquier servidor XML-RPC.
Usaré esta solución para proporcionar un ejemplo de licencia remota. También describiré cómo mejorar esta solución con la codificación de base 64 y proporcionaré algunos consejos sobre el soporte PGP para la protección ultrasegura de asesores expertos e indicadores en MQL5. Soy consciente de que MetaQuotes Software Corp. proporciona algunas opciones para las licencias del código directamente desde la sección del Mercado de MQL5.com. Esto es realmente muy positivo para los desarrolladores y no invalida las ideas presentadas en este artículo. Ambas soluciones usadas conjuntamente solo pueden hacer que la protección sea más fuerte y segura frente al robo de software.
1. Protección con contraseña
Vamos a comenzar con algo simple. La primera solución, la más usada, para la protección del software de un ordenador es la contraseña o la llave de licencia. Durante la primera ejecución, tras la instalación, se solicita al usuario, mediante un cuadro de diálogo, que introduzca una contraseña vinculada a una copia del software (como la llave de serie de Microsoft Windows o Microsoft Office) y si la contraseña introducida coincide el usuario puede usar una única copia del software registrada. Podemos usar una variable de entrada o un cuadro de texto directo para introducir el texto. Abajo se muestra un fragmento de código.
Este código inicializa un campo CChartObjectEdit usado para introducir la contraseña. Hay una matriz predefinida de contraseñas válidas que se compara con la contraseña introducida por el usuario. La contraseña se comprueba en el método OnChartEvent() después de recibir el evento CHARTEVENT_OBJECT_ENDEDIT.
//+------------------------------------------------------------------+ //| 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]); } } //+------------------------------------------------------------------+
Este método es simple pero vulnerable para el que quiera publicar la contraseña en un sitio web con números de serie pirateados. El autor del asesor experto no puede hacer nada hasta que se publique uno nuevo y la contraseña robada se incluya en una lista negra.
2. Generador de claves
Los generadores de claves son mecanismos que permiten usar un conjunto de contraseñas basadas en reglas predefinidas. Daré una visión general proporcionando un fragmento para un generador de claves más abajo. En el ejemplo que se presenta más abajo la clave debe estar formada por tres números separados por dos guiones. Por tanto, el formato permitido para una contraseña es XXXXX-XXXXX-XXXXX.
El primer número debe ser divisible por 3, el segundo debe ser divisible por cuatro y el tercero por 5. Por tanto, las contraseñas permitidas pueden ser 3-4-5, 18000-20000-20000 o la más complicada 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]); } } //+------------------------------------------------------------------+
Por supuesto, el número de dígitos en un número puede establecerse en un valor determinado y los cálculos pueden ser más complicados. Uno puede incluso añadir una variable que sea válida solo con un hardware determinado añadiendo un número de serie HDD o CPU ID a los cálculos. En dicho caso, la persona que ejecute el asesor experto tendría que ejecutar generadores adicionales calculados en base al hardware.
El resultado sería una entrada a un generador de claves y la contraseña generada sería válida solo para un software concreto. Esto tiene una limitación en caso de que alguien quiera cambiar de ordenador o con el uso de VPS para ejecutar asesores expertos, pero se podría resolver proporcionando dos o tres contraseñas válidas. Este es también el caso de la sección del Mercado en el sitio web de MQL5.
3. Licencia de cuenta única
Como el número de cuenta del terminal para un broker determinado es único, este puede usarse para permitir el uso del asesor experto en un número o en un conjunto de números de cuenta. En dicho caso, es suficiente usar los métodos AccountInfoString(ACCOUNT_COMPANY) y AccountInfoInteger(ACCOUNT_LOGIN) para leer los datos de la cuenta y compararlos con los valores permitidos precompilados:
//+------------------------------------------------------------------+ //| 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 } }
Esta es una protección simple pero muy robusta. La desventaja es que es necesario recompilar el asesor experto para cada nuevo número de cuenta añadido a la base de datos de la cuenta.
4. Protección con límite de tiempo
La protección con límite de tiempo es útil cuando la licencia se concede de forma temporal, por ejemplo para una versión de prueba del software o cuando se concede para un mes o un año. Es obvio que este tipo de protección puede aplicarse a los asesores expertos e indicadores.
La idea es comprobar el tiempo del servidor y en base a él permitir al usuario usar el indicador o el asesor experto dentro de un periodo de tiempo determinado. Después de que dicho periodo expire, quien concede la licencia puede deshabilitar total o parcialmente su funcionalidad al titular de la licencia.
//+------------------------------------------------------------------+ //| 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."); }
La única desventaja es que la solución necesitaría ser compilada por separado para cada licencia.
5. Licencias remotas
¿No estaría bien tener un control total para deshabilitar la licencia o extender el periodo de prueba para cada usuario? Esto puede hacerse simplemente usando la llamada MQL5-RPC que envía una consulta con el nombre de cuenta y recibe el valor para ejecutar el script en modo de prueba o para deshabilitarlo.
Por favor, lea el código a continuación para la implementación de un ejemplo:
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()
Este es un simple servidor XML-RPC implementado en Python con dos licencias predefinidas para Meta Trader 5. Las licencias se establecen para el asesor experto "RemoteProtectedEA" ejecutándose en el servidor demo de MetaQuotes (access.metatrader5.com:443) con los números de cuenta 1024221 y 1024223. Una solución industrial haría uso probablemente de una base de datos de licencias en Postgresql o cualquier otra base de datos pero el ejemplo anterior es más que suficiente para este artículo ya que controla las licencias remotas muy bien.
Si necesita una explicación breve sobre cómo instalar Python, por favor, lea "MQL5-RPC. Llamadas de procedimientos remotos desde MQL5: acceso a servicios web y analizador XML-RPC ATC para divertirse y conseguir beneficios".
El asesor experto que usa la licencia remota simplemente necesita preparar una llamada remota MQL5-RPC a su método Valid() que devuelve valores booleanos verdaderos o falsos dependiendo de la validez de la licencia. El ejemplo a continuación muestra un asesor experto basado en la protección de cuentas:
//+------------------------------------------------------------------+ //| 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 } } //+------------------------------------------------------------------+
Si ejecutamos ambos scripts podemos añadir una licencia remota para nuestro número de cuenta. La licencia remota puede usarse también para licencias con límite de tiempo o con contraseñas que pueden ser desactivadas remotamente después de un periodo de prueba. Por ejemplo, podría dar a alguien un asesor experto durante 10 días de prueba, si no queda satisfecho con el producto podríamos desactivar la licencia o, en caso de que el cliente quede satisfecho podemos activar la licencia para un periodo de tiempo determinado.
6. Encriptación de licencia
Las ideas recogidas en el último párrafo usaban las llamadas a procedimientos remotos para intercambiar información entre el servidor de licencias y el terminal de cliente. Esto puede ser probablemente pirateado usando paquetes rastreadores sobre una copia del asesor experto. Usando una aplicación de rastreo, el hacker puede capturar todos los paquetes TCP que se envían entre dos máquinas. Solucionaremos este problema usando una codificación de base 64 para enviar datos de la cuenta y recibir el mensaje encriptado.
Para una persona hábil sería también posible usar PGP y/o poner todo el código en una DLL para mayor protección. Se me ocurrió la idea de que el mensaje fuera de hecho otro mensaje RPC (como en la muñeca Matryoshka rusa) que sería posteriormente convertido a datos de MQL5.
El primer paso es añadir soporte de codificación y decodificación de base 64 para MQL5-RPC. Por suerte esto ya se hizo para Meta Trader 4 en https://www.mql5.com/es/code/8098 por Renat y por tanto solo necesité convertirlo a MQL5.
//+------------------------------------------------------------------+ //| 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++; } //---- } //+------------------------------------------------------------------+
Para una descripción detallada de la codificación en base 64 puede visitar el artículo de la Wikipedia.
A continuación se muestra una prueba de un script de codificación y decodificación en base 64 de MQL5:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
El script genera el siguiente resultado.
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>
La validez de la codificación puede simplemente comprobarse en Python en 4 líneas de código:
import base64 encoded = 'PHRlc3Q+QWJyYWthZGFicmE8L3Rlc3Q+' decoded = base64.b64decode(encoded) print decoded <test>Abrakadabra</test>
El segundo paso es encriptar el resultado de XMLRPC en base 64 (la técnica Matryoshka):
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()
Después de encriptar la licencia podemos usar el método MQL5-RPC para convertir el mensaje desencriptado a datos de MQL5.
//+------------------------------------------------------------------+ //| 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 } } //+------------------------------------------------------------------+
El resultado de la ejecución del script siempre que el servidor RemoteLicenseExampleBase64 se esté ejecutando es el siguiente:
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.
Como puede ver, la carga de XML-RPC contiene un string que es de hecho un mensaje XML-RPC codificado en base 64. Este mensaje codificado en base 64 se decodifica en un string XML y luego se decodifica a datos MQL5.
7. Instrucciones antidecompilación avanzadas
Tan pronto como el código MQL5 se decompila, incluso las protecciones más seguras están expuestas a la ingeniería inversa más hábil y serán vulnerables frente a la posibilidad de un descifrado. Después de buscar en internet encontré un sitio web que ofrece un decompilador de MQL5 pero sospecho que es falso y que tiene por finalidad conseguir dinero de gente ingenua. De cualquier forma, no lo intentaré y puede que me equivoque. Incluso si dicha solución existiera podríamos realizar una protección mucho más robusta enviando parámetros de entrada de un indicador/asesor experto encriptado o pasar índices de objetos.
Sería muy difícil para un hacker obtener los parámetros de entrada correctos para el EA protegido o ver los valores de entrada correctos del indicador protegido que a su vez lo haría inservible. También es posible enviar los parámetros correctos si el ID de la cuenta coincide o enviar parámetros falsos no encriptados si el ID de la cuenta no es válido. Para esta solución uno puede usar PGP (privacidad muy buena). Incluso si el código se decompila, los datos se enviarán encriptados con claves PGP privadas y los parámetros del EA serán desencriptados solo cuando el ID de la cuenta y la clave PGP coincidan.
Conclusión
Este artículo muestra distintas formas de proteger el software MQL5. También he presentado el concepto de licencia remota mediante llamada MQL5-RPC y he añadido el soporte de codificación en base 64. Espero que el artículo sirva como base para ideas posteriores sobre cómo proteger el código MQL5. Todo el código fuente se adjunta al artículo.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/359
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Hola buenas tardes consulta necesito ayuda para poder logar el uso de un EA por número de cuenta en mt4 y que me permita modificar y que solo puedan funcionar las cuentas que estén dentro de la licencia y que lo pueda habilitar y deshabilitar de forma remota ya que utilizó dos mt4 uno master y otro esclavo adjunto la descripción del código y como lo puedo mejor para que funciona bien ya que si ingreso otra cuenta que este por fuera de la licencia me funciona igual
<?xml version="1.0" encoding="UTF-8"?>
<license>
<programVersion>
</programVersion>
<licenseType>Full</licenseType>
<validityPeriod>
<endDate>1/18/2080</endDate>
</validityPeriod>
<Owners>
<ownerName>
<Account>6789</Account>
<Account>7994</Account>
<Account>22302</Account>
</ownerName>
</Owners>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>Cl8vF8+Uyyms3pEodjip5ol5J4s=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>p/NlTyHeUy5CVC01H/2WmvTcN8TqJhrInkhqtyzR6vqn/koJHqm6fiD6mepklpn4fud0em7vqOiyWcQ9KQ8jqoyoN9ExkJUEvepWBQT2HHHcaoW2kpV17ch+CZvtFX74cHE23WrOi9/p3RoK/4G0rsgvdjvefdZRQRsM5k=</SignatureValue>
</Signature>
</license>
Al compilar no me genera error, pero tampoco funcionan o no veo que funcionen, estoy usando el de "las licencias de cuentas" y el de "Limite de Tiempo"
Alguien sabe porque no se ejecutan
Hay que crearlo como Script, EA, ...?
Lo uso en MT5
Saludos cordiales
Al compilar no me genera error, pero tampoco funcionan o no veo que funcionen, estoy usando el de "las licencias de cuentas" y el de "Limite de Tiempo"
Alguien sabe porque no se ejecutan
Hay que crearlo como Script, EA, ...?
Lo uso en MT5
Saludos cordiales
Hola.
Yo usé el de límite de tiempo como un EA.
No tuve problemas con el código tal cual está escrito. Puse como fecha límite de uso el 3/enero/2020. Lo probé en backtesting (modo visual) desde el 1/enero/2020 y al llegar la fecha de inmediato me lanzó los mensajes que había expirado mi tiempo. Si quieres que el código termine o deje de funcionar, requiere que, en lugar de que solo te mande un mensaje cuando se cumpla la condición, el programa deje de funcionar.
El código como lo presentan, lo único que hace es avisar cuando se a cumplido el tiempo.
Saludos!!!!
Estos trozos de código tambien sirven para mt4? tienen algún video donde se explique visualmente como configurar un asesor experto ?
Saludos.