一、定义:
是定义抽象数据类型的标准。
是用于描述数据的表示、编码、传输、解码的灵活记法。
它提供一套正式、无歧义和精确的规则,以描述独立于特定计算机硬件的对象结构。
标准的ASN.1编码规则有其基本编码规则BER,规范编码规则CER,唯一编码规则DER,压缩编码规则PER,和XML编码规则。
二、编码规则
通常ASN.1编码采用TLV格式。
由三部分组成:    TAG--头字节  LEN--长度 VAL--数据
(1)头字节(TAG)
通常位于ASN.1编码的开始,由3部分组成: 类别  结构化位   原始类型
最高位第7和8位表示 类别
    第六位表示 结构化位
    第5-1位表示 原始类型
类别位:描述数据将要解释的上下文。
位8 

位7 

类 

通用(universal) 

应用(Application) 

上下文特定 

专用 

结构化位:表示一个给定的编码是否是相同类型的多种编码的结构化。
当一个应用程序在逻辑上是一个元素,但并不是一次就包含所有组件的元素进行编码时,结构化位就十分有效。结构化元素也是容器类型所必须的,因为在逻辑上,他们只是其他元素的集合。
结构化元素有自己的头字节和长度字节,之后是元素各个要素组件的单独编码,也就是说,从他们自身来看,这些要素组件式独立的可解码ASN.1数据类型。
我们假设除容器外,其他所有数据类型的结构化位都为0
原始类型: 
ASN.1提供了一些基本的预定义的数据类型:
UNIVERSAL    0    保留给编码规则使用
UNIVERSAL    1    布尔类型
UNIVERSAL    2     整数
UNIVERSAL    3    二进制字符串类型    位串
UNIVERSAL    4    八进制字符串类型    八位位组串
UNIVERSAL    5    空类型
UNIVERSAL    6    对象标识符类型
UNIVERSAL    7    对象描述符类型
UNIVERSAL    8    外部类型和类型实例
UNIVERSAL    9    实数类型
UNIVERSAL    10    枚举类型
UNIVERSAL    11    嵌入的pdv类型
UNIVERSAL    12    UTF8字符串类型
UNIVERSAL    13    相关对象标识符类型
UNIVERSAL    14-15    保留
UNIVERSAL    16    序列和类型序列
UNIVERSAL    17   集合和类型的集合
UNIVERSAL    18-22,25-30    字符串类型
UNIVERSAL    23-24    时间类型
(2)长度(Len)
根据编码的实际长度,ASN.1定义了两种长度编码。根据编码的长度和环境,可以用定长或者非定长方式进行编码,而且还可以进一步分割成短编码或者长编码。在基本编码规则中(BER)编码器可以自由的选择短编码或者长编码,但前提是能够完全表示元素的长度。但是DER编码规则规定必须选择可以完全表示元素长度的最短编码。编码后的长度并不包含ASN.1头字节和长度字节,以减少负载。
编码的第一字节表示短编码还是长编码
最高位表示编码是短的还是长的,而低7位则形成一个长度立即数
a 短编码
在短编码中,负载的长度必须小于128个字节,长度立即数域用来表示负载的长度,这也是编码大小限制的来源。它是对所有长度小于128个字节的强制性编码。
b 长编码
在长编码中,定义了附加的抽象数据来对长度进行编码,它仅适用于所有长度大于128字节的负载,在这种情况下,长度立即数域的存储是为了表示负载长度所需的字节数,进一步讲,它表示了为了编码负载的长度需要多少字节,长度必须以big-endian格式进行编码。
如,对长度47310(即0xB8CE)进行编码,比128大,所以采用长编码格式,实际长度需要用2个字节来表示。因此第一个字节则为 0x80|0x02 即0x82.然后用big-endian格式存储长度值,即 0x82 B8 CE
这种方式允许对长度高达2^1016位的对象进行编码。
根据DER编码规则,负载长度值的长度必须为最小,因此所有1字节是无效的。一般来说,对于长编码,可以放心的假设长度立即数大于4个字节是无效的,因为很少有密码协议在一个数据包中交换大于4G的数据。
三、数据类型
4.1    布尔类型
根据BER编码规则,真值的编码可以是任何非零值,但是DER要求真值编码为0xFF
布尔值        编码
真        0x01 01 FF
假        0x01 01 00
4.2    整数型
表示一个有符号的任意精度的标量,它的编码是可移植的,平台无关的。
存储的实际数值分为字节大小的数字,并且以big-endian格式进行存储,例如,对于变量
    x = 256^k *x(k) + 256^(k-1)*x(k-1) + .... + 256^0 * x0
