加密授權(quán)功能塊:設(shè)計思路與SCL實現(xiàn)全解析)
這兩年接過不少西門子S7-1200/1500的項目十有八九的客戶都會提同一個需求能不能做一套動態(tài)加密功能塊程序讓設(shè)備只能在指定的PLC上跑授權(quán)到期自動鎖機程序被拷走也跑不起來。這個需求聽起來玄乎拆開看其實就是一套自研的授權(quán)與校驗邏輯。這篇文章我就把這個功能塊從設(shè)計思路、SCL代碼到踩過的坑完整抖出來想給自家設(shè)備加“防盜鎖”的按這個路子走基本能落地。1. 先拆概念S7-1200/1500動態(tài)加密功能塊到底在加密什么1.1 “動態(tài)加密”不是西門子標準庫而是一類自研授權(quán)邏輯很多人一聽到“動態(tài)加密”四個字以為博途里有個現(xiàn)成的加密功能塊拖進來設(shè)置個密碼就能用。這里必須先澄清一個認知西門子官方并沒有提供名為“Dynamic Encryption”的標準功能塊。博途自帶的塊保護叫“專有技術(shù)保護”Know-how protection它解決的是“上載后別人能不能看到你的梯形圖/SCL代碼”的問題本質(zhì)是靜態(tài)密碼保護。而行業(yè)里常說的“動態(tài)加密功能塊程序”是工程師基于SCL/STL自己寫的一套授權(quán)校驗邏輯典型包含設(shè)備唯一標識采集通常是CPU序列號、注冊碼/解鎖碼的動態(tài)生成與比對、運行許可控制三大部分。之所以叫“動態(tài)”是因為每次校驗的輸入不是寫死在程序里的固定常量而是CPU序列號、當前時間、運行次數(shù)這類會變化的變量。同樣的注冊碼換一臺PLC、過了授權(quán)期限統(tǒng)統(tǒng)失效。所以接下來所有內(nèi)容都是圍繞如何手寫這套自研邏輯展開的。搞清楚這個前提你才不會被網(wǎng)上那些玄乎的“加密神器”帶偏。1.2 三個典型業(yè)務(wù)場景鎖機、收尾款、防抄方案我接觸過的項目里動態(tài)加密功能塊基本都在解決三種現(xiàn)實問題第一個是設(shè)備分期付款。客戶只付了首付設(shè)備先拉去用但到了約定時間點設(shè)備自動鎖定想繼續(xù)用就得聯(lián)系廠家付清尾款換解鎖碼。很多做非標設(shè)備的兄弟應(yīng)該深有體會設(shè)備發(fā)出去收不回錢的情況太常見了機械鎖容易被拆程序鎖相對更體面。第二個是方案防抄。整套設(shè)備程序里最值錢的是工藝參數(shù)和核心邏輯你用專有技術(shù)保護把代碼藏起來之后別人確實看不到了但人家可以把整個項目上載下載到另一臺同型號PLC里照跑不誤。動態(tài)加密把程序“綁定”到特定CPU的序列號上換一臺PLC跑不起來才能攔住這種整體搬運。第三個是臨時授權(quán)。試機、借用、展會演示的設(shè)備給一個固定使用期限到期自動失效。省得派人去現(xiàn)場“拆機”也能防止客戶把試用設(shè)備當正式設(shè)備一直拖下去。1.3 S7-1200和S7-1500的能力邊界決定方案怎么選實話實說S7-1200和S7-1500都能做動態(tài)加密但能力邊界差異明顯方案選型必須提前想清楚。從指令集看S7-1200沒有官方AES/DES這類對稱加密庫S7-1500同樣沒有現(xiàn)成的加密指令所以常規(guī)做法是自研CRC32或者XXTEA這類輕量級算法。S7-1500的字符串處理和數(shù)組運算性能更強可以做更復(fù)雜的多輪混淆而S7-1200的CPU資源有限算法規(guī)模要控制尤其不能把大數(shù)組查表這種操作放在每個掃描周期里跑。從讀取設(shè)備標識看S7-1200從V4.0固件開始可以用Get_IM_Data指令讀IM0數(shù)據(jù)里面帶CPU序列號S7-1500同樣支持讀取速度和數(shù)據(jù)結(jié)構(gòu)完整度都更好。如果項目里用的是很老的S7-1200固件版本得先確認是否有這個指令。從實時時鐘看S7-1200斷電后靠超級電容維持RTC一般能撐十幾天到二十天時間太長會丟S7-1500加電池模塊或緩沖模塊會更可靠。這一點直接決定“時間鎖”方案敢不敢用。后面第4節(jié)我會專門講這個坑。還有一個容易被忽略的點掃描周期。CRC32加時間比對如果每個掃描周期都跑一遍S7-1200的掃描周期會被明顯拉長。所以動態(tài)加密功能塊絕不應(yīng)該放在OB1的每個循環(huán)里全程執(zhí)行要么放到定時中斷OB里節(jié)流執(zhí)行要么在程序里加一個“每N秒執(zhí)行一次”的判斷。2. 動態(tài)授權(quán)FB總體設(shè)計三層結(jié)構(gòu)和一整套授權(quán)狀態(tài)機2.1 三層架構(gòu)設(shè)備身份層、授權(quán)校驗層、運行控制層動手寫代碼之前先把架構(gòu)理清楚。我建議把整個動態(tài)加密功能塊拆成三層各管各的事調(diào)試的時候能少掉一半頭發(fā)。第一層是設(shè)備身份層。它的職責是拿到“這臺PLC是誰”的證據(jù)。最通用的做法是用Get_IM_Data讀取CPU序列號如果現(xiàn)場條件不允許讀序列號也可以用廠家自己寫入保持區(qū)的一個設(shè)備編號。前者好處是換PLC自動失效防挪機后者好處是邏輯簡單但防不了別人把整個項目連DB一起搬走。我的經(jīng)驗是優(yōu)先用CPU序列號。第二層是授權(quán)校驗層。廠家的離線授權(quán)工具拿到序列號和授權(quán)截止日期之后用特定算法生成注冊碼PLC內(nèi)的功能塊用同樣的算法算一遍兩者比對一致才放行。這一層的關(guān)鍵是算法一致性后面第3節(jié)詳細寫。第三層是運行控制層。校驗結(jié)果不是一個簡單的布爾量就完了它要管理一套授權(quán)狀態(tài)機分別處理未授權(quán)、已授權(quán)、試用期、授權(quán)過期、非法篡改等狀態(tài)。不同狀態(tài)對應(yīng)不同動作未授權(quán)可以跑30分鐘試機授權(quán)過期每小時停機5分鐘并亮黃燈非法篡改直接鎖機并記錄次數(shù)。狀態(tài)機的好處是把邏輯邊界劃清楚不會出現(xiàn)“到底要不要停機”這種模糊狀態(tài)。2.2 授權(quán)狀態(tài)機的設(shè)計細節(jié)狀態(tài)機的狀態(tài)定義我一般這樣枚舉狀態(tài)含義觸發(fā)條件設(shè)備行為0 未授權(quán)從未輸入過有效注冊碼上電且授權(quán)標志位為0允許試機N小時HMI提示輸入注冊碼1 已授權(quán)注冊碼校驗通過且在有效期內(nèi)校驗通過且當前日期截止日期全功能開放2 試用期沒有正式授權(quán)但處于首次試機時間內(nèi)首次上電時間試用時長當前時間全功能開放HMI顯示剩余試用時間3 授權(quán)過期超過截止日期當前日期截止日期且曾經(jīng)授權(quán)過鎖定工藝功能每小時解鎖5分鐘4 非法篡改檢測到時鐘回撥或數(shù)據(jù)異常歷史最大時間戳當前時間立即鎖機需廠家遠程解鎖碼恢復(fù)5 永久鎖定連續(xù)多次非法篡改非法篡改次數(shù)3永久鎖機只能返廠處理狀態(tài)轉(zhuǎn)換的邏輯集中在授權(quán)FB內(nèi)部輸出一個枚舉或整數(shù)型狀態(tài)值給HMI顯示。設(shè)備的行為動作則由主程序根據(jù)這個狀態(tài)值去控制不要在FB內(nèi)部直接寫一堆M輸出那樣復(fù)用性會很差。2.3 授權(quán)數(shù)據(jù)塊的設(shè)計原則公開區(qū)、隱藏區(qū)、保持區(qū)授權(quán)FB配套的數(shù)據(jù)塊是整個方案的命脈布局我習慣分三個區(qū)公開區(qū)給HMI讀寫包括輸入序列號顯示或者自動讀取后的顯示、輸入注冊碼的變量、授權(quán)狀態(tài)值、剩余天數(shù)、剩余試機時間。這些變量HMI能讀能寫方便現(xiàn)場人員操作。隱藏區(qū)是FB靜態(tài)變量或者私有DB包括鹽值數(shù)組、歷史最大時間戳、失敗嘗試次數(shù)、鎖定標志、授權(quán)校驗通過標志。這些變量只允許程序內(nèi)部訪問HMI不需要看到。在博途里如果用的是一般DB且沒有勾選“從HMI可見”HMI就訪問不到如果用的是優(yōu)化訪問DB注意在變量屬性里把“HMI可見”關(guān)掉。保持區(qū)是必須掉電不丟的變量授權(quán)激活標志、授權(quán)截止日期、歷史最大時間戳、試機首次上電時間、非法篡改次數(shù)。這些變量必須在DB屬性里設(shè)置為保持性Retain。如果忘記設(shè)保持性客戶現(xiàn)場一斷電授權(quán)狀態(tài)全丟每次開機都要重新輸注冊碼這體驗不用我多說。3. CRC32校驗函數(shù)與注冊碼生成器的SCL實現(xiàn)3.1 為什么選CRC32而不是AES或MD5嚴格從密碼學角度看CRC32不是加密算法它是校驗算法用做授權(quán)碼生成在密碼學上有先天弱點。但在PLC這個特殊環(huán)境里它反而是最務(wù)實的方案。原因有三第一S7-1200/1500沒有現(xiàn)成的MD5/SHA/AES算法庫自己實現(xiàn)AES需要幾百行查表代碼占用大量DB空間而且在S7-1200上跑得慢第二授權(quán)碼生成的場景不是網(wǎng)絡(luò)通信不需要對抗高強度惡意攻擊我們需要的是“生成一個夠長、夠隨機、不可批量推導(dǎo)的注冊碼”第三CRC32的運算量小S7-1200完全跑得動配合加鹽、多輪異或、字節(jié)反轉(zhuǎn)這些混淆手段即使別人拿到幾組注冊碼也推不出原始密鑰。簡單說CRC32在這里承擔的是“把一串輸入變成一串看上去沒規(guī)律的輸出”的散列職責目的不是防密碼學攻擊而是防止現(xiàn)場的客戶或者同行輕松算出注冊碼。3.2 CRC32逐位運算的SCL實現(xiàn)我直接用逐位運算法不搞查表法。雖然查表法速度快但需要256字的常量表在S7-1200上占DB空間而且調(diào)試時想單步看中間結(jié)果比較麻煩。逐位法代碼量小邏輯直觀幾十上百字節(jié)的數(shù)據(jù)算一次耗時在毫秒級對授權(quán)這種低頻操作完全夠用。FC代碼如下輸入是一個Byte數(shù)組和有效長度輸出CRC32值FUNCTION FC_CRC32_ByteArray : DWord { S7_Optimized_Access : TRUE } VERSION : 0.1 VAR_INPUT aData : Array[0..63] of Byte; // 待計算數(shù)據(jù)的緩沖區(qū) iLen : Int; // 有效數(shù)據(jù)長度不能超過64 END_VAR VAR dwCRC : DWord; dwByte : DWord; i : Int; j : Int; END_VAR BEGIN // 長度保護非法長度直接返回0避免數(shù)組越界 IF iLen 0 THEN FC_CRC32_ByteArray : 16#00000000; RETURN; END_IF; IF iLen 64 THEN iLen : 64; END_IF; dwCRC : 16#FFFFFFFF; FOR i : 0 TO iLen - 1 DO dwByte : BYTE_TO_DWORD(aData[i]); dwCRC : dwCRC XOR dwByte; FOR j : 1 TO 8 DO IF (dwCRC AND 16#00000001) 16#00000000 THEN dwCRC : (dwCRC SHR 1) XOR 16#EDB88320; ELSE dwCRC : dwCRC SHR 1; END_IF; END_FOR; END_FOR; FC_CRC32_ByteArray : dwCRC XOR 16#FFFFFFFF; END_FUNCTION提示一下不同TIA版本里Byte轉(zhuǎn)DWord的語法略有差異如果編譯器提示找不到BYTE_TO_DWORD就改成先轉(zhuǎn)USINT再賦值或者直接寫dwByte : aData[i];SCL在部分版本下會做隱式擴展。這種適配在真實工程里很常見別被編譯器的報錯嚇住。3.3 加鹽與多輪混淆讓注冊碼不可批量推導(dǎo)裸算CRC32有個問題如果把序列號和CRC結(jié)果對照幾組理論上可以通過暴力枚舉碰撞推出規(guī)則。所以必須加鹽和混淆。我項目里用的流程是#!/usr/bin/env python3 # 廠家離線注冊碼生成工具 import zlib def generate_regcode(serial: str, expire_date: str) - str: # 鹽值固定寫在算法里和PLC側(cè)保持一致 SALT bS7-AUTH-KEY-2024 # 第一步序列號 鹽值 授權(quán)截止日期 raw serial.upper().encode(ascii) SALT expire_date.encode(ascii) crc1 zlib.crc32(raw) 0xFFFFFFFF # 混淆1異或固定掩碼 crc1 ^ 0xA5A5A5A5 # 混淆2字節(jié)序反轉(zhuǎn) crc1 int.from_bytes(crc1.to_bytes(4, little), big) # 第二步用混淆后的值和序列號再算一次 raw2 crc1.to_bytes(4, big) serial.upper().encode(ascii) crc2 zlib.crc32(raw2) 0xFFFFFFFF # 注冊碼 16位字符串前8位CRC結(jié)果后8位截止日期 return f{crc2:08X}{expire_date} if __name__ __main__: serial input(請輸入CPU序列號: ).strip() expire input(請輸入授權(quán)截止日期(YYYYMMDD): ).strip() print(注冊碼:, generate_regcode(serial, expire))PLC側(cè)校驗的邏輯完全對應(yīng)先取用戶輸入的注冊碼字符串截出后8位作為截止日期然后把序列號、鹽值、這個截止日期拼起來算CRC再做一次異或和字節(jié)反轉(zhuǎn)再算第二次CRC最后把結(jié)果轉(zhuǎn)成8位十六進制字符串和注冊碼前8位比對。這里最容易被忽略的是字節(jié)序。Python里int.from_bytes(..., little)和PLC側(cè)DWord的存儲順序要保持一致不然兩邊算出來的結(jié)果永遠對不上。我第一次聯(lián)調(diào)時就在這上面卡了一個下午最后把中間值逐個打印出來對比才發(fā)現(xiàn)是字節(jié)序問題。3.4 注冊碼比對與防暴力嘗試比對注冊碼時有幾個實操細節(jié)值得注意不要在PLC側(cè)直接把整個長字符串比較正確做法是算完CRC后比較DWord數(shù)值避免大小寫、空格等字符串差異。如果實在要比較字符串統(tǒng)一轉(zhuǎn)大寫再比。HMI上要加防暴力嘗試機制。我見過現(xiàn)場操作工閑著沒事亂輸注冊碼輸錯十幾次把CPU搞進STOP的。我的做法是連續(xù)輸錯3次鎖定輸入框10分鐘用保持變量存失敗次數(shù)和鎖定起始時間。第4次再錯鎖定時間翻倍。這個機制在亞控、西門子精簡面板、WinCC上都很好實現(xiàn)。校驗通過之后立刻置位“授權(quán)激活”保持標志并把截止日期寫入保持區(qū)。之后每次掃描只需要查這個標志和日期不需要重復(fù)跑CRC計算。既省CPU又避免“同一個注冊碼每次上電都重新算一遍”帶來的潛在不一致問題。4. 動態(tài)時間鎖讓設(shè)備按授權(quán)周期運行并防時鐘回撥4.1 讀系統(tǒng)時鐘RD_LOC_T指令和DTL數(shù)據(jù)類型時間鎖的核心是讀取PLC實時時鐘。TIA Portal里S7-1200/1500用RD_LOC_T指令讀本地時間輸出是DTL數(shù)據(jù)類型的變量。調(diào)用方式RD_LOC_T(EN : #bTrigger, RET_VAL : #wRetVal, OUT : #dtlNow);DTL類型是一個結(jié)構(gòu)體里面的YEAR、MONTH、DAY、HOUR、MINUTE、SECOND字段可以直接訪問。我習慣把日期轉(zhuǎn)成一個整數(shù)再比較#iToday : #dtlNow.YEAR * 10000 #dtlNow.MONTH * 100 #dtlNow.DAY;這樣20250115就是一個整數(shù)跟授權(quán)截止日期比大小非常直觀。4.2 授權(quán)截止日期的兩種判斷方式第一種方式精確到天的簡單判斷。把截止日期存成YYYYMMDD格式的DInt每次上電和每天第一次運行OB時用當前日期和它比對。當前日期大于截止日期就切換為“授權(quán)過期”狀態(tài)。這種方式代碼量小完全夠用。第二種方式精確到分鐘。把DTL轉(zhuǎn)成“從2000年1月1日0點開始的分鐘數(shù)”這需要自己寫一個公歷日期換算函數(shù)處理閏年和每月天數(shù)。代碼會翻一倍但在按小時收費的設(shè)備租賃場景里值得做。我個人建議絕大多數(shù)設(shè)備鎖機場景用第一種就夠了真需要精確到分鐘的再上第二種。別一開始就整復(fù)雜現(xiàn)場維護的人會罵娘。4.3 時鐘回撥檢測用歷史最大時間戳防篡改只做“當前日期和截止日期比大小”有一個致命漏洞現(xiàn)場的人把PLC時鐘改回2020年設(shè)備就永遠不過期了。所以必須增加一個“歷史最大時間戳”檢測。具體實現(xiàn)在保持區(qū)定義一個變量hMaxDate存放設(shè)備運行至今見過的最大日期。每次做時間檢查時如果當前日期大于hMaxDate就更新它為當前日期如果當前日期小于hMaxDate說明時鐘被人往回撥了立即把授權(quán)狀態(tài)切換為“非法篡改”鎖機并記錄篡改次數(shù)。這個邏輯相當于給時間鎖裝了一個“只進不退”的棘輪只要撥回去一次就會被發(fā)現(xiàn)。但這里有個連鎖坑如果S7-1200斷電超過超級電容的維持時間RTC會回到出廠默認值比如2000年1月1日。這時當前日期遠小于hMaxDate會被誤判為非法篡改。所以啟用時間鎖的項目設(shè)備說明書或者現(xiàn)場標識一定要寫清楚長期斷電超過兩周需要重新上電或者干脆配一個不間斷電源給PLC保持供電。如果是S7-1500優(yōu)先選帶電池的緩沖模塊這個錢不能省。另外防止別人把整個保持區(qū)DB初始化來繞過時間鎖的招法如果授權(quán)激活標志被清零我建議把設(shè)備行為設(shè)定為“需要重新激活”而不是自動恢復(fù)未授權(quán)試機狀態(tài)。這樣即使對方操作失誤或者故意清DB設(shè)備也不會悄悄變成可免費試用的狀態(tài)至少會暴露問題。5. 疊加專有技術(shù)保護從“防看代碼”升級到“防復(fù)制”5.1 專有技術(shù)保護的正確設(shè)置動態(tài)加密功能塊解決的是“程序換個PLC還能跑”的問題但它本身也是代碼如果不做代碼保護別人上載項目之后直接看SCL你的鹽值、算法、密鑰就全暴露了。所以第一步還是得疊上博途的專有技術(shù)保護。設(shè)置方法很簡單在項目樹里右鍵你要保護的程序塊FB、FC、DB都支持選擇屬性在“保護”選項卡里勾選“專有技術(shù)保護”設(shè)置密碼。然后重新編譯勾選“支持專有技術(shù)保護”。編譯下載后別人上載這個塊只能看到接口定義看不到內(nèi)部實現(xiàn)。這里有幾個操作層面的提醒設(shè)置了專有技術(shù)保護的DB塊同樣會被隱藏內(nèi)容所以不要把授權(quán)鹽值這種敏感信息放在一個完全公開的DB里最好放在FB的靜態(tài)變量里隨FB一起保護。密碼一定要找安全的地方存著最好由兩個人分開保管。忘了密碼之后正規(guī)途徑無法找回只能把塊刪了重寫。塊的保護級別是“防君子不防小人”網(wǎng)上確實有工具能處理掉專有技術(shù)保護所以它只能當?shù)谝坏篱T。5.2 雙保險組合專有技術(shù)保護加動態(tài)加密專有技術(shù)保護負責“代碼不可見”動態(tài)加密負責“代碼不可異地運行”兩者疊加才是完整方案。我的做法是核心工藝FB全部加專有技術(shù)保護動態(tài)加密授權(quán)FB作為獨立塊輸出一個bAuthOK位給主程序主程序里所有關(guān)鍵工藝段的使用使能都引用這個bAuthOK位。一旦授權(quán)校驗失敗bAuthOK為0設(shè)備關(guān)鍵動作全部禁止只有面板燈和HMI提示還活著。注意一個細節(jié)不要在程序里只用一個M點或者D點做授權(quán)標志因為別人上載程序后監(jiān)控變量直接把那個點的狀態(tài)改成1就能繞過校驗。我的做法是授權(quán)狀態(tài)用枚舉值加多個位組合表示比如bAuthOK必須為1同時bLockFlag必須為0同時bTampered必須為0三個條件在多個位置交叉檢查。雖說不算真正的防逆轉(zhuǎn)但至少讓“找點改值”沒那么容易。5.3 關(guān)鍵OB分散校驗防止啟動邏輯被跳過還有一個容易被忽視的攻擊路徑很多人把授權(quán)初始化放在OB100里OB100是啟動組織塊只在CPU啟動時執(zhí)行一次。如果別人把OB100的調(diào)用刪了或者整個項目下載時故意不加載OB100那么授權(quán)初始化邏輯就不跑了設(shè)備可能直接進入一種“未初始化”狀態(tài)。針對這個我設(shè)計了一套“心跳互檢”機制OB100里調(diào)用FB_AuthInit做的事情是讀取序列號、算CRC、校驗注冊碼、把心跳標志H1置1、記錄本次上電時間。OB1每個掃描周期里調(diào)用FB_AuthCheck做的事情是檢查H1是否為1如果H1為0說明OB100沒跑直接判非法篡改同時檢查當前時間是否在授權(quán)期內(nèi)然后置心跳標志H2。OB10定時中斷里調(diào)用FB_AuthLoop每1000毫秒檢查一次H1和H2是否都為1只要發(fā)現(xiàn)心跳斷鏈就把授權(quán)狀態(tài)改成鎖定。三層互相看著誰被刪除或者跳過另外兩層馬上能發(fā)現(xiàn)。雖然不是絕對安全但破解成本又高了一截。對付現(xiàn)場客戶和一般同行已經(jīng)足夠了。6. 項目實測踩坑記錄四個最容易翻車的地方6.1 坑一字符串長度和類型轉(zhuǎn)換最容易把CPU搞進STOPS7-1200默認的String類型是256字節(jié)這個長度對注冊碼來說太大了而且HMI變量連接時長度不一致會導(dǎo)致字符串被截斷或者亂碼。我的建議是用受限字符串比如String[16]同時在功能塊接口定義里嚴格規(guī)定長度。另一個高頻崩潰點是SCL里處理String和Byte數(shù)組的轉(zhuǎn)換。我的經(jīng)驗是不要在FB內(nèi)部反復(fù)截取字符串而是在HMI側(cè)就把序列號和注冊碼都準備好PLC側(cè)只做一次字符串到字節(jié)緩沖區(qū)的轉(zhuǎn)換然后交給CRC函數(shù)。轉(zhuǎn)換時特別注意循環(huán)邊界數(shù)組越界在S7-1200上不是報警是直接STOP。生產(chǎn)設(shè)備在運行中CPU突然STOP這個后果想想就頭疼。安全寫法是先判斷長度再做循環(huán)。我在CRC函數(shù)開頭已經(jīng)加了長度保護這個習慣請務(wù)必保留。6.2 坑二斷電后RTC丟失時間鎖瞬間失效這個前面提到過但值得單獨拿出來說因為太容易踩了。S7-1200斷電后靠超級電容維持RTC一般能撐十幾天到二十天。如果設(shè)備停機超過這個時間再上電RTC會回到出廠默認時間比如2000年1月1日。后果有兩個方向如果做了時鐘回撥檢測設(shè)備會誤報非法篡改鎖機如果沒做設(shè)備可能因為“當前日期早于截止日期”而繼續(xù)運行等于時間鎖白做了。我處理這個問題的思路是分兩步。第一步在程序里判斷如果讀取到的時間年份小于2020年且授權(quán)激活標志為1就判定為“RTC異?!痹O(shè)備進入受限模式允許維持基本功能運行但核心工藝功能鎖定。第二步和設(shè)備說明書配合規(guī)定長期停機的設(shè)備每隔兩周必須上電一次。如果實在做不到定期上電那就換S7-1500加電池模塊。另外現(xiàn)場調(diào)試時有一個低級的坑新出廠的S7-1200RTC時間可能是錯的必須先校準時間再激活授權(quán)不然注冊碼里附帶的截止日期和實際日期對不上。我第一次做樣機測試時就被坑過后來習慣性在設(shè)備出廠前統(tǒng)一校準PLC時鐘。6.3 坑三優(yōu)化訪問DB的保持性設(shè)置細節(jié)多到防不勝防TIA Portal從V13開始推薦使用優(yōu)化訪問DB它對符號名和數(shù)據(jù)結(jié)構(gòu)做了優(yōu)化但也帶來一些新問題。保持性設(shè)置上優(yōu)化DB是在變量屬性里單獨勾選“保持性”而不是像傳統(tǒng)DB那樣在DB級別設(shè)置。我踩過的坑是有些數(shù)據(jù)類型在保持性上有長度限制比如超長的String或者大數(shù)據(jù)數(shù)組在部分CPU型號上配置保持性會導(dǎo)致編譯不通過。解決辦法是把需要保持的數(shù)據(jù)拆成多個小變量或者改用Array[0..n] of Byte這種基礎(chǔ)類型。還有一點下載程序時如果勾選了“初始化保持性存儲器”所有保持變量都會被清掉。這個操作往往發(fā)生在調(diào)試階段一不小心的操作。有一次我在現(xiàn)場調(diào)程序順手點了初始化客戶設(shè)備授權(quán)狀態(tài)被清了后來重新輸注冊碼才恢復(fù)。從那以后我在授權(quán)保持機制里加了一個“授權(quán)初始化標記”一旦發(fā)現(xiàn)保持區(qū)被清空HMI就會彈出一個明確的提示而不是讓設(shè)備在未知狀態(tài)下裸奔。6.4 反破解邊界動態(tài)加密能做什么做不到什么寫到最后必須說幾句實在話。PLC端的動態(tài)加密沒有任何一種方案能做到絕對安全。博途的專有技術(shù)保護可以被專業(yè)工具移除SCL邏輯上載后可以被分析注冊碼在HMI和PLC通信的變量區(qū)里可以被監(jiān)控抓包甚至有些人不看程序直接把授權(quán)判斷結(jié)果改掉。所以我一直跟客戶強調(diào)動態(tài)加密功能塊的目標不是“絕對無法破解”而是“破解成本高于重新開發(fā)一套設(shè)備方案的成本”。讓想抄的人覺得麻煩、讓想賴賬的人覺得不劃算這套程序就值回票價了。商業(yè)層面的防線可能比程序更有效合同里約定破解和繞過授權(quán)的違約責任、法律層面的知識產(chǎn)權(quán)保護、以及提供比破解更省心的正規(guī)售后授權(quán)服務(wù)。程序給技術(shù)兜底合同給商業(yè)兜底兩邊同時上鎖設(shè)備商的權(quán)益才能真正立得住。最后再分享一個體會這套動態(tài)加密方案我最自豪的不是CRC和狀態(tài)機設(shè)計得多巧妙而是它讓設(shè)備商在面對“客戶拖著尾款不付”的時候多了一個理直氣壯又不用撕破臉的談判籌碼。技術(shù)解決不了所有商業(yè)問題但能給商業(yè)問題多一個優(yōu)雅的解法。