cryptox

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 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