进行编码,八位位组{xk, ... , x0}将以递减顺序从xk到x0进行存储。编码过程规定对于正整数,第一个字节的最高位必须为0
因此,假设第一个字节大于127(如49468(0xC13C) 0xC1>0x7F),看上去其编码应该是0x02 02 C1 3C, 但他的最高位为1,所以应该看成负数,最简单有效的办法是用前段零字节填充,即49468编码为0x02 03 00 C1 3C。
负数的编码并不简单,这个过程需要找到一个最小的256的幂,使它比要编码的数的绝对值还大,比如-1555进行编码,比1555还大的256的最小幂为256^2 = 65536 然后,把这两个数相加以得到2的补码表示形式,本例为63981。实际编码的整数是这个和。
因此在这个例子中,-1555的ASN.1编码为0x02 02 F9 ED,然后对整数的编码使用两个附加准则,以减小输出的大小。
第一个八位组的所有位和第二个八位组的第8位必须为:
    不全为1
    不全为0
整数解码相当容易,如果第一个最高位为0,被编码的值是正的且负载时绝对标量值。如果最高位为1,则被编码的值是负的且要从编码值中减去下一个最大的256的幂。
尤其要注意128和-128编码的不同,他们都赋值为0x80但正值需要0x00前缀来区分
值 

编码 

0x02 01 00 

0x02 01 01 

0x02 01 02 

127 

0x02 01 7F 

128 

0x02 02 00 80 

-1 

0x02 01 FF 

-128 

0x02 01 80 

-32768 

0x02 02 80 00 

1234567890 

0x02 04 49 96 02 D2 

4.3    位串类型
位串(BIT STRING)类型用来以可移植形式表示位数组。除了ASN.1头部之外,还有一个附加的头部用来表示填充数据。
这些位如下进行编码,将第一位放到第一个负载字节的最高位。下一位存储到第一个负载字节的第7位,以此类推。例如对位串{1,0,0,1,0,1,1,0}, 编码为0x8E
编码的第一个字节指定形成一个完整的字节所需填充的位数,例如位串{1,0,0,1}将转变为{1,0,0,1,0,0,0,0},其填充数量为4。如果位串为空,填充数为0。有效的填充长度范围为0-7.
负载的长度包括填充数字节和已编码的位。
位串{1,0,0,1}的编码为0x03 02 04 90 。应该注意负载的长度为0x02 而不是0x01,因为我们把填充字节作为负载的一部分。
解码器通过计算  8*负载长度 - 填充数 来得到存储输出所需要的位数。
4.4    八位位组串类型
除了八位位组串(OCT STRING)是保存字节(八位位组)数组之外,它和位串类型很相似,这种类型编码相当简单,像任何其他类型一样对头部进行编码,然后直接将八位位组赋值过去就可以了。
例如对八位位组串(FE, ED, 6A, B4)进行编码,首先存储类型为0x04 接着长度为0x04 然后就是字节本身 0xFE ED 6A B4
4.5    空类型
空(NULL)类型实际上是“占位符”, 它是含有空白选项的选择修改器所特有的。
例如考虑下列的序列(SEQUENCE)
    MyAccount ::= SEQUENCE{
        Name        IA5String,
        Group        IA5String,
        Credentials     CHOICE{
            rsaKey        RSAPublicKey,
            passwdHash    OCTST STRING,
            none        NULL
        },
        LastLogin     UCTIME,
        ...
    }
