提到可编程逻辑控制器多数人脑海里浮现的是流水线上的气缸、变频器、传感器还有一大片看不太懂的梯形图。PLC嘛干的应该是顺序控制、逻辑控制、运动控制跟“随机密码”“动态验证码”这几个词放一起怎么听都像跨了次元。不过这几年我真的接过几个要在触摸屏上做“会变的锁”的项目用信捷PLC写随机数、生成一次性密码、做动态验证码甚至拿它当一台微型“授权服务器”。今天把这套思路展开聊聊包括为什么要在工业设备上干这种事、没有硬件随机源的PLC怎么产生随机数、动态码怎么做到让另一台设备也算得出来以及我踩过的那些坑。这篇文章适合两类人一是正在做设备权限管理、想防止现场人员乱改参数的工程师二是想给老旧设备加“试用期授权”或“按次验证”功能的开发者。如果你暂时没有具体项目纯粹好奇PLC能不能干这种事也能当一份挺有趣的控制算法案例看。文章不纠结某个精确型号我尽量把逻辑讲清楚迁移到其他品牌PLC也只是把指令换一换的事。1. 为什么会在PLC上搞随机密码和动态验证码1.1 工业现场的真实痛点大部分设备出厂时厂家都会在触摸屏上设一个固定密码来保护“工程师菜单”。一开始挺有用但用不了多久问题就来了现场操作工之间口口相传密码写在设备点检表上的都不少见甚至有老师傅直接把密码贴到触摸屏边框上。密码一旦在车间里“公开”跟没设锁基本没区别。更麻烦的是固定密码没办法定向回收你不能让某一个看过密码的人“忘掉”它。我自己遇到过的典型场景是这样的。设备供应商把一台注塑机相关设备卖到客户现场设备里有几十个工艺参数。操作工在一次误操作中进入了工程师菜单改了PID参数当天整批产品就废掉了。查下来原因很简单密码就是设备手册最后一页的那个初始密码谁都背得下来。客户要求加一道锁而且明确说不要固定密码最好每次不一样。另一个场景是远程售后。设备出故障后操作人员打售后电话售后工程师需要先判断“电话那头的人是不是现场设备前的操作工”。如果设备能生成一个动态验证码操作工把屏幕上的数字报过来售后那边一算就知道是不是这台设备、是不是这个时间点、这个人有没有权限。这就是很自然的动态验证码需求。还有一个更商业化的场景设备试用期授权。设备被发到客户那里免费试用30天到了时间需要输入一个解锁码才能继续用。解锁码不能是固定的否则客户之间互相分享也不能太复杂否则售后也不知道怎么算。这个时候基于设备信息和当前时间生成的动态授权码就是比较合适的方案。1.2 固定密码在工厂里几乎等于不设防你可以把固定密码理解为一把永远不会换齿的锁。齿轮磨损不磨损无所谓关键是钥匙从第一天开始就是同一把哪个车间里都可能会有人偷偷配一把。换成动态密码之后锁和钥匙都在变即使这次密码被看到过一段时间它也会自动失效。我做个简单的对比防护目标固定密码随机密码/动态验证码防误操作勉强密码一旦公开就失效每次开机/每次进入都换防住“路过看到”防人员间共享无法阻止有效期短共享后很快失效可追溯防重复使用密码本身长期有效单次有效用后作废售后电话验证不好验证谁都能报出密码按时间窗口计算远距离可验证实现成本零成本HMI自带功能需要PLC加一段逻辑研发成本高一些注意我说的是“防误操作”和“防低级别共享”不是“防黑客”。PLC的能力终究是逻辑控制设备不是加密机。这类方案的价值是把乱入工程师菜单的门槛抬高让人不能因为“猜到密码”或者“看到别人输过一次”就进去改参数。真正的安全性还是要靠工艺管理和设备管理制度来兜底。1.3 这个思路解决什么、不解决什么用随机数和动态验证码能解决的是“人”的问题密码会流传、会过期、会被人拍下来、会在电话里被报出去。但它解决不了“神”的问题程序本身没有加密、PLC程序可以被上载、注册表可以被清掉、时间可以被强改。我做项目时一般会跟客户讲清楚边界这套机制让普通操作工不能随便进但不能阻止一个懂PLC的人把程序重新下载一遍绕过你的锁。所以应用层级上我更推荐把随机密码用在“触摸屏操作员菜单和工程师菜单之间的门禁”上把动态验证码用在“远程电话支持、试用期授权、按次开放的临时权限”上。如果客户指着说“我要拿它当军工级身份认证”我就只能劝他把PLC方案降级成辅助手段核心认证放到上位机或者专门的硬件加密芯片里去。想明白这一点后面的事就顺了我们要做的东西不是高深的密码学而是利用PLC自带的时间、计数器、运算指令拼出一个让现场人员觉得“每次都不一样”但又可控的锁。2. 没有随机数硬件PLC的随机种子从哪里来2.1 PLC里天然存在的三个“熵源”PLC没有主板上的真随机数发生器但这不意味着它没有随机性来源。工程上常用的“熵源”有三个名字听起来很玄其实都是现场很普通的东西。第一个是扫描周期计数。PLC从开机开始不停地循环执行程序每次扫描都有一个计数在走。假设扫描周期是几毫秒一分钟跑上万次这个计数器一直在变化。如果你在某个时刻“截停”它得到的值就带上了那台PLC在这个时刻特有的运行状态。第二个是实时时钟的低字节。PLC的RTC寄存器保存年、月、日、时、分、秒。人的按键动作加上PLC扫描周期落到哪一秒完全是“听天由命”的。把秒的低两位或者更低位拿来用已经能提供不错的随机性。如果选型的时候选了带100ms级时间精度的高精度时钟模块那噪声源就更丰富了。第三个是模拟量输入的低位抖动。温度模块、压力变送器输入进PLC的AD值末几位一直在跳。这个抖动来自现场的真实噪声虽然幅度小但是天然的随机源。我在做演示项目的时候经常开一路悬空的模拟量通道纯粹为了“加盐”。用大白话讲随机种子就是一个“谁也猜不到的数”。它不需要特别完美只要每一次都不一样而且跟当前时刻绑定在一起就行。2.2 构造随机种子的三种实用方案方案一最简单适合入门。PLC里放一个32位计数器只要没有其他逻辑干扰就让它不停自加。当操作工按下“生成密码”按钮时把计数器的值和当前秒寄存器里的值拼在一起得到一个种子。这种方法你会马上发现一个问题如果操作工刚好在同一秒按下两次两个种子的区别就只剩计数器的差异了。虽然不至于完全相同但随机质量一般。方案二用“线性同余发生器”也就是常说的LCG。公式很简单X(n1) (a * X(n) c) mod m给它一个初始种子它每次运算都变出一个新数。比如用经典的32位参数a 1664525c 1013904223m 2^32在PLC里做这个的时候要注意a乘以X很容易超出单个寄存器的范围。好在32位PLC的算术运算天然发生溢出溢出的那部分就是取模的结果。你需要的其实是低32位溢出后留下的正是它。这也解释了为什么很多PLC随机数程序写出来“像是错的”因为程序员总想把乘法结果完整保存下来其实根本不用管。方案三多种来源混合。把扫描计数器、RTC秒、模拟量低位、甚至设备的站号全部做异或再喂给LCG。这样即使某一项被固定住其他项还是会带来变化。我在给一组设备做同样程序的时候会特意把设备编号加进去否则每台设备的密码生成序列是一样的一眼就能看出规律。2.3 随机性够不够你要防的到底是谁经常有人问我LCG算法早就被人研究透了能叫随机数吗确实LCG不是密码学安全的随机数。但对于这个应用场景我反问你几个问题现场谁会去采集你一天的密码样本然后反推出种子序列操作工的目的只是“别让我误改参数”而不是“我要破解这台PLC”。所以我的原则是目标根据威胁等级来确定。如果是防止车间里的误操作和随手拍密码LCG加时间种子已经完全够用。如果设备要联网、要跟云端做真正的身份校验那就别用PLC硬算了把OTP生成放到上位机或者服务器端PLC只负责显示结果和执行比较结果。这也是我测试了多个项目之后一直沿用的边界。3. 动态验证码的完整实现思路3.1 先想清楚“随机密码”和“动态验证码”的区别随机密码和动态验证码经常被混着说但它们的本质不一样。随机密码是PLC自己生成、自己验证的。设备上电后PLC根据某个种子生成一组数字显示在触摸屏上。操作工把这组数字输入进去PLC比对正确就解除锁定。它不需要别人知道算法因为验证方就是PLC自己。这类密码的特点是“每次都不一样”但必须关看屏幕才能输对。动态验证码则更接近银行U盾或者手机验证码的逻辑。PLC和远端验证方售后电脑、手机App、上位机共享同一个密钥和同一个时间基准。PLC根据当前时间和密钥算出一个6位数字。电话那头的售后工程师用同样的算法在电脑上算出同一个数字。两边不需要通信就能在几十秒内确认“你面前那台PLC算出的动态码跟我这边一致”。这才是真正意义上的动态验证码。很多项目做了一半卡住就是因为把这两件事弄混了。做了随机密码却期望远端的人能直接“知道”当前密码这是不可能的事除非中间有网络上报。所以动工之前先问自己谁验证怎么验证验证方手里有没有足够的信息3.2 时间窗口与算法选型PLC撑不起标准TOTP标准TOTP算法用的是HMAC-SHA1整个计算过程对PC来说一句话的事在PLC上却非常笨重。信捷PLC有梯形图、有ST结构文本但提供的指令集并不包含SHA1和HMAC你要自己用基本运算去拼一个SHA1在不少老机型上能把扫描周期拖到几十毫秒工程上不太值。更合理的是做“简化版动态验证码”用一个足够乱、足够稳定的算法把“时间窗口”和“密钥”搅在一起得到一个6位数字。算法设计原则有两条第一PLC能轻松实现第二PC上能用同样逻辑复算。我用的是LCG加时间窗口的方案。具体说就是先把当前时间切成窗口。比如窗口取60秒那么从某个基准时间开始每过60秒窗口编号就加1。PLC用窗口编号和密钥做异或然后再扔进LCG里迭代若干轮。最后对1000000取余数得到一个0到999999之间的数字补足6位就是动态码。这个方案不是标准TOTP安全性远不如HMAC-SHA1。但它有一个现实优势我在任何一台信捷PLC上都能用几条MUL、XOR、DIV指令写出来而且扫描周期几乎不受影响。对大多数工业场景这已经足够把“猜密码”的门槛抬得很高。3.3 在PLC里把RTC时间换算成可以计算的数值动态验证码绕不开时间。PLC的RTC寄存器读出来是年、月、日、时、分、秒分开看都没问题但要参与运算就得先变成“从某个基准时刻开始的总秒数”。否则你没法做除法、没法取窗口编号。这里有一个坑不少PLC的RTC寄存器默认是BCD编码。比如秒寄存器里显示0x35其实是35秒但你直接拿来做算术会把53当数算。所以在做换算之前一定要先用BCD转BIN的指令把时间转成普通二进制数。信捷的XC、XD系列都有对应的转换指令你在手册里找“BCD→BIN”或者“BIN→BCD”就行。转成普通数之后我习惯把时间换算成“从2000年1月1日0时开始的总秒数”。年份区间只在2000到2099之间时计算可以简化很多days (Y - 2000) * 365 (Y - 2000 3) / 4 month_cum[M - 1] D - 1其中month_cum是每月累计天数月份累计天数1月02月313月594月905月1206月1517月1818月2129月24310月27311月30412月334如果是闰年且月份大于2月额外加1天。这个简化算法到2099年之前都是对的因为2000年是闰年每4年一个闰年中间不存在能被100整除却不能被400整除的边界年份。算完天数之后乘以86400得到秒数再加上当前小时、分钟和秒换算出的秒数就是完整的时间戳。注意86400已经超过16位寄存器的范围这一步必须用32位运算。在梯形图里用32位加法、32位除法别让数据被截断。3.4 六位验证码生成与显示拿到时间戳之后先按窗口长度取整。假设窗口是60秒T 总秒数 / 60然后做密钥混合seed T XOR SECRETSECRET是你在PLC里预设的一个32位常数。同一个项目里各台设备的SECRET不要用相同的值。我通常的做法是把设备编号和时间数字做一次异或再作为密钥这样即使别人拿到了一台设备的程序也算不出另一台设备的动态码。然后把这个种子反复喂给LCG迭代几轮让输出更“散”。最后取1000000的余数得到6位动态码code seed % 1000000到这里有个容易忽略的细节如果结果是一个小于6位的数比如256触摸屏上直接显示数字会显示成“256”而不是“000256”。电话报码的时候用户可能会漏掉前面的零导致验证失败。解决办法有两个在HMI数值显示控件的属性里把显示位数设成6位并允许前导零填充或者干脆在PLC里把结果转换成字符串用字符串显示。多数信捷触摸屏都支持前导零填充这个功能只要在画面上设置一下就行了不用写额外逻辑。3.5 防止重放与强制失效动态码最怕的不是猜不到而是“被人看到了还有效”。如果密码周期是60秒操作工拍张照发给别人别人拿这个码在剩下几十秒内也能解锁。所以工程上必须加两道防线。第一道防线是使用状态记录。PLC把上次成功解锁的窗口编号存下来当下次有人输入验证码时先检查当前窗口编号和上次成功的窗口编号是否相同。相同就直接拒绝。这样同一个窗口内的验证码只能用一次。因为每个码都有时间戳和窗口绑定不同窗口的码不一样旧窗口的码即使被重放窗口编号对不上也不会通过。第二道防线是限制尝试次数。我在设备上设置了一个连续错误计数器连续输错3次后锁定五分钟期间HMI不再接受任何验证码输入。这能有效防止现场有人抱着“一个个试”的心态去试密码。反正6位数字有一百万种可能试错机会被掐死之后暴力破解基本不可能。这两道防线加起来动态码就从一个“时间相关的随机数”升级成了“单次有效且具备过期机制的一次性密码”。4. 实操过程从零搭一个“开机随机密码动态验证码”Demo4.1 硬件和软件准备要做这个Demo首先得有一台带实时时钟的信捷PLC。选信捷的XD系列一般都有RTCXC系列里要看具体型号买之前一定跟供应商确认“带不带时钟”。没有RTC的PLC也可以做随机密码但做动态验证码会很吃力因为你没有可靠的时间基准来做窗口判定。如果实在是没有RTC的机型可以用信捷触摸屏的时钟通过触屏和PLC的数据交换区把时间同步进去效果差不多但多一个环节我下面默认PLC自带时钟。软件方面去信捷官网下载对应系列的编程软件。XC、XD系列的软件不完全一样装错版本会打不开程序这是最容易卡住的第一步。同时把触摸屏组态软件也装上。如果你只是做逻辑验证不接屏幕也行用PLC编程软件的在线监控和强制功能也能看到结果但体验差不少。接线就没有什么特殊了PLC通电、编程线连电脑、触摸屏通讯线连PLC确保在编程软件里能搜索到设备并建立在线连接。4.2 第一步把随机数发生器写成可复用功能块不要直接把随机数逻辑堆在主程序里那样后期维护很痛苦。我建议做一个小功能块起名叫FB_Random输入一个“生成触发”信号输出一个32位随机值。下面这段是按照信捷XD系列ST风格写的伪代码作用是给大家理解逻辑真正移植到梯形图时对应操作就是MUL、XOR、MOV三件事。(* FB_Random: 随机数发生器 *) VAR_INPUT bGen : BOOL; (* 生成触发上升沿有效 *) END_VAR VAR_OUTPUT dwRand : DWORD; (* 32位随机值 *) END_VAR VAR dwSeed : DWORD; wScanCnt : WORD; END_VAR IF bGen THEN wScanCnt : wScanCnt 1; (* 经典LCG溢出即取模 *) dwSeed : dwSeed * 1664525 1013904223; (* 加入扫描计数和秒寄存器打散 *) dwSeed : dwSeed XOR (DWORD(wScanCnt) * 17); dwSeed : dwSeed XOR (DWORD(RTC_SEC) * 86543611); dwRand : dwSeed; END_IF我用到的RTC_SEC实际读值时要从对应时钟寄存器取出。如果你用的机型RTC返回的是BCD码那就要先做BCD转BIN。这一步的重要性我在后面还会反复说因为90%的“算出来的码不对”都是时间没有正确转换导致的。4.3 第二步实现动态验证码生成先做时间换算功能块把RTC转换成总秒数。再新建一个FB_OTP功能块输入密钥SECRET和当前总秒数输出6位动态码。核心逻辑就是前面讲过的时间窗口加LCG迭代。伪代码如下(* FB_OTP: 简化动态验证码 *) FUNCTION_BLOCK FB_OTP VAR_INPUT dwEpochSec : DWORD; (* 从2000年1月1日0点起的总秒数 *) dwSecret : DWORD; (* 设备私有密钥 *) END_VAR VAR_OUTPUT wCode : WORD; (* 6位动态码 *) END_VAR VAR dwT : DWORD; dwX : DWORD; i : INT; END_VAR dwT : dwEpochSec / 60; (* 60秒一个窗口 *) dwX : dwT XOR dwSecret; FOR i : 1 TO 8 DO dwX : dwX * 1664525 1013904223; END_FOR; wCode : WORD(dwX MOD 1000000);要注意伪代码里用的MOD和FOR在具体机型上有对应指令。梯形图里没有FOR就用一个计数器加跳转指令实现或者干脆复制八行重复运算。复制八行虽然丑但很实在扫描时间也更确定。我在实际项目中更倾向于复制展开不写循环因为PLC的循环处理不像高级语言那么灵活容易把自己绕晕。如果你希望PC端能复算可以用下面这段Python验证代码import time SECRET 20240401 # 与PLC里的SECRET保持一致 def seconds_since_2000(year, month, day, hour, minute, sec): month_cum [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] days (year - 2000) * 365 (year - 2000 3) // 4 month_cum[month - 1] day - 1 if year % 4 0 and month 2: days 1 return days * 86400 hour * 3600 minute * 60 sec def dynamic_code(secret, window_sec60): now time.localtime() epoch seconds_since_2000(now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec) t epoch // window_sec x (t ^ secret) 0xFFFFFFFF for _ in range(8): x (x * 1664525 1013904223) 0xFFFFFFFF return x % 1000000用的时候把PLC当前算出的动态码和控制电脑上跑出的dynamic_code值对一下必然是同一个数。我一般会留一个测试窗口专门打开Python把值算出来跟屏幕显示比对确认没问题再转给售后用。4.4 第三步HMI画面与权限绑定程序写好了动态码算出来了接下来要把它变成操作工真正能用的界面。HMI这边的设计逻辑其实比PLC程序更值得琢磨。我的习惯是做三个画面。第一个是正常操作主画面显示设备运行状态没有任何权限设置入口。第二个是验证码输入画面上面有一个当前动态码的显示框一个密码输入框还有一句“请输入6位动态码60秒内有效”。第三个是工程师设置画面放真正需要保护的参数和功能按钮。工程师设置画面不能从主画面直接点进去它的“画面启动条件”绑定一个PLC的M点比如M10。M10为ON时HMI才允许切换到工程师设置画面。M10初始为OFF所以任何人开机都停在主画面。操作逻辑是操作工或售后工程师在主画面长按某个隐藏按钮三秒触发PLC计算一次动态码动态码送到HMI显示同时画面切到验证码输入画面。输入完成后PLC调度比较逻辑比对输入值和当前计算出的码。一致则M10置ON允许进入工程师设置不一致则错误计数器加1。工程师设置画面上放一个“退出并上锁”按钮按下后M10复位为OFF同时PLC把使用状态寄存器清掉下次进入必须重新输入新一轮动态码。这样即使有人在工程师设置画面停留很久退出来后设备又回到锁定状态不会一直敞着门。4.5 完整逻辑组合注意事项这几个功能块组合起来的时候最容易出错的是触发沿处理。HMI上的按钮通常按下和松开会各发一次信号如果PLC直接用电平信号触发生成一次点击可能会让随机数连跳两次显示出来的码不是操作工真正想用的码。解决方法是把按钮信号处理成上升沿PLC里有专门的上升沿指令或者用“上一次状态取反”的方式自己做沿检测。另外动态码窗口边界也会带来困扰。如果用户刚好在59.9秒的时候看了一眼码等到输入完按下确认时窗口已经切换PLC会认为验证码不对。我一般会把窗口设置成60秒同时把校验窗口放宽成“当前窗口或前一个窗口”。也就是用户输入的码只要匹配当前窗口或者刚刚过去的那个窗口都算成功。这个策略很实用能避免很多无谓的售后投诉。5. 更多奇妙应用随机数还能这么玩5.1 设备试用期授权与解锁码动态验证码的另一个典型应用是设备试用期授权。原理是这样的PLC里存放一个设备编号和一个首次通电日期。每次上电检查当前日期如果超过了设定的试用天数设备就进入“演示模式”动作变慢、部分输出被屏蔽。操作工联系设备厂家厂家根据设备编号和当前日期用一个PC工具算出解锁码把解锁码输入PLC之后PLC重新计算新授权日期设备恢复全功能。这个方案比“固定序列号密码”强在哪儿呢固定序列号密码等于“每个设备一把永久的钥匙”只要泄出去一次整个厂家的设备都不安全。而按日期动态生成的解锁码有效期可以精确控制到某一天到期后继续算新的。厂家掌握算法和密钥客户永远不知道下一组解锁码是什么。实现上就是动态验证码算法换一层皮把“时间窗口”换成“授权日期”把“密钥”换成“设备号”把“验证码”换成“解锁码”。PLC里负责比较的不再是“当前动态码对不对”而是“输入解锁码展开之后能否让授权日期大于今天”。这套逻辑写一次后面做租赁设备、试用设备都能复用。5.2 混流产线的随机抽检很多产线上有抽检工位传统做法是“每生产500件抽1件”。但这个规律太固定操作工摸清了之后会有意识地在抽检点之前放行有问题的产品。如果PLC在500次计数之内随机选择一个序号作为抽检触发点比如这一次是第167件下一次是第398件操作工就没办法预测到底哪一件会被抽走。用随机数做这件事很简单每次计数时随机数发生器跑一次把产生的32位随机数对500取余数命中某个预设区间就触发抽检。例如余数落在0到4之间就发出抽检信号。这样平均每100件抽1件左右但具体位置完全随机。我实际做过的版本还会把随机抽检的结果记录下来生成抽检记录明细。PLC把抽检序号、时间、设备状态、操作工编号一起存进数据区后续再做质量追溯。这套东西的价值不在“随机”本身而在于“不确定性”让流程里的人不敢心存侥幸。5.3 模拟量仿真与培训演示调试设备的时候经常需要模拟传感器信号。没有真实传感器时最简单的手段是给PLC写一个固定值但固定值跟真实工况差别太大。用随机数发生器生成一个在某个范围内波动的模拟值就有点像真实传感器的噪声了。具体做法是让随机数发生器以固定周期跑然后对波动幅度取余数再叠加上一个基准值。比如需要模拟温度在80到90度之间浮动那就生成0到10之间的随机数加80。如果想让波动更平滑可以做一个一阶惯性环节Ynew Yold (X - Yold) / KK可以根据扫描周期调整。这样随机数的突变被拉平信号看起来就更接近真实工艺曲线。我用这套方法做过好几个培训演示台架效果比单纯写死一个数值好很多学员至少能学会看趋势而不是看一个不动的数。5.4 随机安全确认延时安全复位门闩很多设备都有。复位按钮通常要按住几秒钟才能解除报警但操作工一旦习惯了“按三秒松开”手指就容易形成肌肉记忆根本不过脑子。为了逼操作工真正关注当前状态有些项目会在复位确认环节加入随机延时。做法是按下确认按钮后PLC随机生成一个2到5秒之间的延时延时结束才真正执行复位。屏幕上的倒计时数字每次都不同操作工必须看着屏幕等待直到数字走完才能确认。这个功能同样用LCG取模实现wait_time 2 (rand MOD 3)也就是2到5秒之间随机。单看这个功能不复杂但它解决了安全确认流于形式的问题。工人跟我反馈说“这次怎么还要3.8秒”我回一句“下次可能是2.1秒”他反而笑了说这样至少不会闭着眼按。我个人觉得这种把随机数嵌进人机交互设计里的尝试才是“PLC奇妙应用”里最有意思的部分。6. 常见问题与排错实录6.1 验证码一直是同一个数最常遇到的现象是每次触发生成显示出来的密码都一模一样。原因基本都出在种子没有变化上。扫描计数器在PLC运行中确实一直在走但如果PLC没真正运行或者生成逻辑没有在每次扫描时都刷新计数器种子就可能被固定住。解决办法是先确认PLC处于运行状态然后在生成触发前把秒寄存器读回来人为制造“读两次取差值”的过程。如果RTC秒读出来一直是重复的先查RTC是否在走查看时钟寄存器是不是因为电池没电停在某个时间。还有一种情况是把程序放在仿真环境里跑仿真器的RTC不会真实走动这种只有接了真机才能暴露出来不算PLC程序本身的问题。6.2 时间导致验证码错位动态验证码对不上多半是时间基准出问题。第一个坎是BCD转BIN前面反复提了很多PLC时钟返回的是压缩BCD直接参与运算会得到完全不对的数值。第二个坎是PC端和PLC端使用的时间基准不一样。PLC的RTC是本地时间如果你PC端用UTC时间做计算算出来的码自然对不上。现场设备售后电脑都在同一个时区我一般直接统一用本地时间不搞UTC反而省心。还有一个容易忽略的是时钟电池。PLC断电后靠电池维持RTC电池没电后时间会退回某个初始值。客户过了几个月报修说验证码失效我一查时间已经回到出厂默认。现在我在交付项目时都会写一条维护提醒定期检查PLC时钟每年更换电池或者至少年度保养时同步一次时间。6.3 显示缺前导零明明算出来是000256HMI上却只显示256电话那头的售后直接懵了。这个问题的根源是HMI数值显示默认不填充前导零。解决办法要么在显示控件里把位数锁死为6位并开启前导零填充要么PLC生成码之后转换成字符串存在字符串寄存器里。多数信捷HMI支持前导零填充只是界面上一开始没注意找。我在项目测试清单里专门加了这一条防止交付后才发现。6.4 算法在PLC和PC上算出来不一样同一套算法PLC跑出来码跟PC跑出来不一样这是最容易让人崩溃的。常见原因是32位溢出和取余规则不完全一致。PLC上如果使用了有符号32位运算负数的取余结果跟Python不一样。解决思路是让双方都使用无符号32位空间。Python脚本里我用 0xFFFFFFFF做了截断PLC这边则要求乘法结果保留低32位不要被符号位干扰。取余数之前确保被取余的数落在0到2147483647正区间再对1000000取余。如果PLC的取模指令不好用可以绕开先做32位除法得到商再用“被除数 - 商 * 除数”得到余数。虽然啰嗦但所有PLC都支持结果也完全可控。6.5 程序安全与后续维护最后提醒一个容易被忽略的点如果程序不加密辛辛苦苦写的随机数算法、密钥、授权逻辑全部可以被别人上载出来。密钥一旦泄露动态验证码就形同虚设。交付时务必设置程序保护给PLC工程加上读密码防止客户端擅自上载源码。密钥不要直接写在HMI注释里也不要在梯形图注释里写得明明白白。我一般把SECRET拆成两三个常数分别放在不同位置在程序里再拼起来哪怕别人上载了梯形图也得花时间才能拼出完整密钥。6.6 排查速查表症状可能原因处理方式动态码每次生成都一样种子源不变检查扫描计数、RTC秒、仿真模式电话报码对不上时间基准或编码不一致确认BCD转换、确认本地时间、检查窗口显示少了前导零HMI控件未填充位数设为6位并开启前导零两边算法结果不同溢出/取余规则差异统一无符号32位截断一段时间后全部失效时钟电池耗尽换电池、每年同步时间输入正确码仍被拒窗口边界允许匹配当前或前一个窗口同一台设备码能被猜到密钥太简单或外泄加密程序、拆分密钥、加入设备号7. 做完这几个项目后的一些体会把这些功能做完之后我最大的感受是PLC做随机密码和动态验证码技术上并没有多高深真正难的是设定边界。什么时候用随机密码什么时候用动态验证码什么时候干脆别用PLC做这些判断决定了这个功能是“好用”还是“添乱”。我见过有人非要在老式XC系列上跑完整TOTP结果扫描周期被拖垮设备基本没法用也见过只需要一个开机锁定功能的项目却被做成了联网云平台对接最后现场网络一断连本地解锁都打不开。都是没想清楚边界。从项目实践来看信捷PLC做这种事有几个天然优势。一是价格低、普及率高很多中小设备本来就在用这款不需要额外加硬件。二是XD系列带RTC和功能块编程写随机数逻辑的空间足够。三是梯形图加ST混编的方式让这类偏算法逻辑能被封装起来不至于把主程序塞成一团乱麻。只要把随机源、时间换算、窗口校验、显示格式这几件事理清楚复用性非常高下次换个项目只是改改参数。如果你准备在自己项目里试试我不建议一上来就照着标准TOTP折腾。先拿一台PLC把我上面的随机数功能块跑通再做最简单的60秒动态码。等你在现场验证过“屏幕上的码和我PC上算出的码一样”的那一刻你就会理解这套玩法的魔力在哪里了。