初识ASN.1

Posted by

一 基础介绍

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)Primitive00
BOOLEANPrimitive11
INTEGERPrimitive22
BIT STRINGBoth33
OCTET STRINGBoth44
NULLPrimitive55
OBJECT IDENTIFIERPrimitive66
Object DescriptorBoth77
EXTERNALConstructed88
REAL (float)Primitive99
ENUMERATEDPrimitive10A
EMBEDDED PDVConstructed11B
UTF8StringBoth12C
RELATIVE-OIDPrimitive13D
Reserved14E
Reserved15F
SEQUENCE and SEQUENCE OFConstructed1610
SET and SET OFConstructed1711
NumericStringBoth1812
PrintableStringBoth1913
T61StringBoth2014
VideotexStringBoth2115
IA5StringBoth2216
UTCTimeBoth2317
GeneralizedTimeBoth2418
GraphicStringBoth2519
VisibleStringBoth261A
GeneralStringBoth271B
UniversalStringBoth281C
CHARACTER STRINGBoth291D
BMPStringBoth301E

三 编码

ASN.1定义的内容如何传输?类似Json与JsonScheme,ASN.1只是一种模型定义,而实际传输则是编码之后的具体数据。常见的编码有以下几种:

  • BER:Basic Encoding Rules 基本编码规则
  • CER:Canonical Encoding Rules 规范编码规则
  • DER:Distinguished Encoding Rules 可分辨编码规则

CER与DER都是从BER上变体,都在BER的基础上做了一些限制

BER编码结构:TLV模式

类型标识长度描述内容[结束标记]
TypeLengthValueEOC
1个或多个字节表示1个或多个字节表示
(上述的EOC用于不定长描述)

1.Type域

76543210
TagClass
00Universal表示标准定义的内部类型
01Application特定应用设定的数据类型
10Context-specific根据上下文定义的类型
11Private私人规范中定义的类型
Form
0Primitive单类型,比如BOOLEAN
1Constructed复合类型(多个数据组成)
Tag number
00000
00001
*
11111
举例:定义一个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 定长短格式
76543210
0长度的值
Definite long 定长长格式
7 6543210
1 定义长度值所占用的字节数,后面的字节表示实际长度
Indefinite 不定长格式
76543210
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)

剩余部分省略
T00110000SEQUENCE30
L00011101length:81D
VT10000000Context-specific80
L00000110length:606
V11100110李明E6
100111019D
100011108E
11100110E6
1001100098
100011108E
T10000001Context-specific81
L00000001length:101
V000100101812

四 很小的一个点

在一次对接第三方时,对方返回的签名(采用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,应当去掉。

参考文章:

ASN.1 语法

ASN.1编码方式详解

X.690-201508-S

Leave a Reply

您的电子邮箱地址不会被公开。 必填项已用*标注