Golang CBOR库推荐 - 轻量、高效、稳定,恶意数据零崩溃,支持encoding/json API及toarray/keyasint结构体标签 (fxamacker/cbor v1.3)

Golang CBOR库推荐 - 轻量、高效、稳定,恶意数据零崩溃,支持encoding/json API及toarray/keyasint结构体标签 (fxamacker/cbor v1.3) 这个库是做什么的? fxamacker/cborCBOR 进行编码和解码,就像 encoding/json 对 JSON 所做的那样。

什么是 CBOR? CBOR (RFC 7049) 是一种受 JSON 和 MessagePack 启发的二进制数据格式。CBOR 被用于 IETF 互联网标准中,例如 COSE (RFC 8152) 和 CWT (RFC 8392 CBOR Web Token)。WebAuthn 也使用 CBOR。

既然已有其他库,为何还要创建这个? 我需要在 Go 中使用 CBOR,但希望库体量小,并且不必担心微小的恶意 CBOR 消息可能导致整个系统崩溃。

:point_right: 我发现一个 GitHub 项目用这个库替换了一个拥有 1000+ 星标的库,因为外部安全审计发现,微小的恶意 CBOR 消息能够耗尽另一个库的系统资源。

为什么项目应该选择这个 CBOR 库? 它不会崩溃,并且具备均衡的特性:小巧、快速、可靠且易用。

  • :atom_symbol: 小巧且自包含。它编译后小于 0.5 MB,没有外部依赖,也不需要代码生成。与其他库相比,编译后程序大小的差异可能高达 8+ MB(见图表)。
  • :rocket: 快速(特别是自 v1.3 起)。它仅使用安全的优化。总会有更快的库存在,但速度只是其中一个因素。如果你重视自己的时间、程序大小和系统可靠性,请选择这个库。
  • :lock: 可靠且安全。它通过广泛的测试、覆盖率引导的模糊测试、数据验证以及避免使用 Go 的 unsafe 包,来防止恶意 CBOR 数据导致的崩溃。
  • :hourglass_flowing_sand: 易用且节省时间。它拥有与 Goencoding/json 相同的 API。现有的结构体无需更改。Go 结构体标签如 `cbor:"name,omitempty"``json:"name,omitempty"` 会按预期工作。额外的结构体标签如 keyasinttoarray 使得 CBOR、COSE、CWT 和 SenML 非常易于使用。

使用 go get github.com/fxamacker/cbor 安装,并像使用 Go 的 encoding/json 一样使用它。

示例:CBOR Web Token (CWT)

keyasinttoarray 结构体标签简化了将已签名的 CWT 解码为易于使用的 Go 结构体的过程。这些标签使得解码变得简单:err := cbor.Unmarshal(b, &v)

cbor_cwt_snippet

对比

CBOR library and program size comparison chart

建议进行你自己的对比。使用你最常用的消息大小和数据类型。

可能会不时添加额外的对比(特别是速度对比!)。

当前状态

版本 1.x 具有:

  • 稳定的 API – 不会进行破坏性的 API 更改。
  • 稳定的要求 – 将始终支持 Go v1.12。
  • 通过模糊测试 – v1.3 在超过 72 小时的覆盖率引导模糊测试中通过了超过 20 亿次执行。

近期动态

  • 发布 v1.2 – 添加了 RawMessage 类型、Marshaler 和 Unmarshaler 接口。
  • 发布 v1.3 – 更快的编码和解码。
  • 发布 v1.3 – 添加了结构体与 CBOR 数组的相互转换(toarray 结构体标签),以获得更紧凑的数据。
  • 发布 v1.3 – 添加了结构体与使用整数键的 CBOR 映射的相互转换(keyasint 结构体标签)。简化了 CBOR 的使用,特别是 COSE、CWT、SenML 等。
  • 里程碑 v1.4 – :balloon: (可能)添加对 CBOR 标签(主类型 6)的支持。如果这个功能对你很重要,请告诉我!

更多关于Golang CBOR库推荐 - 轻量、高效、稳定,恶意数据零崩溃,支持encoding/json API及toarray/keyasint结构体标签 (fxamacker/cbor v1.3)的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

我曾使用过这个,很高兴能看到一个经过充分测试的高质量实现! :+1:

更多关于Golang CBOR库推荐 - 轻量、高效、稳定,恶意数据零崩溃,支持encoding/json API及toarray/keyasint结构体标签 (fxamacker/cbor v1.3)的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Release v1.3 已经完成!它在72小时的覆盖引导模糊测试中通过了超过20亿次执行。

变更内容包括:

  • 更快的速度(许多安全优化)
  • toarray 结构体标签
  • keyasint 结构体标签
  • 重构以使用命名和分组内部函数的最佳实践

使用CBOR Web Token (CWT)的示例代码展示了这些标签如何使解码Signed CWT到易于使用的Go结构体变得简单:err := cbor.Unmarshal(b, &v)

您友善的话语促使我将此内容纳入项目的贡献指南smiley

当我在 Go 论坛上宣布 v1.2 版本时,Jakob Borg (calmh) 回应了一个大拇指和鼓励。当时有另一个同等优先级的项目需要我的时间,而 Jakob 的善意话语促使我决定着手处理这个项目(为里程碑 v1.3 进行性能优化)。因此,表达赞赏或鼓励是参与开源项目的一种很好的方式。

