You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
143 lines
5.0 KiB
143 lines
5.0 KiB
// Package license 授权码编解码。
|
|
//
|
|
// 授权码固定 10 字节(20 个 hex 字符),分 5 段:段0 是随机种子,段1-4 是被种子
|
|
// 派生的混淆序列异或过的数据。码长与混淆结构在 v1/v2 之间保持不变。
|
|
//
|
|
// v1 把序号切成 批次(8位) + 批内序号(20位),批次每次生产 +1、上限 255,用尽即废。
|
|
// v2 取消批次字段,序号按产品累加、占满 32 位;批次改由独立的批次表承载。
|
|
//
|
|
// 逻辑布局(异或前。密文经混淆后不可肉眼分辨版本):
|
|
//
|
|
// hex位 1-4 5-8 9 10-12 13-16 17 18-20
|
|
// v1 seed productid probatch高4位 probatch低4位 随机4位 随机 随机
|
|
// +序号高8位 +序号低12位
|
|
// v2 seed productid 0xF(标记) 序号[31:20] 序号[19:4] 序号[3:0] 随机
|
|
//
|
|
// 第 9 个 hex 字符是版本判别位:v1 存 probatch>>4,历史 probatch 恒小于
|
|
// V1ProbatchLimit,故不可能等于 v2 标记。productid 位于第 5-8 位,两版一致,
|
|
// 因此提取 productid 无需先判版本(见 ProductID)。
|
|
package license
|
|
|
|
import (
|
|
cryptorand "crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"strconv"
|
|
)
|
|
|
|
const (
|
|
totalSegments = 5 // 5段,每段4字符 = 20字符 = 10字节
|
|
segmentLength = 4
|
|
|
|
// v2Tag v2 格式标记,占第 9 个 hex 字符
|
|
v2Tag = 0xF
|
|
|
|
// V1ProbatchLimit v1 批次号上界(不含)。历史 probatch 必须全部小于该值,
|
|
// 否则越界的 v1 码会在判别位上撞上 v2 标记而被误判为 v2。
|
|
// 迁移脚本必须先校验 MAX(probatch) < V1ProbatchLimit 再继续。
|
|
V1ProbatchLimit = 0xF0
|
|
)
|
|
|
|
// Version 授权码格式版本
|
|
type Version uint8
|
|
|
|
const (
|
|
V1 Version = 1 // 批次(8位) + 批内序号(20位)
|
|
V2 Version = 2 // 产品级全局序号(32位)
|
|
)
|
|
|
|
// Info 授权码解析结果
|
|
type Info struct {
|
|
ProductID uint16 // 产品ID
|
|
Version Version // 格式版本
|
|
Serial uint32 // v2:产品级全局序号(32位) v1:批内序号(20位)
|
|
Batch byte // 批次号,仅 v1 有效
|
|
}
|
|
|
|
// decode 拆出 5 个数据段,并用种子重建混淆序列。v1/v2 共用,不涉及版本判别。
|
|
func decode(code string) (segments, confusion []int, err error) {
|
|
expectedLength := totalSegments * segmentLength
|
|
if len(code) != expectedLength {
|
|
return nil, nil, fmt.Errorf("授权码长度错误,期望%d字符,实际%d字符", expectedLength, len(code))
|
|
}
|
|
segments = make([]int, totalSegments)
|
|
for i := 0; i < totalSegments; i++ {
|
|
value, perr := strconv.ParseUint(code[i*segmentLength:(i+1)*segmentLength], 16, 32)
|
|
if perr != nil {
|
|
return nil, nil, fmt.Errorf("第%d段包含无效字符", i+1)
|
|
}
|
|
segments[i] = int(value)
|
|
}
|
|
rng := rand.New(rand.NewSource(int64(segments[0])))
|
|
confusion = make([]int, totalSegments-1)
|
|
for i := range confusion {
|
|
confusion[i] = rng.Intn(65536)
|
|
}
|
|
return segments, confusion, nil
|
|
}
|
|
|
|
// GenerateV2 生成 v2 授权码。
|
|
// serial 为产品级全局序号,占满 uint32,按产品累加,不随批次重置。
|
|
func GenerateV2(productid uint16, serial uint32) (string, error) {
|
|
seedBytes := make([]byte, 2)
|
|
if _, err := cryptorand.Read(seedBytes); err != nil {
|
|
return "", fmt.Errorf("生成随机种子失败: %v", err)
|
|
}
|
|
seed := int(seedBytes[0])<<8 | int(seedBytes[1])
|
|
rng := rand.New(rand.NewSource(int64(seed)))
|
|
|
|
confusion := make([]int, totalSegments-1)
|
|
for i := range confusion {
|
|
confusion[i] = rng.Intn(65536)
|
|
}
|
|
|
|
confusion[0] ^= int(productid)
|
|
confusion[1] ^= v2Tag<<12 | int(serial>>20) // 标记 + 序号[31:20]
|
|
confusion[2] ^= int(serial>>4) & 0xFFFF // 序号[19:4]
|
|
confusion[3] ^= int(serial&0xF)<<12 | rng.Intn(1<<12) // 序号[3:0] + 12位随机填充
|
|
|
|
code := fmt.Sprintf("%04X", seed)
|
|
for _, c := range confusion {
|
|
code += fmt.Sprintf("%04X", c)
|
|
}
|
|
return code, nil
|
|
}
|
|
|
|
// Parse 解析授权码,自动识别 v1/v2 格式
|
|
func Parse(code string) (*Info, error) {
|
|
segments, confusion, err := decode(code)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
info := &Info{ProductID: uint16((segments[1] ^ confusion[0]) & 0xFFFF)}
|
|
data2 := segments[2] ^ confusion[1]
|
|
data3 := segments[3] ^ confusion[2]
|
|
|
|
if (data2>>12)&0xF == v2Tag {
|
|
data4 := segments[4] ^ confusion[3]
|
|
info.Version = V2
|
|
info.Serial = uint32(data2&0xFFF)<<20 | uint32(data3&0xFFFF)<<4 | uint32((data4>>12)&0xF)
|
|
return info, nil
|
|
}
|
|
|
|
info.Version = V1
|
|
info.Batch = byte((data2 >> 8) & 0xFF)
|
|
info.Serial = uint32(data2&0xFF)<<12 | uint32(data3&0xFFF)
|
|
if info.Batch < 1 {
|
|
return nil, errors.New("批次数据异常")
|
|
}
|
|
return info, nil
|
|
}
|
|
|
|
// ProductID 提取授权码中的产品ID。
|
|
// productid 在 v1/v2 中位置相同,故不做版本判别、也不校验批次,
|
|
// 新旧码混存期间恒可用(调用方通常只需要它来定位 license 分表)。
|
|
func ProductID(code string) (uint16, error) {
|
|
segments, confusion, err := decode(code)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return uint16((segments[1] ^ confusion[0]) & 0xFFFF), nil
|
|
}
|
|
|