MQL5 代码防护基础:从密码到密钥生成器的防盗逻辑(基础篇)
🔐

MQL5 代码防护基础:从密码到密钥生成器的防盗逻辑(基础篇)

(1/3)· 多数 EA 作者只加个密码框就以为安全,盗版序列号一上网防护即刻失效

偏理论进阶 第 1/3 篇
给 EA 加个输入框让用户输入密码,不等于真正保护代码。一旦破解序列号被发到论坛,作者只能发新版本拉黑旧码。先理清防护层级,比急着写加密函数更有用。

给 EA 加一道授权锁

在 MT5 上分发的 EA 若直接裸奔,很容易被二次打包倒卖。常见的防护手段包括本地密码校验、按机器指纹生成钥匙、限定运行期限,以及把验证请求发到远程服务器做许可证核对。 以社区里 2014 年 1 月的一则示例为例,该帖累计被查看 7496 次、收到 51 条讨论,说明这类需求在实盘开发者里一直有市场。外汇与贵金属杠杆高,未经授权的 EA 流传还可能夹带恶意逻辑,风险不小。 下面这段 MQL5 片段演示了最基础的本地时间限制思路,到期即禁止初始化: [int Year, Month, Day;] [datetime expiry = D'2024.12.31';] [if(TimeCurrent() > expiry) { Print("License expired"); return INIT_FAILED; }] 第一行取系统日期分量备用;第二行写死一个过期时间点;第三行在 EA 初始化时比对当前时间,超期直接返回失败状态,MT5 就不会加载策略。实际部署可把 expiry 换成远程下发的字段,降低被改本地时间绕过的概率。

MQL5 / C++
class="type">int Year, Month, Day;
class="type">class="kw">datetime expiry = D&class="macro">#x27;class="num">2024.12.class="num">31&class="macro">#x27;;
if(TimeCurrent() > expiry) { Print("License expired"); class="kw">return INIT_FAILED; }

「EA 防护从密码到远程许可的演进路径」

做 MQL5 开发的都绕不开一个问题:写出来的 EA、脚本或指标,怎么防止被人白嫖。本文这条线从最朴素的密码校验起步,逐步叠加密钥生成器、按经纪账户授权、时限锁,再到远程许可服务器的思路。 文中示例虽都挂在 EA 上,但同一套法则对脚本和指标同样成立。远程许可那块会直接复用 MQL5-RPC 框架里的 XML-RPC 调用能力,把 MetaTrader 5 当成客户端去问许可服务器拿授权。 在基础方案之上,还会引入 base64 编码做传输混淆,并给出 PGP 支持的建议,把 EA 和指标的保护等级再抬一档。MetaQuotes 官方市场自带的授权机制和本文思路不冲突,两者叠用反而让盗版成本更高。

◍ EA 的密码框怎么落地

给 MT5 上的 EA 加一层最基础的授权,通常就是弹个输入框让用户填序列号,逻辑和 Windows 或 Office 的激活码差不多。第一次加载后若密码不匹配,就别让策略跑单,这是多数小众指标作者的第一道防线。 下面这段存根代码用 CChartObjectEdit 在图表上画了一个可编辑文本框,并预置了 3 个允许密码,例如 "863H-6738725-JG76364"。密码在图表事件 CHARTEVENT_OBJECT_ENDEDIT 触发后于 OnChartEvent 中比对,这里只给了 OnInit 里建框、OnDeinit 里删框、OnTick 里看状态的骨架。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| PasswordProtectedEA.mq5 |
class=class="str">"cmt">//| Copyright class="num">2012, Investeo.pl |
class=class="str">"cmt">//| http://www.investeo.pl |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2012, Investeo.pl"
class="macro">#class="kw">property link "http:class=class="str">"cmt">//www.investeo.pl"
class="macro">#class="kw">property version "class="num">1.00"
class="macro">#include <ChartObjects/ChartObjectsTxtControls.mqh>
CChartObjectEdit password_edit;
const class="type">class="kw">string allowed_passwords[] = { "863H-class="num">6738725-JG76364",
                                     "145G-class="num">8927523-JG76364",
                                     "263H-class="num">7663233-JG76364" };

