在密码学中,诸如证书,公钥等基本上都是使用 ASN.1 DER 进行编码。
前言
本文翻译自 wikipedia - X.690。
X.690 是 ITU-T 标准,它制定了以下几种 ASN.1 编码的格式:
- BER:基本编码格式
- CER:规范编码格式
- DER:可分辨编码格式
BER 是 ASN.1 的原始编码格式,用于将给定的抽象的信息数据编码为具体的八位字节序列数据流。BER 的语法定义了基本数据类型的表示方法,长度信息的结构和基于基本数据类型的复杂数据或复合数据的表示方法。 ITU-T 定义的 X.609 标准文档作为 ASN.1 系列文档的一部分,定义了两个 BER 的子集 CER 和 DER 。
正文
BER/CER/DER编码
BER 定义了用于编码 ASN.1 数据流的自描述和自界定格式。每一个数据元素都由类型标识符,长度描述,实际数据元素和内容结束标记(必要的时候才会使用该标记)编码,这种编码类型又被叫做 TLV 编码,即类型长度值编码。该格式允许解码器从不完整的流中解码出 ASN.1 信息,而不需要预先知道数据元素的大小,内容或具体的含义。
CER 和 DER 均是 BER 的限制变体,它们消除了许多的可选项,而只能遵守标准的编码规范。其中在公钥基础设施中用的最多的是 DER 编码,它主要在 BER 上做了如下限制:
- 长度编码必须使用明确的形式和尽可能短的编码
- Bitstring,octetstring 和受限制的字符串必须使用基本数据类型编码
- 集合的元素根据其标记值按排序顺序编码
编码结构
通常 BER 编码的数据由下面四个部分组成:
类型标识字节序列 | 长度描述字节序列 | 内容字节序列 | 内容结束标记字节序列 |
---|---|---|---|
Type | Length | Value | EOC |
EOC 域与只在编码不定长数据的时候才会使用,用于标记数据内容的结束。Value 域在 NULL 类型的时候也可以省略。
Type 域
Type 域用于指定数据选择的编码类型,在 ASN.1 编码中数据元素一般会选择一个唯一的 Type 进行标记,以便于进行数据间的区分。数据所选择的类型可以是 ASN.1 的基本数据类型,也可以是复合类型。在 ASN.1 中定义了以下原生类型:
数据类型 | 编码类型 | 十进制标记值 | 16 进制标记值 |
---|---|---|---|
End-of-Content (EOC) | Primitive | 0 | 0 |
BOOLEAN | Primitive | 1 | 1 |
INTEGER | Primitive | 2 | 2 |
BIT STRING | Both | 3 | 3 |
OCTET STRING | Both | 4 | 4 |
NULL | Primitive | 5 | 5 |
OBJECT IDENTIFIER | Primitive | 6 | 6 |
Object Descriptor | Both | 7 | 7 |
EXTERNAL | Constructed | 8 | 8 |
REAL (float) | Primitive | 9 | 9 |
ENUMERATED | Primitive | 10 | A |
EMBEDDED PDV | Constructed | 11 | B |
UTF8String | Both | 12 | C |
RELATIVE-OID | Primitive | 13 | D |
Reserved | 14 | E | |
Reserved | 15 | F | |
SEQUENCE and SEQUENCE OF | Constructed | 16 | 10 |
SET and SET OF | Constructed | 17 | 11 |
NumericString | Both | 18 | 12 |
PrintableString | Both | 19 | 13 |
T61String | Both | 20 | 14 |
VideotexString | Both | 21 | 15 |
IA5String | Both | 22 | 16 |
UTCTime | Both | 23 | 17 |
GeneralizedTime | Both | 24 | 18 |
GraphicString | Both | 25 | 19 |
VisibleString | Both | 26 | 1A |
GeneralString | Both | 27 | 1B |
UniversalString | Both | 28 | 1C |
CHARACTER STRING | Both | 29 | 1D |
BMPString | Both | 30 | 1E |
编码方式
ASN.1 将数据类型标记编码成一个或多个字节,编码的内容包括标记的种类 Tag class ,标记是基本数据类型还是复合数据类型 P/C,标记的标识值 Tag number。
如上图所述,在数据元素类型编码字节序列的第一个字节 Octet1 的前面两个 2 进制为,用于标识 Tag Class,Tag Class 主要有一下 4 种:
类型 | 数值 | 说明 |
---|---|---|
Universal | 0 | 上面列举的 ASN.1 的原生数据类型 |
Application | 1 | 为特定的应用设定的数据类型 |
Context-specific | 3 | 根据上下文定义的类型 |
Private | 4 | 私人规范中定义的类型 |
第 6 个二进制位用于指定数据类型是基本数据类型(不可拆分)还是复合数据类型(可拆分):
类型 | 数值 | 说明 |
---|---|---|
Primitive (P) | 0 | 数据内容仅由一个数据元素组成 |
Constructed (C) | 1 | 数据内容由多个数据元素组成 |
如果定义的数据类型不是 Universal 的数据类型,那么此时需要用到更多的字节序列如 Octet2。在使用这类标记的时候,要将 Octet1 的第 5 到第 1 个二进制位置为 1 ,如果 Octet2 后面还有 Octet3,那么 Octet2 的第 8 个二进制位应该为 1。
下面举例子说明一下上述编码规则:
-
编码一个 INTEGER 类型的数据
- Tag Class 为 Universal ,对应的值 0
- INTEGER 为 Primitive 类型,对应的值 0
- INTEGER 的 tag number 为 2
- 编码后的二进制位为
0000 0010
,得到的 Type 域字节序列为0x02
-
编码一个 SEQUENCE 类型的数据
- Tag Class 为 Universal ,对应的值 0
- SEQUENCE 为 Constructed 类型,对应的值 1
- SEQUENCE 的 tag number 为 16
- 编码后的二进制位为
0011 0000
,得到的 Type 域字节序列为0x30
-
编码一个自定义的基于上下文的数据类型
- Tag Class 为 Context-specific ,对应的值 3
- 假设该数据类型为 Primitive 类型,对应的值 0
- 假设该数据类型的 tag number 为 300
- 编码后的二进制位为
1101 1111 1001 0010 0000 1100
,得到的 Type 域字节序列为0xDF 0x92 0x0C
Length 域
长度域的编码分为定长的长度域和不定长的长度域。其中定长的长度域分为长度不超过 127 的短格式和长度超过 127 字节的长格式。
定长短格式:
- 第一个二进制位为 0,后面的 7 个二进制位表示长度,范围为 0 - 127
- 如 126 长度的数据,长度域二进制为
0111 1110
字节序列为0x7E
定长长格式:
- 第一个字节的第一个二进制位为 1,后面七个二级制为表示长度值所占用的字节数,后面的字节表示长度值
- 如长度为 300 的字节长度二进制序列为
1000 0010 0000 0001 0010 1100
字节序列为0x82 0x01 0x2C
不定长格式:
- 长度域直接固定为
0x80
- 数据内容由两个
0x00
组成的 EOC 域结束
保留格式:
- 整个字节的二级制位全为 1 即
0xFF
作为保留格式
Value 域
该域是数据元素的字节字节编码。
注意:如果 ASN.1 对象不存在或是虚对象的时候,可能没有该域,如 ASN.1 的 NULL 对象
举个例子
下面是对一个 DER 编码的公钥数据的拆分:
1
2
3
4
5
6
7
8
30 Type = Universal || Constructed || SEQUENCE
82 010A Length = 266
02 Type = Universal || Constructed || INTEGER
82 0101 Length = 257
00C732BB09542D019C492D616D1F5162A5BA634BF8F476E34EDA0433C1EE9E2CD0C7C93E41327F20771CF5CD037CD1EA77FC8378E5D913336797D24FC0E1C237E131FC4047C9208D6D34132C3888AE43BED4DEBF7A49A7C59D50EF998D4C443AE78A7C077DD94ADF2D2DAF785B75D129585AAEDF59A8C9774A26072492928004238D112F0BF4D777884D1DE38175D81213B5B4C9E69B39BF1BB0B47727665E30DE90884CC130C63305A91A550346FF5AE1F4F43487435A682E3A29E271168C868B6EFE84BA59D38E750A955FD585BD3D6B5D5765043E8EC95D2DCBEA9E8C086BA47AE37F20B1E2D21E01B145E00F2A6BE70F6F7E4501C06682BBFCFFB9261F4167
02 Type = Universal || Constructed || INTEGER
03 Length = 3
010001
注意事项
- BIT STRING 类型的 Value 第一位是填充长度位,用于指明该 BIT STRING 填充了多少个二进制位。因为 BIT STRING 是以二进制位为单位的字符串,但是现在大部分的编程语言表示字符串的字节单位都是字节,所以对于不满一个字节的 BIT STRING 我们需要补充一定的二进制位将其变成一个字节。例如:一个 BIT STRING 类型的 KeyUsage 二级制值为
1 1111
,我们需要在右边填充三个二进制位将其组成一个字节1111 1000
,那么 keyUsage 的 BIT STRING 值为03F8
,03
是说我们填充了 3 个二进制位。