介绍
ASN.1语法可用于表示与编程语言无关的数据结构。比如,RFC X509就使用了ASN.1语法表示证书的结构。
本博客主要参考:http://luca.ntop.org/Teaching/Appunti/asn1.html
在线解析工具:http://lapo.it/asn1js/
基于本博客编写的ASN.1解析库: https://github/xhd2015/net-protocol-tools (见包名com.fulton_shaw.net.ans1
)
基础
7位分组编码
当一个数据需要使用多个字节来表示时,我们按照下面的原则来选择编码方式:
1.能够与单字节编码区分,在连续的字节流中能够识别边界;
2.尽可能少地占用空间
多字节分散编码:
1.将源数据按7位分组,每组填充到一个字节中的低7位;
2.除了最低位字节的高位是0,其他字节的高位都是1;这样能够区分边界;因为高位是1的字节一定不是第一个字节;
3.如果值小于128,即可用7位表示,则只使用一个字节即可。
英文描述:
the number,base 128, most significant digit first, with as few digits as possible, and with the bit 8 of each octet except the last set to “1.”
下面语法中的tag编码,Object Identifier编码均使用这种方式。
对应工具包中的方法:com.fulton_shaw.net.tls_https.EncodingUtils.encode7BitGroupToByteArray(byte[])
com.fulton_shaw.net.tls_https.EncodingUtils.decode7BitGroup(byte[])
示例:
01 bb 8d
编码为 86 f7 0d
80
编码为 8100
7f
编码为 7f
这种编码的好处是能够在编码序列中区分边界。
基本语法
类型
在ASN.1中,类型是指一组值,包括有限的值和无限的值;值是指某个类型的一个元素。
四种类型:1.简单类型,原子类型 2.结构类型,由其他类型组成 3.CHOICE类型,类似于联合 4.ANY,任意类型。
可以使用:==
赋值符号赋予类型或者值一个名称,这个名称可用于定义其他类型。
注意,标识符可以由字母,数字,空格,短线组成。
tag
除了CHOICE和ANY类型,其他类型都有一个tag
,包括一个class
和一个非负的tag编号
。ANS.1的两个类型的等价性完全由tag编号
决定,而不受名称的影响。
有四种类型的tag
:1.Universal 全局的 2.Application 应用的 3.Private 私有的,仅企业使用 4.Context-Specific 上下文相关
类别class | bit8 bit7 |
---|---|
全局 | 00 |
应用 | 01 |
上下文相关 | 10 |
私有 | 11 |
全局类型的tag
映射表
类型 | tag编号(16进制) |
---|---|
INTEGER | 02 |
BIT STRING | 03 |
OCTET STRING | 04 |
NULL | 05 |
OBJECT IDENTIFIER | 06 |
SEQUENCE and SEQUENCE OF | 10 |
SET and SET OF | 11 |
PrintableString | 13 |
T61String | 14 |
IA5String | 16 |
UTCTime | 17 |
简单类型
BIT STRING 任意长字符串
IA5String 即任意长ASCII字符串
INTEGER 任意整数
OBJECT IDENTIFIER 对象标识,一组数字,用来表示算法或属性
OCET STRING 字节数组
PrintableString 可打印的字符串
UTCTime UTC时间
结构类型
SEQUENCE, SEQUENCE OF 分别表示0到多个和1到多个类型的有序集合
SET, SET OF 表示无序集合
显式tag和隐式tag
显式tag:[class number
] EXPLICIT 编码更长,能够区分CHOICE和ANY
隐式tag: [class number
] IMPLICIT 编码更短,但是不能区分CHOICE和ANY
编码
基本编码规则(BER),用于将ASN.1类型表示为字节数组。
有三种编码方法:1.原始类型,定长编码 2.构造类型,定长编码 3.构造类型,非定长编码。
简单的数字类型使用第1种编码,结构使用第2或3种编码,字符串可能使用任何一种。
编码规则将结果分为3或者4个部分:
1.标识,用多个字节标识class
和tag
,同时指定编码方法是原始类型还是构造类型
2.长度,对于定长编码,这里表示长度;非定长编码,第一个字节标识额外的用于表示长度的字节数,后面的字节标识真实的长度。这里可用于表示定长
3.内容,对于原始类型和定长编码,这里表示的就是内容本身;对于构造类型,这里表示多个编码结果的组合
4.尾部,对于非定长编码,这里表示内容结束;其他编码方法没有这个部分。
原始类型的定长编码
标识字节
对于小于等于30的tag,可以使用8位来表示class和tag编号,如下分配
bit8 + bit7: 00 = 全局 01=应用 10=上下文相关 11=私有
bit6 = 0
bit5~bit1 表示tag编号
对于大于30的tag,多个字节标识,第一个字节和上面一样,但bit5~bit1全都是1,后序的字节使用上面定义的7位分组编码
。
长度
短形式,单字节,bit8=0,bit7~bit1表示长度
长形式,多字节, 第一个字节的bit8=1, bit7~bit1表示长度域的长度
定长构造类型编码方法
标识字节,同上面,但bit6=1,表示构造类型
长度同上
内容,拼接
非定长构造类型编码方法
标识字节,同上
长度:单字节 0x80
内容:同上
结尾:两个字节, 0x00,0x00,类型的class和tag都是0。
编码方法选择
DER,BER的子集,用于限定编码选择。
1.如果长度在0~127之间,必须采用长度的短形式
2.如果长度在128或者之上,必须采用长度的长形式
3.简单的字符串类型,由字符串tag推导的类型,原始类型必须使用原始类型定长编码
4.构造类型,由构造类型推导的隐式tag类型,显式tag类型,必须使用构造类型的定长编码
隐式tag类型
语法: [[class] tag_number ] IMPILICT Type
其中class可以省略,默认为context-specific, tag_number是在class中的类型。
显式tag类型
显式tag类型需要在结构体之前加上额外的标签信息。
注:如果省略了IMPLICIT或EXPLICIT关键字,则使用模块定义的默认策略。对于X509v3而言,默认策略是IMPLICIT
Object Identifier
用于定义一组用数字标识的标识符。
首先,会定义一个前缀:
-- Arc for standard naming attributes
id-at OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) ds(5) 4 }
id-at
是前缀,值顺序是2,5,4, 定义中的标识符可以忽略。
然后,定义具体的类型值:
id-at-commonName AttributeType ::= { id-at 3 }
则意味着 id-at-commonName
的值是{2,5,4,3}
,可以表示为2.5.4.3
.
编码规则
该类型的tag是0x06
,长度由实际编码后的长度决定。
编码规则如下:
前两个数(至少包含两个数)使用 40*value1 + value2
的格式编码,因为value2总是小于40,编码后的结果形成第一个字节;
后面的字节,
1.如果小于128,即小于8位,则按字节填入即可比如2.5.4.3
可以编码为55 04 03
, 其中0x55/40=2, 0x55%40=5;
2.大于等于8位,需要拆分成多个字节处理,除了第1个字节的高位是0,其他字节的高位都是1;编码的结果是使得结果的每个字节的低7位拼接起来,能够形成源数据。
比如113549
,其二进制表示是0b1_1011_1011_1000_1101
a.第一个字节应当表示低7位,且高位为0,结果为: 0b0000_1101
即0x0d
;
b.第二个字节应当表示第2批7位,且高位为1,结果为: 0b1111_0111
,即0xf7
;
c.第三个字节应当表示第3批7位,且高位为1,结果为: 0b1000_0110
,即0x86
所以,最终的编码结果是 86 f7 0d
.