Go Library · 零外部依赖

一个内部 ID,
派生出互不相通
外部密钥。

CKD 把 channel_12345 这样的内部资源 ID,派生成 webhook、sse、api 等场景各自独立的外部 Key——不查库、不维护映射表,仅凭一把主密钥即可派生与还原。

$ go get github.com/doc-war/ckd
查看派生协议 →
不可推导 derive parse derive parse C channel_id A webhook key B sse key
derive:C → 外部 Key parse:外部 Key → C
§00概述

用计算代替查表

CKD(Channel Key Derivation)是一种轻量级、无状态、可逆的资源标识派生协议。它把"哪个外部 Key 对应哪个内部 ID"这件事,从一张需要维护的映射表,变成一次可重复的计算。

核心目标

  • 一个内部资源ID,面向多用途派生隔离的外部 Key
  • 无数据库映射,Key本身即可逆向恢复出原始ID
  • 不同用途的Key之间不可互相推导
  • 已知任意数量的外部Key,也无法反推主密钥

数学表现

C → A C → B A → C B → C A 推不出 B B 推不出 A 无需存储 A→C / B→C 映射

查库 vs CKD

把"状态驱动路由"换成"计算驱动路由",减少两次查库,条形长度按数量级示意。

查库CKD
单次路由延迟
1 ~ 10 ms
0.01 ~ 0.1 ms
单机 QPS
1万 ~ 5万
10万 ~ 50万
横向扩容
受 DB 限制
近线性
Redis / MySQL
需要
不需要
§01快速开始

安装与使用

零外部依赖,仅使用 Go 标准库。

bash
go get github.com/doc-war/ckd
main.go
package main

import (
    "fmt"
    "github.com/doc-war/ckd"
)

func main() {
    // 主密钥:32 字节高熵随机字节串(CSPRNG 生成)
    secret := []byte{
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
        0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
        0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
    }

    c, err := ckd.New(ckd.Config{
        CurrentVersion: 1,
        SecretsByVersion: map[uint8][]byte{
            1: secret,
        },
    })
    if err != nil {
        panic(err)
    }

    // 派生:内部 ID → 外部 Key
    channelID := []byte("channel_12345")
    webhookKey, _ := c.Derive(channelID, "webhook")
    sseKey, _ := c.Derive(channelID, "sse")

    fmt.Println(webhookKey) // AXnjdNQal5XDP66STw0pQbOzRi3AdGmiX1Xi5ZxO

    // 恢复:外部 Key → 内部 ID
    recovered, _ := c.Parse(webhookKey, "webhook")
    fmt.Println(string(recovered)) // channel_12345
}

核心概念

概念说明
资源 ID(C)内部资源标识,如 channel_iduser_id
用途(P)用途标识,如 webhooksseapi,仅允许 [a-z0-9_-],长度 1~64
派生密钥(K)外部标识,base64url 编码,含版本号和认证标签
主密钥(S)平台主密钥,推荐 256-bit CSPRNG 字节串
§02协议流程

派生与恢复

整个协议只有三步运算,不依赖任何外部状态。

派生
K = base64url(version_byte || AES-SIV-Encrypt(PurposeKey, C, AAD=P))
恢复
C = AES-SIV-Decrypt(PurposeKey, base64url-decode(K), AAD=P)
密钥
PurposeKey = HKDF(S, salt="", info=P)
C内部资源 ID
P用途标识(AAD)
K对外暴露的派生密钥
S平台主密钥
PurposeKey按用途派生出的子密钥

算法

组件算法标准
密钥派生HKDF (SHA-256)RFC 5869
确定性 AEADAES-256-SIVRFC 5297
编码base64url(无 padding)RFC 4648 §5

测试向量

3 组固定向量用于跨语言验证,见 internal/core_test.go 中的 TestKnownVectors

#SecretPurposeVerResourceIDK
10001...1e1fwebhook1channel_12345AXnjdNQal5XDP66STw0pQbOzRi3AdGmiX1Xi5ZxO
20001...1e1fsse1user_67890Aez5p0zdhSjxmsgy92MfMhiMlhAbRxlldWcl
3ffee...1100api_token2channel_12345ApUFLCCTYol1B57b459DrXW8SiQaJLaJRUx246YY
§03API

接口参考

ckd.go
func New(cfg Config) (CKD, error)

type CKD interface {
    Derive(resourceID []byte, purpose string) (string, error)
    Parse(derivedKey string, purpose string) ([]byte, error)
}
Config 字段类型说明
CurrentVersionuint8当前版本号 [1, 15]
SecretsByVersionmap[uint8][]byte各版本密钥,需包含 CurrentVersion

错误处理

errors
if errors.Is(err, ckd.ErrInvalidPurpose) {
    // 用途标识格式不合法
}
if errors.Is(err, ckd.ErrInvalidDerivedKey) {
    // 派生密钥解析失败(格式错误、认证失败、版本不存在等)
}

Parse 对认证失败、格式错误、版本不存在等情况统一返回 ErrInvalidDerivedKey,不泄露失败原因。

§04运维

密钥轮换与用途隔离

密钥轮换

main.go
c, _ := ckd.New(ckd.Config{
    CurrentVersion: 2,
    SecretsByVersion: map[uint8][]byte{
        1: oldSecret, // 解析旧 Key
        2: newSecret, // 派生新 Key
    },
})

newKey, _ := c.Derive(channelID, "webhook")
oldID, _ := c.Parse(oldKey, "webhook")

用途隔离

main.go
k1, _ := c.Derive(id, "webhook")
k2, _ := c.Derive(id, "sse")

// k1 ≠ k2
// Parse(k1, "sse")     → 错误
// Parse(k2, "webhook")  → 错误
§05安全

安全特性

已知限制