在这个结构中账号的证书应该是一个RSA密钥或者一个密码散列值或者什么都没有。如果不存在这个空类型,编码器必须选择两者之一,并且将其他类型指定为空类型。
空类型编码为0x05 00 它在DER编码中没有负载,然而,从技术上说,在BER编码中必须忽略它的负载。
4.6    对象标识符类型
对象标识符(OBJECT IDENTIFIER, OID)类型用层次的形式来表示标准规范,标识符树通过一个点分十进制符号来定义。这个符号以组织、子部分然后是标准的类型和各自的子标识开始。
例如MD5的散列算法的OID为1.2.840.113549.2.5
这个OID分为 iso (1)  member-body(2) US(840) rasdsi(113549) digestAlgorithm(2) md5(5)
当看到这个标识时,解码程序(并不是解码器本身)能够认识到这是个MD5散列算法。
因为这个原因,OID在公钥算法标准中很流行,它指出证书绑定了那种散列算法。但OID不仅仅局限于散列算法,同样也有公钥算法,分组算法和操作模式的OID。他们是一种高效且可移植的表示数据包中所选算法的形式,并不需要用户来指出算法类型的“魔幻解码”。
这种点分十进制是很容易理解的,但下面两种规则除外:
1、第一部分的范围必须是0<=x<=3
2、如果第一部分小于2,那么第二部分必须小于40
除此之外,其余部分可以使任何正的无符号数,他们的大小通常小于32位,但并不一定都是这样。
对各个部分的编码有点复杂,但仍然是可以容易理解的。前两部分如果定义为x和y,那么他们讲合成一个字40x+y,其余部分单独作为一个字进行编码。
每个字首先被分割为最少数量的没有头零数字的7位数字。这些数字以big-endian格式进行组织,并且一个接一个的组合成字节。除了编码的最后一个字节外,其他所有每个字节的最高位都为1。例如30 331分割成7位的数字之后为{1,108,123}
128^2*1 + 128^1*108 + 128^0*123 = 30331
把他们应用于MD5则为{42, 840, 113549, 2, 5}
进一步将其分割为带有最高位的7位数字,即{{0x24}, {0x86, 0x48}, {0x86, 0xF7, 0x0D},{0x02},{0x05}}
即 0x06 08 2A 86 48 86 F7 0D 02 05
4.7    序列和集合类型
序列(SEQUENCE)和单一序列(SEQUENCE OF)以及相应的集合(SET)和单一集合(SET OF)类型叫做“结构”类型或者叫简单容器。他们是一种用来把相关数据元素收集为一个独立的可解码元素的简单方法。
编码的内容应由ASN.1序列类型定义列表中的所有数据类型值的完全编码组成,并且按照他们出现的顺序进行编码,除非这些类型被可选(OPTIONAL)或者默认(DEFAULT)关键字修改器所引用。
结构化的意思是位6必须设置,这使得序列头字节的值变成由0x10到0x30。结构化编码是一种简单的嵌套编码。如下序列:
    User ::= SEQUENCE{
        ID    INTEGER,
        Active    BOOLEAN
    }
当对值{32, TRUE}编码时,首先给字节0x30表明这是一个结构化的序列。接着我们给出负载的长度,即整数和布尔类型的编码长度,共六个字节即0x06。然后开始结构化部分。将整数编码为0x02 01 20 布尔类型编码为0x01 01 FF因此整个编码为 0x30 06 02 01 20 10 01 FF。在ASN.1文档中,他们使用空白来表明编码的属性。
0x30 06     
        02 01 20 
        01 01 FF
