ASN.1 编码规则
ASN.1(Abstract Syntax Notation One) 是一套标准,是描述数据的表示、编码、传输、解码的灵活的记法,它提供了一套正式、无歧义和精确的规则以描述独立于特定计算机硬件的对象结构。ASN.1本身只定义了表示信息的抽象语法,但是没有限定其编码的方法。
asn.1的标准编码规则有以下几种:
- 基本编码规则(BER,Basic Encoding Rules)
- 规范编码规则 (CER Canonical Encoding Rules)
- 唯一编码规则(DER,Distinguished Encoding Rules)
- 压缩编码规则(PER,Packed Encoding Rules)
- XML编码规则(XER,XML Encoding Rules)
描述ASN.1记法的标准:
- ITU-T Rec. X.680 | ISO/IEC 8824-1
- ITU-T Rec. X.681 | ISO/IEC 8824-2
- ITU-T Rec. X.682 | ISO/IEC 8824-3
- ITU-T Rec. X.683 | ISO/IEC 8824-4
描述ASN.1编码规则的标准
- ITU-T Rec. X.690 | ISO/IEC 8825-1 (BER, CER and DER)
- ITU-T Rec. X.691 | ISO/IEC 8825-2 (PER)
- ITU-T Rec. X.692 | ISO/IEC 8825-3 (ECN)
- ITU-T Rec. X.693 | ISO/IEC 8825-4 (XER)
BER CER DER 编码规则
X.690
X.690 是一个ITU-T(https://en.wikipedia.org/wiki/ITU-T)标准,指定了几种编码规则,主要是(BER, CER, DER )
基本编码规则 (BER) 是 ASN.1 标准制定的用于将数据编码为二进制格式的原始规则。这些规则在 ASN.1 术语中统称为传输语法,指定用于编码数据的确切八位字节(8 位字节)
BER编码规则
BER 基本编码规则的格式指定了一种用于编码 ASN.1 数据结构的自描述和自定界格式。每个数据元素都被编码为类型标识符、长度描述、实际数据元素,以及必要时的内容结束标记。这些类型的编码通常称为类型-长度-值(TLV) 编码。但是,在 BER 的术语中,它是identifier-length-contents。这种格式允许接收者从不完整的流中解码 ASN.1 信息,而无需预先了解数据的大小、内容或语义.
CER 和DER 都是BER的一种变体,他们从基本编码规则允许的编码中选择一种编码,消除了其余选项。
BER编码的数据由以下几个部分组成:
Identifier octets Type | Length octets | Contents octets | End-of-Contents octets |
---|---|---|---|
Type | Length | Value | (only if indefinite form) |
End-of-Contents只在编码不定长数据的时候才会使用,用于标记数据内容的结束。
Value在 NULL类型的时候也可以省略
Identifier octets Type
type 被编码成一个或多个字节,主要由 标签种类tag class,数据类型P(Primitive)/C(Constructed),tag number 组成。
结构如下:
第一个字节的前2位表示 tag class 第3位表示p/c,后边则是tag number
- tag class 有以下几种,它的值是用来区分 ASN.1 类型
类型 | 值 | 说明 |
---|---|---|
universal | 0 | 表示原始的数据类型 |
application | 1 | 只适用于一个特定的应用程序类型 |
context-specific | 3 | 根据上下文定义的类型 |
private | 4 | 私人规范中定义的类型 |
- p/c 表示数据内容是基本的数据类型还是复合的数据类型
类型 | 数值 | 说明 |
---|---|---|
Primitive (P) | 0 | 数据内容仅由一个数据元素组成 |
Constructed (C) | 1 | 数据内容由多个数据元素组成 |
- tag number 数据的标识,是固定的。在asn.1的原始数据类型表里可以看到。
如果定义的数据类型不是 Universal 的数据类型,那么此时需要用到更多的字节序列如 Octet2。在使用这类标记的时候,要将 Octet1 的第 5 到第 1 个二进制位置为 1 ,如果 Octet2 后面还有 Octet3,那么 Octet2 的第 8 个二进制位应该为 1。
asn.1的原始数据类型
数据类型 | 编码类型 | Tag number(十进制) | Tag number(十六进制) |
---|---|---|---|
Decimal | Hexadecimal | ||
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 |
TIME | Primitive | 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 | Constructed | 29 | 1D |
BMPString | Both | 30 | 1E |
DATE | Primitive | 31 | 1F |
TIME-OF-DAY | Primitive | 32 | 20 |
DATE-TIME | Primitive | 33 | 21 |
DURATION | Primitive | 34 | 22 |
OID-IRI | Primitive | 35 | 23 |
RELATIVE-OID-IRI | Primitive | 36 | 24 |
举个栗子:
以私钥签名的结构体为例
数据类型是SEQUENCE,那么根据表中对应的编码类型为Constructed
tag calss 是Universal
tag number 是16,转换为2进制是 10000
编码后的type应该是 0011 0000
结果就是0x30
DigestInfo ::= SEQUENCE {
digestAlgorithm AlgorithmIdentifier,
digest OCTET STRING
}
Length octets
Length字段标识 value字段编码 的字节数,分为定长和不定长。其中定长的length指的是长度不超过 127 的短格式和长度超过 127 字节的长格式。
定长的短格式:
-
前面的第一位是0
- 后边的7个位代表长度,因为7个位的2进制是
0100 0000
,最大也就是127,所以要求短格式不能超过127
定长长格式:
- 后边的7个位代表长度,因为7个位的2进制是
-
最高位是1,后面的7个位表示长度值占用的字节数,然后跟上长度值。
- 例如长度为300,转换成2进制是
0000 0001 0010 1100
,占用2个字节,那么编码后应该是
1 000 0010 0000 0001 0010 1100
总共占用3个字节,用16进制表示0x82 0x01 0x2c
- 例如长度为300,转换成2进制是
不定长格式:
- 根据图上看到最高位为1,后边7个位全是0,那就是
1000 0000
也就是0x80
固定的数值- 在value结尾处标记2个
0x00
代表内容结束,因为是不定长,必须知道内容在网络上传输时读到啥时候结束。
- 在value结尾处标记2个
保留格式
- 最高位为1,后边7个为全是1,
0xFF
表示 - 同时在数据内容结尾处用2个
0x00
标记,代表着内容结束。
Contents octets
value 是数据内容的字节编码,如果不存在或者是虚对象的时候可能没有,比如value是NULL
DER(Distinguished Encoding Rules)
DER 适用于需要唯一编码的情况,例如在密码学中,并确保需要数字签名的数据结构产生唯一的序列化表示。DER 可以被认为是 BER 的规范形式。DER编码主要是为满足 X.509 规范的安全数据传输的要求而创建的。
DER 在 BER 规则基础上增加了如下限制:
1. 如果长度在 0 - 127 之间,必须使用短型长度表示法,definite-Short-form;
2. 如果长度大于等于 128,必须使用长型长度表示法,并且长度必须使用尽可能少的字节表示;
3. 对于简单 string 类型和在其基础上使用隐式标签生成的类型,必须使用简单定长编码方法;
4. 对于结构化类型和在其基础上使用隐式标签生成的类型及在任何类型基础上使用显式标签生成的类型,必须使用结构化定长编码方法。
举个例子
这是之前用私钥签名der编码的数据
DigestInfo ::= SEQUENCE {
digestAlgorithm AlgorithmIdentifier,
digest OCTET STRING
}
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
oidSHA256 := asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
// 算法标识符
type AlgorithmIdentifier struct {
Algorithm asn1.ObjectIdentifier
Parameters asn1.RawValue `asn1:"optional"`
}
// 签名的数据结构
type DigestInfo struct {
DigestAlgorithm AlgorithmIdentifier
Digest []byte
}
sha := sha256.New()
m := []byte{50}
sha.Write(m)
h := sha.Sum(nil)
var digestInfo = DigestInfo{
DigestAlgorithm: AlgorithmIdentifier{
Algorithm: oidSHA256,
Parameters: asn1.RawValue{
Tag: asn1.TagNull,
},
},
Digest: h,
}
d, err := asn1.Marshal(digestInfo)
if err != nil {
fmt.Println(err)
return
}
oid := hex.EncodeToString(d)
fmt.Println(oid)
// 3031300d060960864801650304020105000420d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35
30 31
30 0d
0609608648016503040201
0500
0420 d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35
第一个字节30代表Identifier octets Type,转换成2进制是
00110000
,我们计算下type,前2位是tag class,00110000 >> 6 = 00
代表universal,对照表SEQUENCE是原始数据类型,所以是OK的。第三个位是1代表是复合数据类型,也就是由多个元素构造的数据类型Constructed,那后边的
10000`自然是tagNumber了,转换成十进制是16刚好与上表对应。第二个字节31 代表 Identifier octets length,转换成2进制是
00110001
,通过编码后的数据,我们可以看出长度是小于127的(可以用0x31 & 0x7F == 0x31
,如果是true,说明是短格式,否则是长格式,如果是长格式那0x31 & 0x7F = 长度占用了几个字节,那紧接着往后的这几个字节组成的就代表长度值本身
),而DER编码长度在 0 - 127 之间,使用的是定长的短长度编码,第一位是0,后边7个位代表长度,那么长度=00110001
也就是31。第三个字节还是30,因为是SEQUENCE里面套了个SEQUENCE,所以30代表的是
AlgorithmIdentifier
结构体的type。第四个字节同理0d代表的是长度,
AlgorithmIdentifier
这个的长度,0x0d==十进制的13
所以13就是长度,短长度。-
第五个往后13个字节
0609 6086480165030402010500
就代表AlgorithmIdentifier
这个的数据内容- 这13个字节其中有11个字节
0609608648016503040201
是算法标识符Algorithm
- 0609,是type和长度,后边的是数据内容,06 转换为二进制
0000 0110
,前2位00是tag class 第三位0代表不是构造的复合数据类型,0 0110
就是tagnumber,也就是6。 -
608648016503040201
就是sha256
算法标识符2.16.840.1.101.3.4.2.1
DER编码后的值
- 0609,是type和长度,后边的是数据内容,06 转换为二进制
- 05和00是值NULL的编码,我们在代码中给的值就是NULL,05这个字节指的是NULL的type,00指的是长度为0么。
- 这13个字节其中有11个字节
第十四个字节04指的是
Digest
它的类型是OCTET STRING,那tag class为 universal 值是0,基本数据类型primitive由一个元素组成值是0,tag number是4,组合起来就是00 0 00100
,转16进制是0x04。第十五个字节20代表了长度,指的是
Digest
这个字段的长度,用二进制表示0010 0000
,短格式,长度就是32个字节。-
剩下的
d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35
就是数据编码的内容了,在本示例中就是哈希值h的DER编码值
在ASN.1 JavaScript decoder中把DER编码后的值输入进去可以看到分析的结果图如下: