IoT
CBOR
COSE
CWT
WebAuthn

[CWT入門その1] CBORによるオブジェクトのバイナリ表現


RFC7049 CBORによるオブジェクトのバイナリ表現


概要

この投稿はCBOR Web Token(CWT)入門3部作の第1弾です。

CWTとは


  • みんな大好き JWT(JSON Web Token) のバイナリ版

  • JSON + Base64な日常にピリリと辛い刺激を与える、IoT時代注目の一品

  • 今話題のWebAuthn(WebAuthentication API)でも使われている(追記)

という感じです。

詳細な説明は後ほどの投稿で行うとして、ここではJWTにおけるJSONに相当する、CBORというオブジェクト表現についてざっくりと紹介します。


CBORとは


The Concise Binary Object Representation (CBOR) is a data format

whose design goals include the possibility of extremely small code

size, fairly small message size, and extensibility without the need

for version negotiation.


メッセージサイズよりも、コードを小さくしたい気持ちが伝わってきます。


CBORが目指すところ


  • インターネット標準で利用される一般的なデータフォーマットをエンコード可能

  • 電力、CPUパワー、命令セットが限られたシステムのために、エンコーダー/デコーダーをコンパクトに実装可能

  • エンコードされたデータは自己記述的であり、デコードのためのスキーマ記述は不要

  • (サイズ、実装の複雑性がJSONよりも)合理的なコンパクトシリアライゼーション

  • 制限があり、大量のデータ処理を扱う端末のための効率的なCPU利用率

  • JSON互換

  • 拡張性


CBORで表現可能なデータ


  • 整数

  • バイナリ

  • テキスト


  • Array, Map

  • 浮動小数点数、多倍長整数


  • true / false / null / undefined

  • 日付、日時

などなど、一通りのデータフォーマットがサポートされています。


エンコード仕様

全てのデータフォーマットについて細かいエンコードの方法を記載していくのは大変なので、少しだけ抜粋して紹介します。気になった方は仕様をご覧ください。

また、CBOR playground にて、エンコードしたいオブジェクトとバイナリの16進表記の変換ができるので大変便利です。


整数

エンコード結果は、整数であることとその値から構成されます。

# 0

00 # unsigned(0)

# 23
17 # unsigned(23)

# 24
18 18 # unsigned(24)

# 255
18 FF # unsigned(255)

1バイトの符号なし整数のうち、0 から 23 までは、 0 + 整数値として表現します。

24 から 255 までの値は、先頭が 24(18)、 その次に整数値が続く2バイトで表現します。

2, 4, 8バイトの符号なし整数については、それぞれ先頭が 25(19), 26(1A), 27(1B) となります。

# 256

19 0100 # unsigned(256)

# 65535
19 FFFF # unsigned(65535)

# 65536
1A 00010000 # unsigned(65536)

# 4294967295
1A FFFFFFFF # unsigned(4294967295)

# 4294967296
1B 0000000100000000 # unsigned(4294967296)

# 18446744073709551615
1B FFFFFFFFFFFFFFFF # unsigned(18446744073709551615)

整数の表現は、バイナリやテキストをエンコードする際にもサイズの表現に使われます。

負の整数については、符号なし整数の表現の1バイトめに 20(32) が加算され、その後に(符号なし整数の値 - 1)の値を続けて表現します。

# -1

20 # negative(0)

# -24
37 # negative(23)

# -25
38 18 # negative(24)

...

# -18446744073709551616
3B FFFFFFFFFFFFFFFF # negative(18446744073709551615)


バイナリ/テキスト

バイナリとテキストの表現は、(種類 + サイズ + バイナリ)で表現されます。

種類 + サイズの部分は、符号なし整数のエンコードと同様の形式です。

# h''

40 # bytes(0)
# ""

# ""
60 # text(0)
# ""

# h'6162636465'
45 # bytes(5)
6162636465 # "abcde"

# "abcde"
65 # text(5)
6162636465 # "abcde"

# h'6162636465666768696A6B6C6D6E6F707172737475767778'
58 18 # bytes(24)
6162636465666768696A6B6C6D6E6F707172737475767778 # "abcdefghijklmnopqrstuvwx"

# "abcdefghijklmnopqrstuvwx"
78 18 # text(24)
6162636465666768696A6B6C6D6E6F707172737475767778 # "abcdefghijklmnopqrstuvwx"


Array , Map

それぞれ、Arrayであることと要素数、Map であることと key の数に続いて、エンコードされた要素が並びます。

# [1, "a"]

82 # array(2)
01 # unsigned(1)
61 # text(1)
61 # "a"

# ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x"]
98 18 # array(24)
61 # text(1)
61 # "a"
...
61 # text(1)
78 # "x"

# {1:"a"}
A1 # map(1)
01 # unsigned(1)
61 # text(1)
61 # "a"

(Mapの要素が多いやつは省略)


Indefinite-Length

Indefinite-Length つまり、不定長の Array, Map, バイナリ、文字列も表現可能です。

Array では 9FFF , Map では BFFF で表現されます。

# [_ 1, "a"]

9F # array(*)
01 # unsigned(1)
61 # text(1)
61 # "a"
FF # primitive(*)

# {_ 1:"a"}
BF # map(*)
01 # unsigned(1)
61 # text(1)
61 # "a"
FF # primitive(*)


その他

JSONで表現できる、false / true / null も表現可能です。

# false

F4 # primitive(20)

# true
F5 # primitive(21)

# null
F6 # primitive(22)


その他、仕様に書いてあること


  • CBORを用いたプロトコルの作り方(扱い方?)

  • CBOR / JSON の相互変換

  • 拡張について

  • 既存のバイナリ表現との違い

と言ったあたりが定義されていますが、今回はどんな感じでエンコードできるの?ってのがわかれば良さそうという認識で、省略します。

MessagePack あたりとの比較とかも面白そうなので後でやるかもしれません。


JSONとのサイズ比較

一通りのオブジェクトに対して、CBORでJSONと同レベルの表現ができそう、ということで、ちょっと気になるサイズ比較をしてみます。

Value
CBOR
JSON

0
1
1

23
1
2

18446744073709551615
9
20

18446744073709551616
11
20

"12345678901234567890123"
24
25

"123456789012345678901234"
26
26

[]
1
2

[1, 2, 3, 4]
5
9

[“a”, “b”, “c”, “d”]
9
17

{}
1
2

{1:2, 3:4}
5
13

{“a”:”b”, “c”:”d”}
9
17

h’01020304’
5
10

32 bytes binary
34
46

やはり、整数はコンパクトになってる感じがしますね。

Map もCBORだとkey/valを並べるだけなので、Array と同様のサイズになります。

となると、例えばkey/value形式のクレームを扱いたいような場合、あらかじめ想定されている文字列などは整数値(できれば0~23)に置き換えつつ、 Array なり Map で構造化されたデータを作ってやるとデータ量を抑えることができそうです。


まとめ&次回予告


  • CBORで一般的なデータフォーマットの表現方式(エンコード方法)について紹介した

  • JSONとのサイズ比較をしてみた

次回は、このCBORを用いた署名付き/暗号化された構造体の実現方法、つまりJWTで言う所のJOSEに相当する、"CBOR Object Signing and Encryption(COSE)" を紹介する予定です。


リンク