Help us shape the future of Go! Take the 2026 Go Developer Survey

cryptox

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package cryptox 提供独立的 HMAC-SHA256、AES-GCM 和密码学安全随机数,只使用标准库。 HMAC 适用于共享密钥的内部接口参数签名,AES-GCM 适用于分享 token 的保密与防篡改。 实例由 NewHMACSHA256 / NewAESGCM 构造并可并发复用;零值不可用。

以下分享示例需导入 github.com/bpcoder16/pixiu/cryptox。 临时生成密钥仅用于展示;生产环境应读取安全持久化的密钥,各用途独立用钥。

key, err := cryptox.RandomBytes(32)
if err != nil {
	return err
}
box, err := cryptox.NewAESGCM(key)
if err != nil {
	return err
}
token, err := box.EncryptToken([]byte(`{"resource":"file-42","exp":1800000300}`), "share-download")
if err != nil {
	return err
}
payload, err := box.DecryptToken(token, "share-download")
if err != nil {
	return err
}
// 仅在认证成功后解析 payload,并校验 exp、资源权限和撤销状态。
_ = payload

HMAC 使用独立的至少 32 字节随机密钥,以下代码还需导入 errors:

signer, err := cryptox.NewHMACSHA256(signingKey)
if err != nil {
	return err
}
signature, err := signer.Sign(message)
if err != nil {
	return err
}
if !signer.Verify(message, signature) {
	return errors.New("invalid request signature")
}

signingKey 和 message 由应用提供;message 必须包含所有影响业务操作的字段。 signature 是完整 32 字节认证码,不可截短。 message 应按固定编码绑定用途、方法、目标 host、路径、查询、body 摘要、时间戳及 nonce。 包的外部示例展示长度前缀编码和拒绝重复 query key,应用还须验证时间窗口并原子去重。 HMAC 验签方也能签名,不能把共享密钥交给不可信客户端;签名不隐藏参数。

AES 字节接口输出 nonce(12) || ciphertext || tag(16),支持空明文与可选 AAD。 token 接口输出 v1. 加规范 Base64URL,purpose 必填,编码长度最多 MaxTokenLen 字节; 格式版本和 purpose 被认证,同一密钥跨所有实例最多加密 2^32 次,须提前轮换。 加密失败返回空结果,解密失败返回 nil 明文;本包不记录密钥、明文或 token。

RandomToken(32) 可生成不携带明文的分享凭证;有效期、撤销和一次性消费由服务端记录实现。 本包不替代 HTTPS、业务授权、重放防护或密码存储方案,也不提供 JWT/JWE。

Index

Examples

Constants

View Source
const MaxRandomSize = 1 << 20

MaxRandomSize 是单次 RandomBytes 的最大字节数量,限制无意的大内存分配。

View Source
const MaxTokenLen = 8192

MaxTokenLen 是 token 便捷接口允许的最大编码字节长度,不包含 URL 参数名。

Variables

View Source
var (
	// ErrUninitialized 表示实例未经构造函数初始化。
	ErrUninitialized = errors.New("cryptox: uninitialized instance")
	// ErrDecrypt 统一表示密文格式或认证失败,不包含不可信输入。
	ErrDecrypt = errors.New("cryptox: decryption failed")
	// ErrTooLarge 表示输入超过对应接口的长度限制。
	ErrTooLarge = errors.New("cryptox: input too large")
	// ErrInvalidPurpose 表示 token 用途不满足 1–256 字节限制。
	ErrInvalidPurpose = errors.New("cryptox: purpose must contain 1 to 256 bytes")
	// ErrInvalidSize 表示请求的随机字节数量不在允许范围内。
	ErrInvalidSize = errors.New("cryptox: invalid random size")
)

Functions

func RandomBytes

func RandomBytes(size int) ([]byte, error)

RandomBytes 返回 1–MaxRandomSize 个密码学安全随机字节。 AES-256/HMAC 密钥通常选择 32 字节;实际密钥应安全持久化并按用途隔离。

func RandomToken

func RandomToken(size int) (string, error)

RandomToken 将 16–6144 个随机字节编码为无填充 Base64URL;常规推荐 size=32。 它不携带可解密内容;撤销、有效期和一次性消费需配合服务端存储。

Example
package main

import (
	"fmt"

	"github.com/bpcoder16/pixiu/cryptox"
)

func main() {
	token, err := cryptox.RandomToken(32)
	if err != nil {
		panic(err)
	}
	// 服务端保存 token 的摘要及资源、有效期;一次性使用须原子消费该记录。
	fmt.Println(len(token))
}
Output:
43

Types

type AESGCM

type AESGCM struct {
	// contains filtered or unexported fields
}

AESGCM 使用标准库 AES-GCM 和随机 nonce,可并发复用;零值不可用。

func NewAESGCM

func NewAESGCM(key []byte) (*AESGCM, error)

NewAESGCM 接受 16/24/32 字节随机密钥,通常推荐 32 字节。 同一密钥跨全部实例累计加密不得超过 2^32 次;轮换由应用负责。

func (*AESGCM) Decrypt

func (c *AESGCM) Decrypt(ciphertext, aad []byte) ([]byte, error)