如果使用下面的嵌套结构,这种符号表示特别有用。
Account ::= SQEUENCE{
    User SEQUENCE{
        Name    PrintableString,
        Group    PrintableString,
        Credential     SEQUENCE{
            PasswdHash     OCTST STRING,
            RSAKey    RSAPublickey    OPTIONAL
    },
    LastOn        UTCTIME,
    Valid        BOOLEAN
}
当给定序列{{"tom","users",{0x01 02 03 04 05 06 07 08}},"060416180000Z", TRUE},它将被编码为如下格式:
Account    0x30 2C
User        0x30 18
Name            13 03 74 6F 6D
Group            13 05 75 73 65 74 75
Credential            30 0A
PasswdHash            04 08 01 02 03 04 05 06 07 08
LastOn        17 0D 30 36 30 34 31 36 31 38 30 30 30 30 5A
Valid        01 01 FF
和openssl库一同安装的openssl命令行程序提供了一种把DER编码的文件转化为可阅读的处处的简单办法。
你可以和这个第三方已知的正确的进行比较。可以通过如下命令
    openssl    asn1parse -inform der -in $INFILE -i            ¥INFILE是文件名
(2)集合
集合石一种类似于序列的结构化类型,但他的头字节是0x31而不是0x30,且其成员编码的顺序并不是集合定义的顺序。严格来讲,对BER规则来说,发送者是可以判断出顺序的,意思是说,如果没有将集合的顺序事先发给接受者,那么,集合不能包含两个相同的类型。在DER编码规则中,是按照类型值的顺序进行升序排列,如果两个元素有相同的类型,则由已提交集合的原始顺序决定“加时赛规则‘,即这个重复类型的第一次出现为”胜者“。
对于前面的序列,其集合编码为
User ::= SET{
    ID    INTEGER,
    Active    BOOLEAN
}
当对值{32, TRUE}进行编码时,我们首先给出字节0x31来表示这是一个结构化的集合。其长度为6。根据DER规则,首先将他们的类型进行排序,因为布尔值类型为0x01,整数类型为0x02所以布尔值作为第一个。因此编码完成后的结构为0x31 06 01 01 FF 02 01 20
对于下面的集合:
User ::= SET{
    ID    INTEGER,
    Active    BOOLEAN,
    LogCount    INTEGER
}
在这个例子中,对实例{32, TRUE, 1023}编码以头字节0x31开始,长度为0A 接下来对布尔类型编码,ID和LogCount为相同类型,但是ID首次出现,所以接下来存储它的编码0x02 01 20 最后是LogCount编码 0x02 02 3F FF完整编码为0x31 0A 01 01 FF 02 01 20 02 02 3F FF
4.8    可打印字符串和IA5String类型
可打印字符串类型(PrintableString)和IA5String类型定义了一种独立于本地代码页和字符集定义,在任何平台上都可以将ASCII字符串编码为可读字符串的可移植方法。
可打印字符串编码对象是ASCII集合的一个有限子集,这个子集包括32(空格),39(单引号),40-41, 43-58, 61, 63, 以及65-122 这些范围之外的数值都是无效的并且应该报错,可打印字符串的意思是在不改变显示文字流的情况下,能够在大多数的终端下打印出来的字符。
IA5String类型编码对象时ASCII集合中的大多数值,它包括NUL,BEL,TAB,NL,LF,CR以及在32和126之间的ASCII码值,包括32和126。通常,在没有过滤的情况下使用TTY来显示IA5String是不安全的。因为它允许那些已编码的数据做一些诸如清屏,替换字符之类的事情。
可打印字符串的头字节为0x13
IA5String的头字节为0x16
例如hello world 编码为  0x13 0B 48 65 6D 6D 6F 20 57 6F 72 6D 64
4.9    世界协调时类型
世界协调时(UTCTIME)定义了一种相对于GMT时间的时间标准(以日期)编码。
从X.690的2002草案开始,所有的UTC编码都应该使用”YYMMDDHHMMSSZ“这种格式,分别表示年 月 日 时 分 秒
Z 遗留自初始UTCTIME,如果没有Z,那么就允许两种附加组"[+/-]hh 'mm'" 其中hh和mm分别为于GMT的时差和分差(正值或者负值)。如果有Z,则时间是以Zulu或者GMT时间表示的。
采用IA5String类型表示,如2013年7月4号 11:33:28 表示为
        0x17 0D 31 33 30 37 30 34 31 31 33 33 32 38 5A 


来自为知笔记(Wiz)


posted on 2013-11-11 15:49  dspeeding  阅读(2671)  评论(0编辑  收藏  举报