Access 中实现“一机一码”注册系统的原理与代码解析
在实际的企业信息化交付中,很多开发者依然会使用 Access 作为桌面级应用或者局域网管理系统的快速开发平台。利用 VBA 的强大生态,Access 能够以极低的成本解决中小型企业的核心痛点。 然而,在软件交付或试用阶段,我们经常面临一个棘手的问题:如何保护软件知识产权,防止用户随意拷贝和分发? 通常我们会将 本文将解析如何在 Access 项目中引入“一机一码”软授权机制。通过提取目标设备的固有硬件指纹,结合哈希密码学算法,实现业务终端与应用授权的强绑定。 “一机一码”认证模型的核心在于:硬件唯一性、防逆向伪造、轻量级部署。以下是该机制的技术原理: 在 VBA 中调用系统的 WMI 服务可以轻易获取硬件属性,这是该机制的基础。这里同时采集 CPU ID 与逻辑磁盘 C 盘序列号。 标准 MD5 会产出相对较长的文本,这里我们采用变种的轻量级 Hash 算法(类似 DJB2),利用位运算( “机器码”是给用户侧界面的,“注册码”是需要管理端工具单独生成的。二者在生成路径上的差异点在于核心加密密钥只存在于计算注册码的时候。 管理端由于持有私钥 新建一个通用模块,具体代码如下: 这里创建一个简单的窗体给大家演示一下,具体窗体如下: 窗体的具体代码,就是调用一下函数: 这里给大家看一下,运行效果: 将上述底层方法封装完毕后,你只需要在 Access 中配置入口宏。 任何暴露在客户端的代码都有被逆向的可能,即使是 我们可以运用异或运算( 采用“硬件信息采集 + Hash运算压缩”组合成的这套方案,是给 Access 系统加上商业护城河的一条务实捷径。无需搭建云端激活服务,几段代码就能有效规避 90% 的随意白嫖行为。 如果你正苦于自己辛辛苦苦开发的局域网/单机桌面应用被客户随手 U 盘拷走,不妨把这套机制嵌进你的项目里,花 10 分钟的时间,将纯技术转化成可掌控的生产力。 Access 开发」 专注于 Microsoft Access 开发与企业级应用,提供以下服务: 📚 技术培训 Access VBA 从入门到精通(线上/线下) Access + SQL Server 企业级开发实战 Access 系统性能优化与架构设计 💼 定制开发 企业 ERP/CRM/进销存等系统开发 旧系统升级与性能优化 🔧 技术支持 代码审查与重构建议 疑难问题远程诊断 一对一技术辅导 联系方式: 公众号后台留言 微信:edonsoft 公众号:access开发 技术改变业务,专注创造价值。1. 前言
.accdb 文件编译为 .accde 或 .mde 以隐藏核心源码,但这无法阻止物理文件的直接复制。2. 技术原理分析
阶段 核心实现与原理 指纹采集 使用 WMI(Windows Management Instrumentation)获取主板 CPU ID 及 C 盘卷序列号。增加“计算机名称”以降低同批次同型号硬件可能带来的碰撞率。 混淆加盐 在原始特征码中加入动态“盐值(Salt)”,防止懂技术的用户直接推导原始字符串。这部分通过异或运算隐藏在 VBA 数组中。 哈希降维 硬件信息组合后的字符串长且无规律,不适合作为机器码让用户手工复制或抄写。通过 DJB2 哈希算法变体,散列为 16 位紧凑格式( XXXX-XXXX-XXXX-XXXX)。注册生成 根据用户提交的 16 位机器码,加入管理员独占的“私有密钥(Secret Key)”,再次进行哈希运算生成配对的注册码。 3. 核心功能实现(附源码)
3.1 获取硬件指纹
' 获取CPU标识符 (通过WMI)
Private Function GetCPUId() As String
On Error GoTo ErrHandler
Dim objWMI As Object, colItems As Object, objItem As Object
Set objWMI = GetObject("winmgmts:\\.\root\cimv2")
Set colItems = objWMI.ExecQuery("Select ProcessorId From Win32_Processor")
For Each objItem In colItems
GetCPUId = Trim$(objItem.ProcessorId & "")
If GetCPUId <> "" Then Exit For
Next
If GetCPUId = "" Then GetCPUId = "NOCPUID"
Exit Function
ErrHandler:
GetCPUId = "NOCPUID"
End Function3.2 自定义哈希算法压缩长度
And、Xor),将其压制在 16 位十六进制的范围内。' 核心哈希函数:将长字符串映射为 0~65535 之间的整型哈希值
Private Function Hash16(ByVal s As String, ByVal seed As Long) As Long
Dim h As Long
h = seed And &H7FFF&
Dim i As Long
For i = 1 To Len(s)
' 乘以 33 并加上 ASCII 值,使用 &H7FFFFFFF 防止 VBA 长整型溢出
h = ((h And &HFFFF&) * 33 + Asc(Mid$(s, i, 1))) And &H7FFFFFFF
Next i
Hash16 = h And &HFFFF&
End Function3.3 机器码与注册码的生成分离
' 获取当前机器的机器码 (4组16进制)
Public Function GetMachineCode() As String
' 1. 采集硬件信息
Dim rawInfo As String
rawInfo = GetCPUId() & "|" & GetDiskSerial() & "|" & Environ("COMPUTERNAME")
' 2. 加入盐值防推断
Dim combined As String
combined = rawInfo & DecodeSalt()
' 3. 分段 Hash 映射
Dim h1 As Long, h2 As Long, h3 As Long, h4 As Long
h1 = Hash16(combined, &H1A3B)
'...略
GetMachineCode = Right$("0000" & Hex$(h1), 4) & "-" & Right$("0000" & Hex$(h2), 4) '...略
End FunctionDecodeKey(),当拿到机器码之后:' 根据机器码生成对应防伪注册码
Private Function ComputeRegCode(ByVal mcClean As String) As String
Dim combined As String
' 机器码加上私有密钥,进行不对称 Hash
combined = mcClean & DecodeKey()
Dim h1 As Long, h2 As Long, h3 As Long, h4 As Long
h1 = Hash16(combined, &H5183)
'...略
' 产出与该机器唯一匹配的授权识别码
ComputeRegCode = Right$("0000" & Hex$(h1), 4) & "-" & Right$("0000" & Hex$(h2), 4) '...略
End Function3.4 完整代码
Option Compare Database
Option Explicit
' 获取当前机器的机器码
' 格式: XXXX-XXXX-XXXX-XXXX (十六进制)
Public Function GetMachineCode() As String
Dim rawInfo As String
rawInfo = GetCPUId() & "|" & GetDiskSerial() & "|" & Environ("COMPUTERNAME")
Dim salt As String
salt = DecodeSalt()
Dim combined As String
combined = rawInfo & salt
Dim h1 As Long, h2 As Long, h3 As Long, h4 As Long
h1 = Hash16(combined, &H1A3B)
h2 = Hash16(combined, &H2C4D)
h3 = Hash16(combined, &H3E5F)
h4 = Hash16(combined, &H4A71)
GetMachineCode = Right$("0000" & Hex$(h1), 4) & "-" & _
Right$("0000" & Hex$(h2), 4) & "-" & _
Right$("0000" & Hex$(h3), 4) & "-" & _
Right$("0000" & Hex$(h4), 4)
End Function
' 根据机器码生成注册码
Public Function GenerateRegCode(ByVal machineCode As String) As String
Dim mcClean As String
mcClean = Replace(UCase$(Trim$(machineCode)), "-", "")
If Len(mcClean) <> 16 Then
GenerateRegCode = "错误:机器码格式不正确,应为16位十六进制字符(XXXX-XXXX-XXXX-XXXX)"
Exit Function
End If
GenerateRegCode = ComputeRegCode(mcClean)
End Function
' ==================== 内部实现 ====================
Private Function ComputeRegCode(ByVal mcClean As String) As String
Dim combined As String
combined = mcClean & DecodeKey()
Dim h1 As Long, h2 As Long, h3 As Long, h4 As Long
h1 = Hash16(combined, &H5183)
h2 = Hash16(combined, &H6295)
h3 = Hash16(combined, &H73A7)
h4 = Hash16(combined, &H4B9)
ComputeRegCode = Right$("0000" & Hex$(h1), 4) & "-" & _
Right$("0000" & Hex$(h2), 4) & "-" & _
Right$("0000" & Hex$(h3), 4) & "-" & _
Right$("0000" & Hex$(h4), 4)
End Function
Private Function Hash16(ByVal s As String, ByVal seed As Long) As Long
Dim h As Long
h = seed And &H7FFF&
Dim i As Long
For i = 1 To Len(s)
h = ((h And &HFFFF&) * 33 + Asc(Mid$(s, i, 1))) And &H7FFFFFFF
Next i
Hash16 = h And &HFFFF&
End Function
Private Function DecodeKey() As String
Dim b() As Variant
b = Array(116, 12, 70, 120, 12, 81, 30, 108, 12, 92, 77, 12, 75, 127, 13, 15, 13, 10)
Dim s As String, i As Long
For i = 0 To UBound(b)
s = s & Chr$(CLng(b(i)) Xor &H3F)
Next i
DecodeKey = s
End Function
' 解码盐值(运行时混淆)
Private Function DecodeSalt() As String
Dim b() As Variant
b = Array(23, 25, 27, 104, 106, 104, 99, 121, 8, 63, 61)
Dim s As String, i As Long
For i = 0 To UBound(b)
s = s & Chr$(CLng(b(i)) Xor &H5A)
Next i
DecodeSalt = s
End Function
' 获取CPU标识符 (通过WMI)
Private Function GetCPUId() As String
On Error GoTo ErrHandler
Dim objWMI As Object
Dim colItems As Object
Dim objItem As Object
Set objWMI = GetObject("winmgmts:\\.\root\cimv2")
Set colItems = objWMI.ExecQuery("Select ProcessorId From Win32_Processor")
For Each objItem In colItems
GetCPUId = Trim$(objItem.ProcessorId & "")
If GetCPUId <> "" Then Exit For
Next
If GetCPUId = "" Then GetCPUId = "NOCPUID"
Exit Function
ErrHandler:
GetCPUId = "NOCPUID"
End Function
' 获取C盘卷序列号 (通过WMI)
Private Function GetDiskSerial() As String
On Error GoTo ErrHandler
Dim objWMI As Object
Dim colItems As Object
Dim objItem As Object
Set objWMI = GetObject("winmgmts:\\.\root\cimv2")
Set colItems = objWMI.ExecQuery( _
"Select VolumeSerialNumber From Win32_LogicalDisk Where DeviceID='C:'")
For Each objItem In colItems
GetDiskSerial = Trim$(objItem.VolumeSerialNumber & "")
If GetDiskSerial <> "" Then Exit For
Next
If GetDiskSerial = "" Then GetDiskSerial = "NODISK"
Exit Function
ErrHandler:
GetDiskSerial = "NODISK"
End Function
3.5 创建窗体

Private Sub btnGenerateRegCode_Click()
Me.txtGenerateRegCode = GenerateRegCode(Me.txtMachineCode)
End Sub
Private Sub btnMachineCode_Click()
Me.txtMachineCode = GetMachineCode()
End Sub3.6 运行测试

4. 业务落地与使用方法
AutoExec 宏,配置 RunCode 行为指向拦截函数 =CheckRegistration()。ComputeRegCode() 函数算出激活码并交付。5. 进阶内容(静态混淆与反逆向)
.accde,VBA P-Code 依然有可能被还原。所以私钥不写明文是基础中的基础。XOR)在内存中动态还原秘钥,提高破解门槛,具体的功能就不再说明了,如果可以,大家可以自行优化。6. 总结