Decrypt 验证认证标签后返回独立明文;格式或认证失败返回 ErrDecrypt 和 nil。

func (*AESGCM) DecryptToken

func (c *AESGCM) DecryptToken(token, purpose string) ([]byte, error)

DecryptToken 只接受规范编码和固定 v1 格式;失败不返回明文。 只有返回 nil 错误后才能解析 payload 并检查有效期、资源和权限。

func (*AESGCM) Encrypt

func (c *AESGCM) Encrypt(plaintext, aad []byte) ([]byte, error)

Encrypt 返回 nonce(12) || ciphertext || tag(16),不修改输入。 aad 被认证但不被加密或保存;解密时须提供相同字节。允许空 plaintext 和 aad。

func (*AESGCM) EncryptToken

func (c *AESGCM) EncryptToken(plaintext []byte, purpose string) (string, error)

EncryptToken 生成 v1. 前缀的无填充 Base64URL 密文;purpose 为 1–256 字节的用途。 版本与 purpose 参与认证。结果最多 MaxTokenLen 字节;payload 有效期由应用管理。

Example
package main

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/bpcoder16/pixiu/cryptox"
)

func main() {
	key, err := cryptox.RandomBytes(32)
	if err != nil {
		panic(err)
	}
	box, err := cryptox.NewAESGCM(key)
	if err != nil {
		panic(err)
	}
	type share struct {
		Resource string `json:"resource"`
		Scope    string `json:"scope"`
		Expires  int64  `json:"exp"`
	}
	now := time.Now()
	payload, err := json.Marshal(share{
		Resource: "file-42",
		Scope:    "download",
		Expires:  now.Add(5 * time.Minute).Unix(),
	})
	if err != nil {
		panic(err)
	}
	token, err := box.EncryptToken(payload, "share-download")
	if err != nil {
		panic(err)
	}
	decrypted, err := box.DecryptToken(token, "share-download")
	if err != nil {
		panic(err)
	}
	var claims share
	if err := json.Unmarshal(decrypted, &claims); err != nil {
		panic(err)
	}
	if time.Now().Unix() >= claims.Expires || claims.Resource == "" || claims.Scope != "download" {
		panic("invalid share claims")
	}
	// 应用继续检查资源是否仍可分享及撤销状态,再执行下载。
	fmt.Println(claims.Resource, claims.Scope)
}
Output:
file-42 download

type HMACSHA256

type HMACSHA256 struct {
	// contains filtered or unexported fields
}

HMACSHA256 使用固定密钥签名和验签,可并发复用;零值不可用。

Example
package main

import (
	"crypto/sha256"
	"encoding/binary"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"github.com/bpcoder16/pixiu/cryptox"
)

func main() {
	// 生产密钥由安全配置加载;此处临时生成仅为完整示例。
	key, err := cryptox.RandomBytes(32)
	if err != nil {
		panic(err)
	}
	signer, err := cryptox.NewHMACSHA256(key)
	if err != nil {
		panic(err)
	}
	nonce, err := cryptox.RandomToken(16)
	if err != nil {
		panic(err)
	}
	req, err := http.NewRequest(http.MethodPost, "https://internal.example/jobs?queue=normal", nil)
	if err != nil {
		panic(err)
	}
	body := []byte(`{"job":"42"}`)
	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
	message, err := requestMessage(req, body, timestamp, nonce)
	if err != nil {
		panic(err)
	}
	signature, err := signer.Sign(message)
	if err != nil {
		panic(err)
	}
	// 接收端从请求重建相同 message,再验证签名;成功后还须校验时间窗口并原子登记 nonce。
	fmt.Println(signer.Verify(message, signature))
}

// requestMessage 是应用协议示例,不是 cryptox 的 HTTP 公共 API。
// 签名放在独立 header 中;body 为读取了大小受限的请求体后取得的原始字节。
func requestMessage(req *http.Request, body []byte, timestamp, nonce string) ([]byte, error) {
	query, err := url.ParseQuery(req.URL.RawQuery)
	if err != nil {
		return nil, err
	}
	for _, values := range query {
		if len(values) != 1 {
			return nil, fmt.Errorf("duplicate query parameter")
		}
	}
	digest := sha256.Sum256(body)
	fields := []string{
		"cryptox:request:v1",
		req.Method,
		req.Host,
		req.URL.EscapedPath(),
		query.Encode(),
		string(digest[:]),
		timestamp,
		nonce,
	}
	var message []byte
	for _, field := range fields {
		message = binary.AppendUvarint(message, uint64(len(field)))
		message = append(message, field...)
	}
	return message, nil
}
Output:
true

func NewHMACSHA256

func NewHMACSHA256(key []byte) (*HMACSHA256, error)

NewHMACSHA256 复制至少 32 字节的密钥,密钥应由 crypto/rand 生成。 持有相同密钥的验签方也具有签名能力。

func (*HMACSHA256) Sign

func (s *HMACSHA256) Sign(message []byte) ([]byte, error)

Sign 返回独立持有的完整 32 字节认证码,不改变 message。

func (*HMACSHA256) Verify

func (s *HMACSHA256) Verify(message, signature []byte) bool

Verify 拒绝截短的认证码,使用恒定时间比较;未初始化的实例返回 false。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL