月咒授权系统

软件卡密授权怎么做:时间卡、次数卡、一卡一机的选型与接入指南

更新于 2026-10-29 · 适合人群:要给自己软件加授权的独立开发者、接私活的全栈、做小工具收年费的团队

给软件加授权这件事,绝大多数人第一次做都会踩同一批坑:卡密类型一开始就选错,导致后期加续费要改数据结构;机器码绑定做得太死,用户换台电脑就得找你人工解绑;卡密明文躺在数据库里,一次拖库全泄露。这篇按「选型 → 绑定策略 → 接口设计 → 安全落地」四段讲清,能直接照着做。

先想清楚:你要防的是谁

授权系统不是防破解的银弹。它真正能解决的是三件事:把卡密发出去、知道谁在用、能远程停掉。把这三件事做好,商业上已经足够;指望客户端里一段校验代码挡住逆向,是把力气用错了地方。

所以设计的重心应该放在服务端:卡密状态、到期时间、剩余次数、绑定关系全部以服务端为准,客户端每次启动或定时向服务端问一次「我还能用吗」。客户端被打补丁,最多是这一台机器绕过;服务端一封,全网失效。

一句话原则:授权状态的唯一真相在服务端,客户端只做展示。

卡密类型怎么选:时间卡 / 次数卡 / 混合卡

三条路的适用场景差别很大,选错了后面很难补。

类型怎么算适合什么容易踩的坑
时间卡 从激活那一刻起,加 N 天/月/年,到期失效 订阅制软件、年费工具、按月收费的服务 「激活时开始计时」还是「生成时开始计时」必须一开始定死;生成时计时会让囤卡的用户吃亏
次数卡 每次核销扣 1,扣到 0 停 按次计费:导出、跑批、生成图片、调用模型 并发核销会超扣,扣减必须是服务端原子操作,不能客户端报数
混合卡 有效期与次数同时存在,任一先到就拦 「一年内可用 500 次」这类套餐 续费时两个维度要一起加,只加一个会造成「续了钱却用不了」

关键决策:计时起点

时间卡有两种起点,选错会直接影响退款率:

无论选哪种,剩余时长要能实时算出来给用户看,而不是只在数据库里存一个到期时间戳就完事——用户最常问的问题就是「我这张卡还剩几天」。

要不要一开始就做「产品套餐」

如果你打算卖不止一种规格(月卡 / 季卡 / 年卡 / 500 次卡),建议第一天就把「套餐」这层抽出来:套餐定义时长、次数、解绑策略、限频,卡密只是套餐的一个实例。这样后面加规格、调价格、按套餐批量发卡都不用改表结构。等到有几百张卡再补这层,迁移会很痛。

一卡一机与机器码绑定:三种策略的取舍

「一卡一机」听着简单,实际是整套系统里最容易引起客服工单的地方。三种常见策略:

策略做法优点代价
不绑定卡密任何机器都能用零客服、实现最简单一张卡能被无限台机器共用
首绑锁定第一次验卡时把机器码写进卡,之后必须一致挡住大部分共用用户换机/重装系统就得解绑
首绑 + 自助解绑锁定,但允许用户自己解绑 N 次,两次之间设冷却既挡住共用,又不产生工单实现要多一套解绑状态机

机器码取什么

别只用 MAC 地址——虚拟机会变、USB 网卡会换。稳妥做法是多因子拼一个指纹再哈希:

machine_code = sha256(
  cpu_id + motherboard_serial + system_volume_serial + machine_guid
).slice(0, 32)

拼完只上传哈希,不上传原始硬件信息,省得被质疑收集隐私。哈希后长度固定,也方便在日志里做脱敏展示。

注意:机器码要允许「轻微变化」(换网卡、加内存不该导致失效)。指纹里只放稳定的部件,或者在服务端做模糊匹配(N 项里命中 M 项即视为同一台)。

换机解绑:自助解绑的三道闸

如果做自助解绑,这三个参数必须可配,缺一个都会出问题:

  1. 解绑次数上限:一张卡总共能解绑几次。不设上限等于没绑定。
  2. 冷却时间:两次解绑之间至少隔多久。挡住「解绑 → 给朋友 → 再解绑」的循环共用。
  3. 是否需要原设备确认:设备还在手上时,让原设备点确认;设备已经坏了,走后台人工。

解绑动作一定要写审计日志:谁、什么时候、哪张卡、从哪个机器码解到哪个。这类日志平时没人看,一旦有纠纷就是唯一凭据。

验卡接口怎么设计

一个够用的验卡接口只要三件事:认身份(api_key)、认卡(card_key)、认机器(machine_code)。

POST /api/v1/open/use_card
Content-Type: application/json

{
  "api_key": "YOUR_API_KEY",
  "card_key": "月咒-1234-5678-90AB",
  "machine_code": "8f3c1a9e5b7d2046"
}

← 200 OK
{
  "success": true,
  "code": "OK",
  "remain_days": 27,
  "remain_count": 460,
  "expires_at": "2026-11-25 14:03:00"
}

失败要把原因说清楚

别只返回 false。客户端要能区分「卡号错了」和「卡过期了」,前者提示用户检查输入,后者提示去续费。一套够用的原因码大概长这样:

原因码是给程序读的,另外再给一句人话文案给用户看。两者都返回,客户端按场景取用。

幂等与并发

次数卡的扣减要防重复提交:客户端网络抖动重发一次,不能扣两次。做法是让客户端带一个 request_id,服务端在短时间窗口内做去重;或者干脆把「扣减」放在一个独立的确认接口,验卡接口只做查询。

卡密怎么存、怎么传才不裸奔

这一节是被低估最多的地方。见过太多系统卡密直接明文存数据库,一次备份泄露,全部卡密被薅光。

落库:加密存,索引留哈希

-- 表里存这三列,不存明文
card_cipher  TEXT    -- v1.salt.iv.tag.cipher(AES-256-GCM)
card_hash    TEXT    -- HMAC-SHA256(pepper, card)
card_mask    TEXT    -- YZ**************66

传输:要不要上参数加密

HTTPS 已经挡住了链路窃听,参数加密解决的是另一个问题:客户端被逆向、密钥被提取,攻击者直接构造请求刷你接口。这时候把请求体做一层 AES 加密(密钥由服务端下发并定期轮换),配合时间戳与随机串防重放,能显著抬高成本。

诚实一点:参数加密抬高了门槛,但密钥最终在客户端手里,挡不住有心人。它真正的价值是拦住脚本小子的批量请求,不是对抗专业逆向。

其它几条便宜的加固

续费要不要一开始就留好

要。用户到期当天最想干的事就是续费,如果这时候你只能「再发一张新卡」,等于把用户推去重新激活、重新绑定,体验很差。

续费的设计要点:

上线前检查清单

想直接跳过这些实现细节

月咒授权系统把这些都做完了:卡密加密落库、一卡一机、自助解绑、续费、到期预警、开放接口与参数加密。

进入控制台 →