fxamacker/cbor 是一个优秀的CBOR编解码库,完全符合你的需求。它提供了与标准库encoding/json相同的API设计,同时具备轻量、高效、安全的特点。以下是一些关键特性及示例代码:

基本使用示例

package main

import (
    "fmt"
    "github.com/fxamacker/cbor"
)

type Person struct {
    Name string `cbor:"name"`
    Age  int    `cbor:"age"`
    City string `cbor:"city,omitempty"`
}

func main() {
    // 编码
    p := Person{Name: "Alice", Age: 30, City: "New York"}
    data, err := cbor.Marshal(p)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Encoded: %x\n", data)

    // 解码
    var p2 Person
    err = cbor.Unmarshal(data, &p2)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Decoded: %+v\n", p2)
}

使用keyasint标签处理COSE/CWT数据

package main

import (
    "fmt"
    "github.com/fxamacker/cbor"
)

// 使用keyasint标签处理整数键的CBOR映射
type CWTClaims struct {
    Issuer    string `cbor:"1,keyasint"`
    Subject   string `cbor:"2,keyasint"`
    Expiry    int64  `cbor:"4,keyasint"`
    IssuedAt  int64  `cbor:"6,keyasint"`
    Audience  string `cbor:"3,keyasint,omitempty"`
}

func main() {
    // 模拟CWT数据
    cwtData := []byte{
        0xa4, // map(4)
        0x01, 0x64, 0x74, 0x65, 0x73, 0x74, // 1: "test"
        0x02, 0x66, 0x61, 0x6c, 0x69, 0x63, 0x65, // 2: "alice"
        0x04, 0x1a, 0x5f, 0x2e, 0x8f, 0x00, // 4: 1597318800
        0x06, 0x1a, 0x5f, 0x2e, 0x8e, 0xc0, // 6: 1597310400
    }

    var claims CWTClaims
    err := cbor.Unmarshal(cwtData, &claims)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Issuer: %s\n", claims.Issuer)
    fmt.Printf("Subject: %s\n", claims.Subject)
    fmt.Printf("Expiry: %d\n", claims.Expiry)
}

使用toarray标签处理紧凑数组格式

package main

import (
    "fmt"
    "github.com/fxamacker/cbor"
)

// 使用toarray标签将结构体编码为数组
type Point3D struct {
    X float64 `cbor:"0,keyasint"`
    Y float64 `cbor:"1,keyasint"`
    Z float64 `cbor:"2,keyasint"`
}

type CompactData struct {
    Name   string   `cbor:"0,keyasint"`
    Points []Point3D `cbor:"1,keyasint,toarray"`
}

func main() {
    data := CompactData{
        Name: "trajectory",
        Points: []Point3D{
            {X: 1.0, Y: 2.0, Z: 3.0},
            {X: 4.0, Y: 5.0, Z: 6.0},
        },
    }

    encoded, err := cbor.Marshal(data)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Encoded size: %d bytes\n", len(encoded))
    
    var decoded CompactData
    err = cbor.Unmarshal(encoded, &decoded)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Decoded: %+v\n", decoded)
}

处理恶意数据的安全示例

package main

import (
    "fmt"
    "github.com/fxamacker/cbor"
)

func main() {
    // 恶意CBOR数据示例(深度嵌套的数组)
    maliciousData := []byte{
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
        0x81, // array(1)
    }

    var result interface{}
    err := cbor.Unmarshal(maliciousData, &result)
    if err != nil {
        // fxamacker/cbor会安全地拒绝深度嵌套的恶意数据
        fmt.Printf("安全拒绝恶意数据: %v\n", err)
    } else {
        fmt.Printf("解码成功: %v\n", result)
    }
}

性能优化示例

package main

import (
    "fmt"
    "time"
    "github.com/fxamacker/cbor"
)

type SensorData struct {
    Timestamp int64     `cbor:"t"`
    Values    []float64 `cbor:"v"`
    DeviceID  string    `cbor:"d"`
}

func main() {
    // 准备测试数据
    data := SensorData{
        Timestamp: time.Now().Unix(),
        Values:    make([]float64, 1000),
        DeviceID:  "sensor-001",
    }
    
    for i := range data.Values {
        data.Values[i] = float64(i) * 0.5
    }

    // 编码性能测试
    start := time.Now()
    encoded, err := cbor.Marshal(data)
    if err != nil {
        panic(err)
    }
    encodeTime := time.Since(start)
    
    // 解码性能测试
    start = time.Now()
    var decoded SensorData
    err = cbor.Unmarshal(encoded, &decoded)
    if err != nil {
        panic(err)
    }
    decodeTime := time.Since(start)
    
    fmt.Printf("数据大小: %d bytes\n", len(encoded))
    fmt.Printf("编码时间: %v\n", encodeTime)
    fmt.Printf("解码时间: %v\n", decodeTime)
    fmt.Printf("解码验证: DeviceID=%s, Values count=%d\n", 
        decoded.DeviceID, len(decoded.Values))
}

这个库确实如描述所示,在保持与encoding/json API兼容的同时,提供了CBOR特有的功能标签(keyasint、toarray),并且在安全性和性能方面都有良好表现。编译后的二进制体积小,没有外部依赖,适合对资源敏感和对安全性要求高的场景。

回到顶部