copy自_Iwin的百度空間: http://hi.baidu.com/peiwenlin/blog/item/be4ba62b454b21f4e6cd40c9.html
因圖片防盜鏈限制,部分圖片無法正常顯示,請參考上面的原文鏈接。
ASN.1:
高級數據描述語言,描述數據類型、結構、組織及編碼方法。包含語法符號和編碼規則兩大部分。SNMP使用ASN.1描述協議數據單元(PDU)和管理對象信息庫(MIB);
BER:
是 ASN.1中的基本編碼規則。描述具體的ASN.1對象如何編碼成比特流在網絡上進行傳輸。SNMP使用BER作為編碼方案,數據首先經過BER編碼,再 經由傳輸層協議(一般是UDP)發送往接收方。接收方在SNMP端口收到PDU后,經過BER解碼后,得到具體的SNMP操作數據。
SMI
是SNMP的描述方法。ASN.1功能很強大,但SNMP只用到其中一小部分,為了方便使用,對這部分內容做了描述,限定了范圍,這就是SMI。SMI由ASN.1的一個子集合和一部分自定義的類型、宏等組成。SMI是ASN.1的一個子集和超集。
MIB:
使用SMI中定義的類型和ASN.1中的基本類型進行對象描述,是一個使用SMI描述的管理信息庫。每一類關心的事件都有一組MIB,比如網絡接口有一棵MIB樹,TCP有一棵MIB樹,UDP也有一棵狀態樹。定義了數據格式、類型、順序、意義等;
PDU
是網絡中傳送的數據包,SNMP的協議數據單元。每一種SNMP操作物理上都對應一個PDU。PDU是基本的通信格式,使用ASN.1描述,使用BER編碼,通過傳書層協議傳送;
1.什么是ASN.1(抽象語法記法一)?
ASN.1是ITU-T的一個標準集,它用來編碼及表示通用數據類型,這些數據類型有可打印串值,八位位組串值,位串值,整數值以及用可移值方式組合而成 的其他類型序列值.簡單的說,ASN.1指定了以何種方式對非平凡的數據類型進行編碼,以便其他任何平臺及第三方工具都能夠解釋其內容.比如,字母a在一 些平臺以ASCII編碼為十進制數值97,而在其他非ASCII平臺上,可能會是另外的編碼.而ASN.1指定了一種編碼方式,在任何平臺上,字母a的編 碼都是統一的.
2.ASN.1語法.
ASN.1語法遵循傳統的巴科斯范式BNF風格.最基本的表達式如: Name ::= type . 表示為定義某個名稱為Name的元素,它的類型為type. 例如: MyName ::= IA5String . 表示為定義了一個名為MyName的元素或變量,其類型為ASN.1類型IA5String (類似于ASCII字符串).
2.1 ASN.1顯式值(Explict Value).
有些時候,我們需要定義一種ASN.1類型,它的子集元素包含預定義值. Name ::= type (Explict Value) . 顯式值(Explict Value).必須是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
}
2.2 ASN.1容器(container)
容器是值一個包含了其他相同或者不同類型元素的數據類型(例如序列值SEQUENCE或集合值SET類型).目的是為了組合一些復 雜的數據類型集.ASN.1規范定義了4種容器類型:序列,單一序列(SEQUENCE OF),集合和單一集合(SET OF).雖然它們意義不同,但是語法是一樣的.
Name ::= Container {Name Type [ Name Type...]} 方括號中的內容和容器的元素個數都是可選項.還可以進行嵌套定義.
例:
UserRecord ::= SEQUENCE {
Name SEQUENCE {
First IA5String,
Last IA5String
},
DoB UTCTIME
}
將其粗略的翻譯成C語言中的結構如下:
struct UserRecord {
struct Name {
char *First,
char *Last
};
time_t DoB;
}
將其粗略的翻譯成Object Pascal語言中的記錄如下(Object Pascal不支持嵌套記錄):
Type
Name = record
First : String;
Last : String;
end;
UserRecord = record
aName : Name;
DoB : DateTime;
end;
2.3ASN.1修改器
ASN.1定義了各種修改器,如可選(OPTIONAL),默認(DEFAULT),和選擇(CHOICE). 他們可以改變表達式的聲明.典型地用于定義一種要求編碼靈活,而定義又不繁瑣的類型.
<1>.可選(OPTIONAL)。顧名思義,其表示改變一個元素以便在編碼時它的類型是可選擇的.即編碼器可以忽略這個元素,解碼器不能假設它將出現.但當鄰接的兩個元素具有相同的類型時,會給解碼器帶來一些問題.
定義: Name ::= Type OPTIONAL
例如:
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密鑰的公鑰證書.
3.ASN.1數據類型
ASN.1針對廣泛的應用定義了多種數據類型,我們這里只討論跟密碼學應用相關的數據類型.我們將討論如下數據類型:
-
-
- 布爾型 (Boolean);
- 八位位組串 (OCTET String);
- 位串 (BIT String);
- IA5String;
- 可打印字符串 (PrintableString);
- 整數 (INTEGER);
- 對象標識符 (OBJECT Identifier, OID);
- 世界協調時 (UTCTIME);
- 空 (NULL);
- 序列,單一序列;
- 集合;
- 單一集合;
任何ASN.1編碼都是以兩個字節開始(或者八位位組,含有8個二進制位),不管什么類型,它們都是通用的.第一個字節是類型標識符,也包含一些修正位;第二各字節是長度.
3.1 ASN.1頭字節
頭字節(hearder byte)位于ASN.1編碼的開始,由3部分組成。如下圖:

<1>. 類別位。
類別位(classification bits)由兩位表示,用來描述數據將要解釋的上下文。
位8 |
位7 |
類別 |
0 |
0 |
通用(Universal) |
0 |
1 |
應用(Application) |
1 |
0 |
上下文特定(Context Specific) |
1 |
1 |
專用(Private) |
所有的類型中,通用類別最常用。
<2>. 結構化位。
結構化位(constructed bit)表示一個給定的編碼是否是相同類型的多種編碼的結構化。結構化元素是容器類型必需的,因為在邏輯上,它們只是其他元素的集合。
結構化元素有自己的頭字節和長度字節,之后是元素各個要素組件的單獨編碼。也就是說,這些要素組件是獨立地可解碼ASN.1數據類型。
嚴格的說,容器類是唯一允許使用結構化位的數據類型。這是因為對于其他數據類型,給定內容,只允許一種編碼。所以其他所有數據類型的結構化位都為0。
<3>. 原始類型。
ASN.1頭字節的低5位定義了32種ASN.1的原始類型(primitive type)
代碼 |
ASN.1類型 |
作用 |
1 |
布爾型 |
儲存布爾值 |
2 |
整數 |
儲存大整數 |
3 |
位串 |
存儲位數組 |
4 |
八位位串 |
存儲字節數組 |
5 |
空 |
預留位(例如在選擇修改器中) |
6 |
對象標識符 |
標識算法及協議 |
16 |
序列和單一序列 |
未分類元素的容器 |
17 |
集合和單一集合 |
已分類元素的容器 |
19 |
可打印字符串 |
ASCII編碼(忽略一些不可打印字符) |
22 |
IA5String |
ASCII編碼 |
23 |
世界協調時 |
以統一格式表示的時間 |
3.2 ASN.1長度編碼
根據編碼的實際長度,ASN.1定義了兩種長度編碼(length encoding)方法,長編碼和短編碼。

編碼字節的最高位代表的是短編碼還是長編碼;而低7位則形成一個長度立即數。
<1>. 短編碼。
在短編碼中,負載的長度必須小于128字節。長度立即數用來表示負載的長度。例如,對于一個長度為65 (0x41)的負載進行編碼,其長度編碼字節只需簡單的設置為0x41即可。因為其最高位是0,則編碼器可以判斷出這是短編碼,而且長度是65。
<2>. 長編碼。
在長編碼中,定義了附加的抽象數據來對長度進行編碼,它僅適用于所有長度為128字節或以上的負載。在這種模式下,長度立即數存儲的是為了表示負載長度所 需的字節數。這個長度必須以big-endian格式進行編碼。(其實big endian是指低地址存放最高有效字節(MSB),而little endian則是低地址存放最低有效字節(LSB)。)。
例如,為一個長度為47310 (0xB8CE)的負載進行編碼,因為它的長度大于127, 所以要采用長編碼方式。實際的長度需要兩個字節來表示。則,長度編碼字節為0x82; 然后用big-endian格式存儲的長度值為0xB8 0xCE。則全部長度編碼為 0x82 B8 CE。
3.3 ASN.1布爾類型
布爾編碼的負載或者是全0或者是全1的八位位組。頭字節以0x01開始,長度編碼字節為0x01,負載內容取決于布爾值的取值。
布爾值 |
編碼 |
False |
0x01 01 00 |
True |
0x01 01 FF |
3.4 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 02 F9 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 |
3.5 ANS.1位串類型
位串(BIT STRING)類型以可移植形式表示位數組.除了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 * 負載長度 - 填充數 來得到存儲輸出所需要的位數.
3.6 ASN.1八位位組串類型
八位位組串(OCTET STRING)是保存字節數組,它和位串類型(BIT STRING)很相似.這種編碼非常簡單,像其他類型一樣對頭部進行編碼,然后直接將八位位組復制過去即可.例如:對{FE, ED, 6A, B4}編碼;首先存儲類型0x04, 接著是長度0x04,然后是字節本身0xFE ED 6A B4; 完整的編碼為 0x04 04 FE ED 6A B4.
3.7 空類型
空(NULL)類型實際上是"占位符", 它是含有空白選項的選擇修改器所特有.例如:
MyAccount ::= SEQUENCE {
Name IA5String,
Group IA5String,
Credentials CHOICE{
rsaKey RSAPublicKey,
passwdHash OCTET STRING,
none NULL
}
}
在上面這個結構中,帳號的證書應該包含一個RSA密鑰或一個密碼散列值或什么都沒有.
空類型的編碼是 0x05 00.
3.8 ASN.1 對象標識符類型
對象標識符(OBJECT IDENTIFIER, 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.
3.9 ASN.1序列和集合類型
序列(SEQUENCE)和單一序列(SEQUENCE OF)以及相應的集合(SET)和單一集合(SET OF)類型叫做"結構"類型或簡單容器.它們是一種用來把相關數據元素收集為一個獨立的可解碼元素的簡單方法.
序列編碼有以下性質:
1. 編碼是結構化的.即頭字節的位6必須設置.
2. 編碼的內容是由ASN.1序列類型定義列表中的所有數據類型值的完全編碼所組成,并且按照它們出現的順序進行編碼,除非這些類型被可選(OPTIONAL)或默認(DEFAULT)關鍵字所引用.
例:考慮如下序列
User ::== SEQUENCE{
ID INTEGER,
Active BOOLEAN
}
當取值為{32,TRUE}時,編碼為 0x 30 06 02 01 20 01 01 FF} 在ASN.1文檔里,使用空格來表示編碼的屬性.
0x30 06
02 01 20
01 01 FF
3.10 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 6D 6D 6F 20 57 6F 72 6D 64.
3.11 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.