class="type">int   password_status = -class="num">1;
class="type">class="kw">string password_message[] = { "WRONG PASSWORD. Trading not allowed.",
                              "EA PASSWORD verified." };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   password_edit.Create(class="num">0, "password_edit", class="num">0, class="num">10, class="num">10, class="num">260, class="num">25);
   password_edit.BackColor(White);
   password_edit.BorderColor(Black);
   password_edit.SetInteger(OBJPROP_SELECTED, class="num">0, true);
class=class="str">"cmt">//---
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//---
   password_edit.Delete();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---
  if (password_status>class="num">0)
  {
   class=class="str">"cmt">// password correct
  }
  }
逐行拆一下关键点:第 8 行 CChartObjectEdit password_edit; 声明图表编辑框对象;第 9–11 行 allowed_passwords 是写死在程序里的白名单,共 3 条;第 14 行 password_status 初始为 -1 表示未验证;OnInit 里 Create(0,"password_edit",0,10,10,260,25) 在左上角 (10,10) 画了宽 260 高 25 的框;OnTick 仅判断 password_status>0 才视作通过。 别把白名单当铁壁 这种方案的软肋很实在:只要有一个人把可用序列号发到论坛上,防护就破了。作者能做的只有发新版本、把被盗码塞进黑名单,但在外汇和贵金属这种高杠杆市场里,未授权拷贝跑起来的风控漏洞可能比少卖几份授权更麻烦。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| PasswordProtectedEA.mq5 |
class=class="str">"cmt">//| Copyright class="num">2012, Investeo.pl |
class=class="str">"cmt">//| http://www.investeo.pl |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2012, Investeo.pl"
class="macro">#class="kw">property link "http:class=class="str">"cmt">//www.investeo.pl"
class="macro">#class="kw">property version "class="num">1.00"
class="macro">#include <ChartObjects/ChartObjectsTxtControls.mqh>
CChartObjectEdit password_edit;
const class="type">class="kw">string allowed_passwords[] = { "863H-class="num">6738725-JG76364",
                                     "145G-class="num">8927523-JG76364",
                                     "263H-class="num">7663233-JG76364" };

class="type">int   password_status = -class="num">1;
class="type">class="kw">string password_message[] = { "WRONG PASSWORD. Trading not allowed.",
                              "EA PASSWORD verified." };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   password_edit.Create(class="num">0, "password_edit", class="num">0, class="num">10, class="num">10, class="num">260, class="num">25);
   password_edit.BackColor(White);
   password_edit.BorderColor(Black);
   password_edit.SetInteger(OBJPROP_SELECTED, class="num">0, true);
class=class="str">"cmt">//---
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//---
   password_edit.Delete();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---
  if (password_status>class="num">0)
  {
   class=class="str">"cmt">// password correct
  }
  }

图表输入框里的密码校验逻辑

在 MT5 的 EA 或指标里,用 OnChartEvent 捕获用户在编辑框里的回车动作,是做本地轻量鉴权的常用手法。上面这段代码挂在一个名为 password_edit 的图表对象上,只在对象结束编辑且标识名为 "password_edit" 时才触发。 核心判断是拿编辑框当前文本和预存的 allowed_passwords 数组逐个比对。匹配成功就把 password_status 设成对应下标并跳出循环,失败则保持 -1。 随后根据 status 改写编辑框显示内容:status 为 -1 时塞入 password_message[0](通常是错误提示),否则塞入 password_message[1](可能是欢迎语)。这种写法不涉及网络请求,纯前端字符串比对,外汇或贵金属脚本里拿来做多账户分权可能够用,但明文比对本身高风险,别直接用于实盘资金相关校验。 想验证的话,在 MT5 新建指标工程,把 allowed_passwords 和 password_message 先写成字符串数组常量,拖一个 OBJ_EDIT 到图表并命名 password_edit,贴入下面代码看回显即可。

