一 基础介绍
1.什么是ASN.1
ASN.1:Abstract Syntax Notation 抽象语法标记,其中的1是在起草初期为了方便后续扩展而设计的,但是直到现在,依旧还是ASN.1,没有出现2。
2.用途
定义了一套数据描述的规范,不限制于任何平台或者程序上使用。
二 语法
C语言如下:
struct student{
char name[10];
int age;
};
GO描述如下:
type student struct {
name string
age int
}
ASN.1如下:
Student ::= SEQUENCE {
name UTF8String,
age INTERGER
}
1.语法
类型的定义
新类型的名字(大写字母开头) ::= 类型(已定义的类型)
关键字全部大写
值定义
新的值名字(小写字母开头) 值的类型 ::= 值
2.基本类型
数据类型 | 编码类型 | 十进制 | 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定义的内容如何传输?类似Json与JsonScheme,ASN.1只是一种模型定义,而实际传输则是编码之后的具体数据。常见的编码有以下几种:
- BER:Basic Encoding Rules 基本编码规则
- CER:Canonical Encoding Rules 规范编码规则
- DER:Distinguished Encoding Rules 可分辨编码规则
CER与DER都是从BER上变体,都在BER的基础上做了一些限制
BER编码结构:TLV模式
类型标识 | 长度描述 | 内容 | [结束标记] |
Type | Length | Value | EOC |
1个或多个字节表示 | 1个或多个字节表示 |
1.Type域
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
TagClass | |||||||
0 | 0 | Universal | 表示标准定义的内部类型 | ||||
0 | 1 | Application | 特定应用设定的数据类型 | ||||
1 | 0 | Context-specific | 根据上下文定义的类型 | ||||
1 | 1 | Private | 私人规范中定义的类型 | ||||
Form | |||||||
0 | Primitive | 单类型,比如BOOLEAN | |||||
1 | Constructed | 复合类型(多个数据组成) | |||||
Tag number | |||||||
0 | 0 | 0 | 0 | 0 | |||
0 | 0 | 0 | 0 | 1 | |||
* | |||||||
1 | 1 | 1 | 1 | 1 | |||
举例:定义一个INTEGER的类型
INTERGER是Universal,所以,第7、6位为00
它是Primitive类型,所以第5位为0
INTERGER原生类型中定义的十进制标记值为2,所以第4到0位为00010,合并起来就是 0000010,记为0x02
特殊:Tag number > 30
采用多字节来描述Tag number
举例:比如,现有一个Context-specific类型的,Form是Primitive,Tag number是35:
35,二进制表示为 100011
字节1:
Context-specific,则第7、6位是10
Form是Primitive,则第5位是0
按照规则,剩余5位全为1,则是11111
字节2:
第7位是:0
剩余的7位:0100011
则合并起来就是
1001 1111 0010 0011
2.Length域
用来表示值的长度,同时又可以分为以下4种
Definite short 定长短格式 | |||||||
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
0 | 长度的值 |
Definite long 定长长格式 | |||||||
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
1 | 定义长度值所占用的字节数,后面的字节表示实际长度 |
Indefinite 不定长格式 | |||||||
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
1 | 第6到0位固定为0 |
不定长的数据内容由两个EOC域结束,即两个连续的0x00。
举例:假设长度是28
28的二进制是:11100
则完整的即是 0001 1100
举例:假设长度是300
实际长度的值300,二进制是 1 0010 1100,需要2个字节来表示
所以第7位是1,然后用第6到0位表示2,则是 1000 0010
所以连起来完整的就是 1000 0010 0000 0001 0010 1100
3.Value域
就是指实际的数据。实际的应用场景中将会包含更多的TLV的结构,也就是值里面又是一个复合结构
4.实际的例子
已知现有数据对应的ASN.1描述如下
Test DEFINITIONS AUTOMATIC TAGS ::= BEGIN
Student ::= SEQUENCE
{
name UTF8String,
age INTEGER,
addr Address
}
Address ::= SEQUENCE
{
country UTF8String,
postcode INTEGER
}
END
分析以下数据
30 1D
80 06 E6 9D 8E E6 98 8E (len:8)
81 01 12
A2 10
80 09 67 75 61 6E 67 7A 68 6F 75 (len:11)
81 03 00 C3 51(len:5)
分析如下:
30 -> 0011 0000,其中第5到0位为10000,转成十进制为16,表示的是SEQUENCE
1D -> 0001 1101,其中第5到0位为11101,转成十进制为29,表示的是长度为29
80 -> 1000 0000,则说明 tag number:0
06 -> 0000 0110,长度为6
E6 9D 8E E6 98 8E,6个字节,即具体的值。根据上文,这里是UTF-8的表示方式,转成二进制后
11100110 10011101 10001110 -> 0110 011101 001110 -> 1100111 01001110 -> 103,78 -> 0x67 4e -> \u674e -> 李
11100110 10011000 10001110 -> 0110 011000 001110 -> 1100110 00001110 -> 102,14 -> 0x66 0e ->\u660e -> 明
引申:关于UTF-8
unicode字符集:4个字节表示
utf-8:对Unicode实行可变长度编码
1字节:0xxxxxxx
2字节:110xxxxx 10xxxxxx
3字节:1110xxxx 10xxxxxx 10xxxxxx
4字节:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
81 -> 1000 0001,tag number为1
01 -> 0000 0001,长度为1
12 -> 转成对应的十进制就是18(根据上文描述这里的值类型是INTERGER)
剩余部分省略
T | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | SEQUENCE | 30 | |
L | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | length:8 | 1D | |
V | T | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | Context-specific | 80 |
L | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | length:6 | 06 | |
V | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 李明 | E6 | |
1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 9D | |||
1 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 8E | |||
1 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | E6 | |||
1 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 98 | |||
1 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 8E | |||
T | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | Context-specific | 81 | |
L | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | length:1 | 01 | |
V | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 18 | 12 |
四 很小的一个点
在一次对接第三方时,对方返回的签名(采用DER编码)是
MEYCIQB/9nnS53lttOTuFngNJrIynuDmtsHgdCNvYLoFDQe/kwIhAJeerGdCRpM9wgjplF1JKUg7m7OWZbc3Zya2QnytJDb2
结果,本地验签失败,其中一个涉及的package抛异常提示:Integer not minimally encoded
分析原因:
以上编码转换成hex
30 46 02 21 00 7f f6 79 d2 e7 79 6d b4 e4 ee 16 78 0d 26 b2 32 9e e0 e6 b6 c1 e0 74 23 6f 60 ba 05 0d 07 bf 93 02 21 00 97 9e ac 67 42 46 93 3d c2 08 e9 94 5d 49 29 48 3b 9b b3 96 65 b7 37 67 26 b6 42 7c ad 24 36 f6
解码分析:
30 46
30->0011 0000 Constructed SEQUENCE
46->0100 0110 length:70
02 21
02->0000 0010 Universal INTEGER
21->0010 0001 length:33
具体的INTERGER值为:00 7f f6 79 d2 e7 79 6d b4 e4 ee 16 78 0d 26 b2 32 9e e0 e6 b6 c1 e0 74 23 6f 60 ba 05 0d 07 bf 93
00 -> 0000 0000
7f -> 0111 1111
而这里关于INTERGER的编码规范,存在一个要求:对于正数,如果最高比特位为0则直接编码;如果为1,则在最高比特位之前增加一个全0的八位组。比如以下示例:
整数 1500 -> 00000101 11011100 => 02 02 05 DC
整数 40000 -> 10011100 01000000 => 02 03 00 9C 40
为什么要这样?见:https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf,8.3.2指出:
If the contents octets of an integer value encoding consist of more than one octet, then the bits of the first octet and bit 8 of the second octet:
a) shall not all be ones; and
b) shall not all be zero.
NOTE – These rules ensure that an integer value is always encoded in the smallest possible number of octets.
翻译出来即是,针对多字节编码整数的,第一个字节的所有位和第二个字节的第八位(即整体的第1位到第9位),须满足以下规则:不能全为0、不能全为1。目的是为了保证最小的编码。
所以,对方给回的签名编码是错误的,它给7f前面加了00,应当去掉。
参考文章: