ASN.1编码详解:从入门到代码实战

一、ASN.1编码简介
ASN.1,全称是Abstract Syntax Notation One,是一种在计算机网络中为传输信息而设计的,采用抽象编码规则的标记语言。ASN.1是一种ISO/ITU-T标准,最初是1984年的CCITT X.409:1984标准的一部分,由于其广泛应用,1988年ASN.1被移动到独立标准X.208,并于1995年进行全面修订后变成X.680系列标准。ASN.1标准本身只定义了表示信息的抽象语法和编码规则,但是没有限定其编码的方法,在如今的计算机编程和信息安全领域,有基于Java/Python/C++等编程语言实现的ASN.1编码方法。

ASN.1编码通过独立于硬件平台和编程语言的抽象语法来描述数据结构和数据类型,实现了平台无关的抽象设计。因此,被ASN.1编码后的数据可以很方便地实现跨设备传输。

ASN.1编码广泛用于X.509证书、HTTPS证书等信息安全标准中,SNMP协议和LDAP协议采用了BER编码来实现数据的传输,信用卡和借记卡的EMV支付标准采用了BER编码来实现卡上数据的刷写。

与ASN.1编码的用途类似的还有Protocol Buffer和Thrift,也可以用来描述数据结构,并提供一套自定义的编解码和序列化处理机制,可以实现跨设备通信。

ASN.1编码的数据样例:

Foo DEFINITIONS ::= BEGIN

Question ::= SEQUENCE {
id INTEGER,
question IA5String
}

Answer ::= SEQUENCE {
id INTEGER,
answer BOOLEAN
}

END

二、ASN.1编码分类

1.基本编码规则BER(Basic Encoding Rules)
BER是一种原始的ASN.1编码规则,灵活但编码生成的字节流十分冗长,语法基于TLV结构:
示例: INTEGER 42的BER编码
Tag(INTEGER=02) |Length(01) |Value(2A)
02 01 2A
2.唯一编码规则DER(Distinguished Encoding Rules)
BER的子集,语法比BER更严格,对同一数据只有唯一的一种编码,要求使用定长形式的L字段,BER的L字段是不定长的。

3.规范编码规则CER(Canonical Encoding Rules)
类似于DER用于不定长数据的编码,但外界很少用。

4.压缩编码规则PER(Packed Encoding Rules)
分为Aligned PER和Unaligned PER,旨在高度优化编码长度,实现比特级打包,PER编码省略了部分TLV信息,如标签、长度,解码时需要完整的ASN.1的schema定义。
Aligned PER(APER)在适当位置填充比特至字节边界。
Unaligned PER(UPER)无比特填充,编码上更紧凑。
PER编码规则主要用于带宽敏感的协议,如3G/4G/5G通信等。

5.XML编码规则XER(XML Encoding Rules)
将ASN.1值编码为XML文档,可读性很强,数据很直观,但编码很冗长,扩展的XER,即EXER,支持XML Schema。
XER编码样例:

5 Anybody there?

三、ASN.1编码的核心语法

1.schema文件
通常以 “.asn”或 “.asn1″作为文件的后缀名。

2.模块定义
使用”模块名 DEFINITIONS ::= BEGIN … END”来包含完整的内容。
样例:
MyProtocolModule DEFINITIONS AUTOMATIC TAGS ::= BEGIN
— 类型和值定义写在这里 —
END
“AUTOMATIC TAGS”用来设定标记模式。

3.数据字段定义
类型名 ::= 类型描述
类型描述可以是内置类型或用户自定义类型,用户自定义类型类似于C语言中的结构体。
样例:
UserId ::= INTEGER
— 定义UserId为整数类型
UserName ::= IA5String (SIZE(1..32))
— 定义UserName为1到32个字符的字符串

4.常量和默认值的定义
值名 类型名 ::= 值
样例:
maxUsers INTEGER ::= 100
defaultCountry PrintableString ::= “US”

5.常用数据类型
ANY类型,表示任意类型的一个任意值。
BIT STRING类型,表示0和1组成的任意长度的比特流。
IA5String类型,表示由IA5字符组成的字符串,IA5代表International Alphabet 5,与ASCII码类似,该字符集包括不可打印的控制字符。
INTEGER类型,表示的整数类型。
OBJECT IDENTIFIER类型,表示一个对象识别符,由一列整数组成。
OCTET STRING类型,表示任意长度的二进制数据组成的字符串。
PrintableString类型,表示由可打印字符组成的字符串。
SEQUENCE类型,表示一个或多个类型组成的有序集合。
SET类型,表示一个或多个类型组成的无序集合。

6.数值约束
范围约束:INTEGER (0..100) 取值为0~100。
取值约束:Status (FROM {idle, busy}) 限制有效枚举值。
格式约束:(PATTERN): PrintableString (FROM (“[A-Z]*”)) 正则表达式。
长度约束:SEQUENCE SIZE(1..10) 列表长度、IA5String (SIZE(1..64)) 字符串长度。

完整的ASN.1编码文件样例:
Demo1:
TestModule DEFINITIONS ::= BEGIN — Module parameters preamble
Circle ::= SEQUENCE { — Definition of Circle type
position-x INTEGER, — Integer position
position-y INTEGER, — Position along Y-axis
radius INTEGER (0..MAX) — Positive radius
} — End of Circle type
END — End of TestModule
Demo2:
MyShopPurchaseOrders DEFINITIONS AUTOMATIC TAGS ::= BEGIN

PurchaseOrder ::= SEQUENCE {
dateOfOrder DATE,
customer CustomerInfo,
items ListOfItems
}

CustomerInfo ::= SEQUENCE {
companyName VisibleString (SIZE (3..50)),
billingAddress Address,
contactPhone NumericString (SIZE (7..12))
}

Address::= SEQUENCE {
street VisibleString (SIZE (5 .. 50)) OPTIONAL,
city VisibleString (SIZE (2..30)),
state VisibleString (SIZE(2) ^ FROM (“A”..”Z”)),
zipCode NumericString (SIZE(5 | 9))
}

ListOfItems ::= SEQUENCE (SIZE (1..100)) OF Item

Item ::= SEQUENCE {
itemCode INTEGER (1..99999),
color VisibleString (“Black” | “Blue” | “Brown”),
power INTEGER (110 | 220),
deliveryTime INTEGER (8..12 | 14..19),
uantity INTEGER (1..1000),
unitPrice REAL (1.00 .. 9999.00),
isTaxable BOOLEAN
}
END
取值约束:
color VisibleString (“Black” | “Blue” | “Brown”)
power INTEGER (110 | 220)
范围约束:
quantity INTEGER (1..1000)
deliveryTime INTEGER (8..12 | 14..19)
unitPrice REAL (1.00..9999.00)
State ::= VisibleString SIZE(2) ^ FROM (“A”..”Z”))
长度约束:
contactPhone NumericString (SIZE (7..12))
ListOfItems ::= SEQUENCE (SIZE (1..100)) OF Item
zipCode NumericString (SIZE (5 | 9))

四、ASN.1编码代码实战

编码场景:我们基于开源库asn1c,实现BER的编解码。

asn1c开源库地址:
https://github.com/vlm/asn1c/tree/v0.9.28
asn1c开源库的编译和安装命令:
test -f configure || autoreconf -iv
./configure
make
make check
make install

以下两个命令可以用来检验asn1c是否安装完成:
asn1c -h
man asn1c

(1).ASN.1编码的schema文件:rectangle.asn1
RectangleModule1 DEFINITIONS ::= BEGIN
Rectangle ::= SEQUENCE {
height INTEGER,
width INTEGER
}
END
编译方式:
asn1c rectangle.asn1
编译完成以后,会生成很多编解码所需要的ASN头文件。

(2).ASN.1 BER编码实现:encoder.c
采用der_encode接口将BER编码生成的二进制内容写入到本地文件。
采用xer_fprint接口打印编码字段。

include

include

include “Rectangle.h”

/* Rectangle ASN.1 type / / Write the encoded output into some FILE stream. */
static int write_out(const void *buffer, size_t size, void *app_key) {
FILE *out_fp = app_key;
size_t wrote = fwrite(buffer, 1, size, out_fp);
return (wrote == size) ? 0 : -1;
}
int main() {
Rectangle_t *rectangle;
asn_enc_rval_t ec;

/* Allocate the Rectangle_t */
rectangle = calloc(1, sizeof(Rectangle_t));
if(!rectangle) {
    perror("calloc() failed");
    exit(1);
}
/* Initialize the Rectangle members */
rectangle->height = 42;
rectangle->width = 23;

/* BER encode the data if filename is given */
constchar *filename = "ASN_TEST";
FILE *fp = fopen(filename, "wb");
if(!fp) {
    perror(filename);
    exit(1);
}
/* Encode the Rectangle type as BER (DER) */
ec = der_encode(&asn_DEF_Rectangle, rectangle, write_out, fp);
fclose(fp);
if(ec.encoded == -1) {
    fprintf(stderr, "Could not encode Rectangle (at %s)\n",
    ec.failed_type ? ec.failed_type->name : "unknown");
    exit(1);
} else {
    fprintf(stderr, "Created %s with BER encoded Rectangle\n", filename);
}
/* Also print the constructed Rectangle XER encoded (XML) */
xer_fprint(stdout, &asn_DEF_Rectangle, rectangle);
return0;

}
编译方式:
gcc -o encoder -I. *.c
(3).ASN.1 BER解码实现:decoder.c
采用ber_decode接口读取本地的二进制文件,并进行BER解码。
采用xer_fprint接口打印编码字段。

include

include

include

/* Rectangle ASN.1 type */
int main(int ac, char **av) {
char buf[1024];
asn_dec_rval_t rval;

Rectangle_t *rectangle = 0;
FILE *fp;
size_t size;
constchar *filename = "ASN_TEST";

fp = fopen(filename, "rb");
/* Read up to the buffer size */
size = fread(buf, 1, sizeof(buf), fp);
fclose(fp);
if(!size) {
    fprintf(stderr, "%s: Empty or broken\n", filename);
    exit(1);
}
/* Decode the input buffer as Rectangle type */
rval = ber_decode(0, &asn_DEF_Rectangle, (void **)&rectangle, buf, size);
if(rval.code != RC_OK) {
    fprintf(stderr, "%s: Broken Rectangle encoding at byte %ld\n", filename,
    (long)rval.consumed);
    exit(1);
}
/* Print the decoded Rectangle type as XML */
xer_fprint(stdout, &asn_DEF_Rectangle, rectangle);
return0;

}
编译方式:
gcc -o decoder -I. .c ——-运行结果——- step.01:编码 编码完成以后,会在本地生成一份二进制文件”ASN_TEST” $ ./encoder Created ASN_TEST with BER encoded Rectangle 42 23 二进制文件”ASN_TEST”的内容如下: $ vim ASN_TEST 0^F^B^A^B^A^W
step.02:解码
解码成功以后会打印字段内容:
$ ./decoder
42 23
如果破坏二进制文件”ASN_TEST”,会导致解码失败:
$ ./decoder
ASN_TEST: Broken Rectangle encoding at byte 0
*扩展场景,如果需要对编解码的字段的取值范围加以限制,比如不超过100或255,可以将ASN.1编码的schema文件改写为如下:
RectangleModule1 DEFINITIONS ::= BEGIN
Rectangle ::= SEQUENCE {
height INTEGER (0..100), –Value range constraint
width INTEGER (0..255) –Makes width non-negative
}
END
此时,需要在调用der_encode之前和ber_decode之后,利用asn_check_constraints接口进行字段取值范围的校验。
代码实现如下,返回值大于0表示取值校验不通过:
int ret;
char errbuf[128];
size_t errlen = sizeof(errbuf);
ret = asn_check_constraints(&asn_DEF_Rectangle, rectangle,
errbuf, &errlen);
if(ret) {
fprintf(stderr, ”Constraint validation failed: %s\n”,
errbuf
);
}

声明:来自程序员与背包客,仅代表创作者观点。链接:https://eyangzhen.com/1855.html

程序员与背包客的头像程序员与背包客

相关推荐

关注我们
关注我们
购买服务
购买服务
返回顶部