ASN.1 语法・一

X.690 规范

Posted by mingfer on April 1, 2019

在密码学中,诸如证书,公钥等基本上都是使用 ASN.1 DER 进行编码。

前言

本文翻译自 wikipedia - X.690

X.690ITU-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

image-20190405114633840

如上图所述,在数据元素类型编码字节序列的第一个字节 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 字节的长格式。

image-20190405195354337

定长短格式:

  • 第一个二进制位为 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

注意事项

  1. BIT STRING 类型的 Value 第一位是填充长度位,用于指明该 BIT STRING 填充了多少个二进制位。因为 BIT STRING 是以二进制位为单位的字符串,但是现在大部分的编程语言表示字符串的字节单位都是字节,所以对于不满一个字节的 BIT STRING 我们需要补充一定的二进制位将其变成一个字节。例如:一个 BIT STRING 类型的 KeyUsage 二级制值为 1 1111 ,我们需要在右边填充三个二进制位将其组成一个字节 1111 1000 ,那么 keyUsage 的 BIT STRING 值为 03F803 是说我们填充了 3 个二进制位。