ASN.1概述及資料型別詳解
1.ASN.1概述
抽象語法表示(標記)ASN.1(Abstract Syntax Notation One )一種資料定義語言,描述了對資料進行表示、編碼、傳輸和解碼的資料格式。網路管理系統中的管理資訊庫(MIB)、應用程式的資料結構、協議資料單元(PDU)都是用ASN.1定義的。
ASN.1優點:
通過如下的獨立:
⑴獨立於機器;
⑵獨立於程式語言;
⑶獨立於應用程式的內部表示,用一種統一的方式來描述資料結構。
解決如下的不同:
⑴程式語言之間資料型別不同
⑵不同機器平臺之間資料的儲存方式不同
⑶不同種類的計算機內部資料表示不同
比如:IBM為EBCDIC,其它為ASCⅡ;Intel的晶片從右到左計數字節數,而Motorola的晶片則從左到計數字節數。
在任何需要以數字方式傳送資訊的地方,都可使用ASN.1傳送各種形式資訊。包括音訊、視訊、圖片、資料等。由於各種系統對資料的定義並不完全相同, 這自然給利用其它系統的資料造成了障礙。表示層就擔負了消除這種障礙的任務。表示層如同應用程式和網路之間的翻譯官:主要解決使用者資訊的語法表示問題,即提供統一的、格式化的表示和轉換資料服務。資料的壓縮、解壓、加密、解密都在該層完成。
2.ASN.1語法.
ASN.1語法遵循傳統的巴科斯正規化BNF風格.最基本的表示式如: Name ::= type . 表示為定義某個名稱為Name的元素,它的型別為type. 例如: MyName ::= IA5String . 表示為定義了一個名為MyName的元素或變數,其型別為ASN.1型別IA5String (類似於ASCII字串).
ASN.1顯式值(Explict Value).
有些時候,我們需要定義一種ASN.1型別,它的子集元素包含預定義值. Name ::= type(Explict Value) . 顯式值(ExplictValue).必須是ASN.1型別允許選擇的值,而且也必須是元素所允許的值.例: MyName ::=IA5String (Tom) 表示MyName是字串Tom的IA5String編碼.又例如:MyName ::= IA5String(Tom|Joe) 表示字串的值既可以是Tom, 也可以是Joe.
這種語法的使用是為了擴充套件確定的解碼器.例:
PublicKey::= SEQUENCE {
KeyType BOOLEAN(0),
Modulus INTEGER,
PubExponent INTEGER
}
PrivateKey ::= SEQUENCE {
KeyType BOOLEAN(1)
Modulus INTEGER,
PubExponent INTEGER,
PrivateExponent INTEGER
}
ASN.1容器(container)
容器是值一個包含了其他相同或者不同型別元素的資料型別(例如序列值SEQUENCE或集合值SET型別).目的是為了組合一些複雜的資料型別集.ASN.1規範定義了4種容器型別:序列,單一序列(SEQUENCEOF),集合和單一集合(SET OF).雖然它們意義不同,但是語法是一樣的.
Name ::= Container {Name Type [ Name Type...]} 方括號中的內容和容器的元素個數都是可選項.還可以進行巢狀定義.
例:
UserRecord::= SEQUENCE {
Name SEQUENCE {
First IA5String,
Last IA5String
},
DoB UTCTIME
}
將其粗略的翻譯成C語言中的結構如下:
structUserRecord {
struct Name {
char *First,
char *Last
};
time_t DoB;
}
將其粗略的翻譯成ObjectPascal語言中的記錄如下(ObjectPascal不支援巢狀記錄):
Type
Name = record
First : String;
Last : String;
end;
UserRecord = record
aName : Name;
DoB : DateTime;
end;
ASN.1修改器
ASN.1定義了各種修改器,如可選(OPTIONAL),預設(DEFAULT),和選擇(CHOICE). 他們可以改變表示式的宣告.典型地用於定義一種要求編碼靈活,而定義又不繁瑣的型別.
<1>.可選(OPTIONAL)。顧名思義,其表示改變一個元素以便在編碼時它的型別是可選擇的.即編碼器可以忽略這個元素,解碼器不能假設它將出現.但當鄰接的兩個元素具有相同的型別時,會給解碼器帶來一些問題.
定義: Name ::= TypeOPTIONAL
例如:
Float::= SEQUENCE {
Exponent INTEGER OPTIONAL,
Mantissa INTEGER,
Sign BOOLEAN
}
當解碼器讀取這個結構時,在它看來第一個整數(INTEGER)可能是Exponent,也有可能認為是Mantissa. 一般建議不使用這種方式定義結構.
<2>.預設(DEFAULT).預設修改器允許容器包含預設值.如果待編碼的資料值等同於它的預設值,那麼它將在傳送的資料流中被忽略.例如:
Command::= SEQUENCE {
Token IA5String(NOP) DEFAULT,
Parameter INTEGER
}
如果編碼器把Token看成是代表字串NOP,那麼序列將按照定義的那樣編碼為:
Command ::= SEQUENCE {
Parameter INTEGER
}
<3>.選擇(CHOICE). 選擇修改器允許一個元素在給定的例項中可以有多個可能值.實質上說,解碼器將嘗試所有期望的解碼演算法,直到有一個型別符合為止.當一個複雜的容器中包含其他容器時,時候選擇器就十分有用了.例如:
UserKey::= SEQUENCE {
Name IA5String,
StartDate UTCTIME,
Expire UTCTIME,
KeyData CHOICE {
ECCKey ECCKeyType,
RSAKey RSAKeyType
}
}
上例簡單的允許ECC也允許RSA金鑰的公鑰證書.
任何ASN.1編碼都是以兩個位元組開始(或者八位位組,含有8個二進位制位),不管什麼型別,它們都是通用的.第一個位元組是型別識別符號,也包含一些修正位;第二各位元組是長度.基本型別如下:
.布林型(Boolean);
.八位位組串 (OCTETString);
.位串 (BITString);
.IA5String;
.可列印字串(PrintableString);
.整數 (INTEGER);
.物件識別符號 (OBJECTIdentifier, OID);
.世界協調時(UTCTIME);
.空 (NULL);
.序列,單一序列;
.集合;
.單一集合;
ASN.1布林型別
布林編碼的負載或者是全0或者是全1的八位位組。頭位元組以0x01開始,長度編碼位元組為0x01,負載內容取決於布林值的取值。
布林值 |
編碼 |
False |
0x01 01 00 |
True |
0x01 01 FF |
ASN.1整數型別
整數型別表示一個有符號的任意精度的標量,它的編碼是可移植,平臺無關的。
正整數的編碼比較簡單。每個位元組表示的最大整數是255 (0xFF), 儲存的實際數值分成位元組大小的數字,並且以big-endian格式儲存。
八位位組{Xk,Xk-1,...., X0}將以遞減的順序從Xk到X0進行儲存.編碼規定正整數的第一個位元組的最高位必須是0,即Xk的最高為必須是0,為1的話則為負數.例如: x = 49468= 193 * 256 + 60 = 0xC1 * 0x FF + 0x3C; 即X1=0xC1, X0= 0x3C. 按正常規定,編碼應該是 0x02 02 C1 3C, 但是X1的最高位是1, 應該被看成負數.最簡單的方法是用前端零位元組進行填充.編碼變為 0x02 02 00 C1 3C.
負整數的編碼有些複雜.要先找到一個最小的256的冪,使它比要編碼的負數的絕對值還要大.例如:x = - 1555; 被1555大的256的最小的冪是256^2 = 65536; 然後將這個數跟負數相加以得到2的補碼. 65536 + (-1555) = 63981 = 0xF9 * 0xFF + 0x ED. 則編碼為 0x02 02F9 ED.
以下是一些常用整數編碼的例子.
值 |
編碼 |
0 |
0x02 01 00 |
1 |
0x02 01 01 |
2 |
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 |
ANS.1位串型別
位串(BITSTRING)型別以可移植形式表示位陣列.除了ASN.1頭部兩個位元組之外,還有一個附加的頭部用來表示填充資料(通常是一個位元組,因為填充是為了形成一個完整的位元組).編碼規則:位串的第一位放到第一個負載位元組的第8位;位串的第二位放到第一個負載位元組的第7位; 依此類推.填充滿第一個負載位元組,就繼續填充第二個負載位元組.如果最後一個負載位元組未被填充滿,空的位用0來填充, 0的個數存放到頭部用來表示填充資料的那個位元組裡.
下面舉例說明:
有一個位串{1,0,0,0,1,1,1,0,1,0,0,1},開始填充負載位元組.第一個位元組填充後為10001110= 0x 8E; 第二個位元組填充後為10010000 = 0x90, 低位4個0為填充的空位.則,負載為2個位元組加上表示填充0個數的一個位元組0x04總共3個位元組.則完整的編碼為:0x03 03 04 8E 90.
解碼器通過計算8 * 負載長度 - 填充數來得到儲存輸出所需要的位數.
ASN.1八位位組串型別
八位位組串(OCTET STRING)是儲存位元組陣列,它和位串型別(BIT STRING)很相似.這種編碼非常簡單,像其他型別一樣對頭部進行編碼,然後直接將八位位組複製過去即可.例如:對{FE, ED, 6A, B4}編碼;首先儲存型別0x04, 接著是長度0x04,然後是位元組本身0xFE ED 6A B4; 完整的編碼為 0x04 04 FE ED 6A B4.
空型別
空(NULL)型別實際上是"佔位符", 它是含有空白選項的選擇修改器所特有.例如:
MyAccount ::= SEQUENCE {
Name IA5String,
Group IA5String,
Credentials CHOICE{
rsaKey RSAPublicKey,
passwdHash OCTET STRING,
none NULL
}
}
在上面這個結構中,帳號的證書應該包含一個RSA金鑰或一個密碼雜湊值或什麼都沒有.
空型別的編碼是 0x05 00.
ASN.1 物件識別符號型別
物件識別符號(OBJECTIDENTIFIER, OID)型別用層次的形式來表示標準規範.識別符號樹通過一個點分的十進位制符號來定義,這個符號以組織,子部分然後是標準的型別和各自的子識別符號開始.
例如:MD5的OID 是1.2.840.113549.2.5 表示為"iso(1) member-body (2) US (840) rsadsi(113549) digestAlgorithm(2) md5 (5)", 所以當解碼程式看到這個OID時,就知道是MD5雜湊.
OID在公鑰演算法標準中很流行,它指出證書綁定了哪種雜湊演算法. 同樣,也有公鑰演算法,分組演算法,和操作模式的OID. 它們是一種高效且可移植的表示資料包中所選演算法的形式.
對OID的編碼規則:
前兩部分如果定義為x.y, 那麼它們將合成一個字40*x + y, 其餘部分單獨作為一個位元組進行編碼.
每個字首先被分割為最少數量的沒有頭零數字的7位數字.這些數字以big-endian格式進行組織,並且一個接一個地組合成位元組. 除了編碼的最後一個位元組外,其他所有位元組的最高位(位8)都為1.
舉例: 30331 = 1* 128^2 + 108 * 128 + 123 分割成7位數字(0x80)後為{1,108,123}設定最高位後變成{129,236,123}.如果該字只有一個7位數字,那麼最高為0.
MD5 OID的編碼:
1. 將1.2.840.113549.2.5轉換成字陣列 {42, 840, 113549, 2, 5}.
2. 然後將每個字分割為帶有最高位的7位數字,{{0x2A},{0x86,0x48},{0x86,0xF7,0x0D},{0x02},{0x05}}.
3. 最後完整的編碼為 0x06 08 2A 86 48 86 F7 0D 02 05.
ASN.1序列和集合型別
序列(SEQUENCE)和單一序列(SEQUENCE OF)以及相應的集合(SET)和單一集合(SET OF)型別叫做"結構"型別或簡單容器.它們是一種用來把相關資料元素收集為一個獨立的可解碼元素的簡單方法.
序列編碼有以下性質:
1. 編碼是結構化的.即頭位元組的位6必須設定.
2. 編碼的內容是由ASN.1序列型別定義列表中的所有資料型別值的完全編碼所組成,並且按照它們出現的順序進行編碼,除非這些型別被可選(OPTIONAL)或預設(DEFAULT)關鍵字所引用.
例:考慮如下序列
User ::== SEQUENCE{
ID INTEGER,
Active BOOLEAN
}
當取值為{32,TRUE}時,編碼為 0x 3006 02 01 20 01 01 FF} 在ASN.1文件裡,使用空格來表示編碼的屬性.
0x30 06
02 01 20
01 01 FF
ASN.1可列印字串和IA5String型別
可列印字串(PrintableString)和IA5String型別定義了一種獨立於原生代碼頁和字符集定義,在任何平臺上都可以將ASCII字串編碼為可讀字串的可移植方法.
可列印字串物件是ASCII集合的一個有限子集,這個子集包括32,39,40~41,43~58,61,63以及65~122.
IA5String型別的編碼物件是ASCII集合中的大多數.包括NULL,BEL,TAB,NL,LF,CR以及32~126.
可列印字串和IA5String的編碼和八位位組串相似.可列印字串的頭位元組是0x13, IA5String的是0x16. 例如:"Hello World"的編碼為0x13 0B 48 65 6D6D 6F 20 57 6F 72 6D 64.
ASN.1世界協調時型別
世界協調時(UTCTIME)定義了一種相對GMT時間的標準時間(以日期)編碼.它使用"YYMMDDHHMMSSZ"的格式分別表示年,月,日,時,分,秒. 其中"Z"是遺留自初始的UTCTIME.如果沒有"Z",就允許兩種附加組"[+/-]hh 'mm'",其中"hh"和"mm"分別為與GMT的時差和分差. 如果有"Z",則時間是以Zulu或GMT時間表示.
字串的編碼按照IA5String編碼規則進行轉換(ASCII字符集),其頭位元組為0x17而不是0x16. 例如:
July 4,2003 at 11:33 and 28 seconds編碼為"030704113328Z",再編碼0x17 0D 30 33 30 37 30 34 31 31 33 33 32 38 5A.