MQL5 / C++
class="type">void OnChartEvent(const class="type">int id,
                   const class="type">long &lparam,
                   const class="type">class="kw">double &dparam,
                   const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//---
   if(id == CHARTEVENT_OBJECT_ENDEDIT && sparam == "password_edit")
     {
      password_status = -class="num">1;
      
      for(class="type">int i=class="num">0; i<ArraySize(allowed_passwords); i++)
      if(password_edit.GetString(OBJPROP_TEXT) == allowed_passwords[i])
        {
         password_status = i;
         class="kw">break;
        }
        
      if(password_status == -class="num">1)
         password_edit.SetString(OBJPROP_TEXT, class="num">0, password_message[class="num">0]);
      else
         password_edit.SetString(OBJPROP_TEXT, class="num">0, password_message[class="num">1]);
     }
  }

「用规则化密钥锁死 EA 运行权限」

密钥生成器本质是一套按预设规则产出密码的机制,用来限制 EA 只在授权环境跑。最简单的形式是三段数字用连字符隔开,格式为 XXXXX-XXXXX-XXXXX,例如 3-4-5、18000-20000-20000,甚至 3708-102792-2844770 这类组合。 规则可以设定为第一段被 3 整除、第二段被 4 整除、第三段被 5 整除。位数和算法都能加码,比如把硬盘序列号或 CPU ID 揉进计算,让密码只认某一台硬件。 这种绑硬件的做法有边界:换机器或用 VPS 跑 EA 会失效,通常留两三个备用有效密码来兜底。MT5 社区市场里售卖的受保护 EA 就是这套逻辑。 下面这段存根演示了密码输入框与校验状态骨架。OnInit 里画出编辑框,OnTick 里仅当 password_status==3 才视作通过,其余情况不交易。 [CODE] 部分逐行看:前几行 property 声明版权与版本;包含两个系统头文件用于处理图表文本控件和字符串。password_edit 是输入框对象,divisor_sequence 数组存了 3.0/4.0/5.0 三个除数,对应三段校验。password_status 初始 -1 表示未验证,password_message 存了错误与通过两种提示。OnInit 在坐标(10,10)到(260,25)画白底黑框输入框并取消选中;OnDeinit 销毁它;OnTick 判断状态为 3 才放行,否则空转。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 KeyGeneratorProtectedEA.mq5 |
class=class="str">"cmt">//|                     Copyright class="num">2012, Investeo.pl                                |
class=class="str">"cmt">//|                         http://www.investeo.pl                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2012, Investeo.pl"
class="macro">#class="kw">property link      "http:class=class="str">"cmt">//www.investeo.pl"
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include <ChartObjects/ChartObjectsTxtControls.mqh>
class="macro">#include <Strings/String.mqh>
CChartObjectEdit password_edit;
CString user_pass;
const class="type">class="kw">double divisor_sequence[] = { class="num">3.0, class="num">4.0, class="num">5.0 };
                                
class="type">int     password_status = -class="num">1;
class="type">class="kw">string password_message[] = { "WRONG PASSWORD. Trading not allowed.",
                             "EA PASSWORD verified." };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   password_edit.Create(class="num">0, "password_edit", class="num">0, class="num">10, class="num">10, class="num">260, class="num">25);
   password_edit.BackColor(White);
   password_edit.BorderColor(Black);
   password_edit.SetInteger(OBJPROP_SELECTED, class="num">0, true);
class=class="str">"cmt">//---
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//---
   password_edit.Delete();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---
 if (password_status==class="num">3)
 {
   class=class="str">"cmt">// password correct
 }
 }
 
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 图表输入框里的分段校验逻辑

在 MT5 自定义指标或 EA 里,用 OnChartEvent 捕获 CHARTEVENT_OBJECT_ENDEDIT 事件,可以监听用户在图表对象(如密码框 password_edit)里敲完回车那一刻。上面这段把用户输入的字符串按两个连字符 '-' 拆成三段,再分别做整除校验。 代码先找首次和末次出现 '-' 的位置,若缺任一个或两位置重合,直接把提示语 password_message[0] 写回输入框并退出。之后用 Mid 切出三段转成整数,对 divisor_sequence[0~2] 三个除数做 MathMod 取模,全为 0 才让 password_status 累加到 3。 只有三段全部通过取模(password_status == 3),界面才显示 password_message[1],否则维持错误提示。你在 MT5 里把 divisor_sequence 设成例如 {3,7,11},就能逼着用户输入类似 '12-14-33' 这种满足模运算的串,外汇或贵金属脚本加这层交互校验,能挡掉大部分手滑输错,但任何终端脚本都只是辅助,实盘杠杆品种风险依旧很高。

MQL5 / C++
class=class="str">"cmt">//| ChartEvent function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//---
   if (id == CHARTEVENT_OBJECT_ENDEDIT && sparam == "password_edit" )
     {
      password_status = class="num">0;
      
      user_pass.Assign(password_edit.GetString(OBJPROP_TEXT));
      class="type">int hyphen_1 = user_pass.Find(class="num">0, "-");
      class="type">int hyphen_2 = user_pass.FindRev("-");
      
      if (hyphen_1 == -class="num">1 || hyphen_2 == -class="num">1 || hyphen_1 == hyphen_2) {
         password_edit.SetString(OBJPROP_TEXT, class="num">0, password_message[class="num">0]);
         class="kw">return;
      } ;
      
      class="type">long pass_1 = StringToInteger(user_pass.Mid(class="num">0, hyphen_1));
      class="type">long pass_2 = StringToInteger(user_pass.Mid(hyphen_1 + class="num">1, hyphen_2));
      class="type">long pass_3 = StringToInteger(user_pass.Mid(hyphen_2 + class="num">1, StringLen(user_pass.Str())));
      
      class=class="str">"cmt">// PrintFormat("%d : %d : %d", pass_1, pass_2, pass_3);
      
      if (MathIsValidNumber(pass_1) && MathMod((class="type">class="kw">double)pass_1, divisor_sequence[class="num">0]) == class="num">0.0) password_status++;
      if (MathIsValidNumber(pass_2) && MathMod((class="type">class="kw">double)pass_2, divisor_sequence[class="num">1]) == class="num">0.0) password_status++;
      if (MathIsValidNumber(pass_3) && MathMod((class="type">class="kw">double)pass_3, divisor_sequence[class="num">2]) == class="num">0.0) password_status++;
      
      if (password_status != class="num">3)
         password_edit.SetString(OBJPROP_TEXT, class="num">0, password_message[class="num">0]);
      else
         password_edit.SetString(OBJPROP_TEXT, class="num">0, password_message[class="num">1]);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
把许可逻辑交给小布盯盘跑一遍
这些防护思路在小布盯盘的 AIGC 诊断里已内置,打开对应品种页能看到 EA 授权状态异常提示,你专注策略本身就好。

常见问题

密码以明文或简单比对存在于客户端,破解者反编译或抓包即可提取允许数组,网上一发所有人都能用,作者只能更新版本拉黑。
它把可用密码收敛到可验证的数学规则上,分发时按规则批量出码,降低随机暴力穷举命中率,但仍属本地校验,需配合远程许可。
可以,原文明确相同法则适用于脚本及指标,初始化编辑框与 CHARTEVENT_OBJECT_ENDEDIT 检查逻辑通用。
能,小布盯盘的 AIGC 模块对品种页的 EA 授权状态做持续诊断,出现越权或失效倾向会直接标出,省去人工翻日志。
不算,base64 只是编码不是加密,原文将其作为增强远程许可可读性的手段,真正强度依赖 PGP 或远程服务器校验。