在工作時常會遇到的幾個資料,certificate(X.509 DER),signed image(PKCS#7 DER), snmp packet
這些資料都用了 ASN.1 的 DER 格式。雖然有很多工具能把這些值解出來,但還是希望能靠著hexdump和小算盤就解出資料,有時也想要對資料做一些處理
那就只好硬吞spec,看它到底是如何編碼的。不過因為英文不好,spec看不太懂,有時候反而覺得程式碼看起來還比較輕切,於是一邊看spec,搭配tcl的ASN package來了解編碼方式
這邊紀錄一些常看到的資料類型,了解怎麼編碼解碼之後,就可以編輯certificate,SNMP 封包,當然直接使用現成的程式碼移植擴展到TCL是最方便的,但我不是每次都能順利地把
程式碼轉成TCL用的格式,在還沒編譯出TCL的dll時,就是純用TCL硬上
參考資料
ITU-T X.680
ITU-T X.690
http://www.obj-sys.com/asn1tutorial/asn1only.html
DER編碼把每一筆資料分三部分 Identifier octets, Length octets, Contents octets
Identifier octets
Identifier octets 存放的值就是ASN.1 的tag,tag可以分成4個Class,這可以用前2 bits來編碼,再用 bit6 把tag分成Primitive(0)或 Constructed(1)
Class | Bit 8 | Bit 7 |
---|---|---|
UNIVERSAL | 0 | 0 |
Application | 0 | 1 |
Context-specific | 1 | 0 |
Private | 1 | 1 |
8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
---|---|---|---|---|---|---|---|
Class | P/C | Tag Number |
Tag class 使用原則(T-REC-X.680-200811-I -> G.2.12 Tagged)
1.UNIVERSAL : Universal class tags are used only within this Recommendation | International Standard.
應該就是指X.680裡提到的那些 type, 例如: Boolean(0x01), Integer(0x02), Bitstring(0x03) ...
2.Application : A frequently encountered style for the use of tags is to assign an application class tag precisely once in the
entire specification, using it to identify a type that finds wide, scattered, use within the specification.
例如: RFC2578 SMIv2 定義了一些snmp用到的type, IpAddress(0x40), Unsigned32(0x42), TimeTicks(0x43) ...
可以抓snmp封包驗證
3.Context-specific : Context-specific tagging is frequently applied in an algorithmic manner to all components of a SET, SEQUENCE, or CHOICE.
例如: RFC1157 SNMP 定義的SNMP PDU(protocol data unit), GetRequest(0xA0), GetNextRequest(0xA1), GetResponse(0xA2) ...
4.Private : Private class tagging should normally not be used in internationally standardized specifications (although this
cannot be prohibited).
還沒看過這類的Tag
Tag 編碼方式
這個 byte 被保留了 3 bits 用來區分類型,只剩 5 bits 來表示 tag number,通常應該夠用(32種),不過這套編碼都預留好了不夠用時的編碼方式,所以31 (111112)這個值保留做其他用途,
所以1個byte 能表示的 tag number只有0~30
當tag number >= 31 時的編碼方式 : 第一個 byte 前3 bit (8~6) 依照 class, primitive 編碼, bit5~bit1 全部填 1 (111112 被保留在這邊使用了)
接下來就看tag number大小來決定,先轉成二進位,在前方(MSB)補0,直到bitstring為7的整數倍,再以 7 bits為一組拆開,若是最後一組 bit 8填0,否則填1
例如: 建一組 Tag, calss=Application 且為 primitive, tag number = 200
第一 byte => 01011111 => 0x5F
200=0xC8 => 11001000 => 0000001 1001000 => 10000001 01001000 => 0x8148
會被編成 0x5F8148
tcllib有 asn package 可以處理編碼和解碼
package require asn # 用來看ASN.1編碼的結果,以下範例都當作有這個proc以及以載入asn package proc hexdump {data} { binary scan $data H* hex puts "[string toupper [regsub -all .. $hex "& "] ]\n" } # tag encoding # syntax: ::asn::asnTag tagnumber ?class? ?tagstyle? # class: U,A,C,P 預設值為U # tagstyle: P,C 預設值為P puts "Test 1: encoding UNIVERSAL Primitive 2" hexdump [::asn::asnTag 2 U P] puts "Test 2: encoding Application Primitive 200" hexdump [::asn::asnTag 200 A P] puts "Test 3: encoding Context Constructed 999" hexdump [::asn::asnTag 999 C C] # tag decoding # syntax ::asn::asnPeekTag data_var tag_var tagtype_var constr_var # 這個指令的參數給的是變數名稱,執行後變數值會被更新,類似C 指標的概念 puts "Test 4: decoding 0x02" set data [binary format H* 02] ::asn::asnPeekTag data tag class constr puts "tag=$tag, class=$class, constr=$constr\n" puts "Test 5: decoding 0x5F8148" set data [binary format H* 5F8148] ::asn::asnPeekTag data tag class constr puts "tag=$tag, class=$class, constr=$constr\n" puts "Test 6: decoding 0xBF8767" set data [binary format H* BF8767] ::asn::asnPeekTag data tag class constr puts "tag=$tag, class=$class, constr=$constr\n"
Length octets
用來表示資料(Contents octets)長度,不包含Identifier octets 和 Length octetsLength 編碼方式
先將 Length 可以分成2種格式
1.definite form
2.indefinite form
格式選用原則
1. primitive 類的使用 definite form
2. constructed 類並且所有資料是立即可用的可任選一種
3. constructed 類並且不是所有資料是立即可用的使用indefinite form
definite form 又可分成2種編碼方式,可自由選擇
1. short form : 最簡單的方式,資料難容幾個byte就填多少,範圍0~127
2. long form : 用多個 bytes 來描述,第一個 byte bit8 (MSB)填1,bit7~bit1 用來描述後面用幾個 bytes 來描述長度,但不能全為 1 (0xFF),0xFF 先保留未來或許會用到
例如: length=201,
201=0xC9 , 1 byte,所以第一 byte 是 0x81 => 最後就是編成 0x81c9
indefinite form: 固定為 0x80,因為這種格試是用在資料長度未定的方式,為了知道資料何時結束,當最後一筆資料出現時最後要補上End-of-contents
End-of-contents: tag:0x00 length:0x00 value:Absent
目前還沒遇過這種類型的資料,tcl的asn package也只支援definite form編碼
package require asn # asn package 裡 length 的 encoding/decoding 通常不需要使用者去管它 # 在這個package裡 encoding/decoding時會順便處理掉 # length encoding # syntax: ::asn::asnLength len puts "Test 1 length=100" hexdump [::asn::asnLength 100] puts "Test 2 length=999" hexdump [::asn::asnLength 999] # length decoding # syntax: ::asn::asnGetLength data_var length_var # 這個指令解碼完後會將data_var裡length的部分移除,將取得的結果存到length_var # 這個package解碼的指令大多數都會將原資料改變 puts "Test 3 decoding 0x64" set data [binary format H* 64] ::asn::asnGetLength data leng puts length=$leng\n puts "Test 4 decoding 0x8203E7" set data [binary format H* 8203E7] ::asn::asnGetLength data leng puts length=$leng\n
Contents octets
Contents octets: 資料內容,根據不同類型有不同編碼方式,這邊只記錄常用和tcl asn package有的幾種類型Type | Class | Constructed | Tag number | Tag encoding |
---|---|---|---|---|
Boolean | UNIVERSAL | 0 | 1 | 0x01 |
Integer | UNIVERSAL | 0 | 2 | 0x02 |
Bitstring | UNIVERSAL | 0 | 3 | 0x03 |
Octetstring | UNIVERSAL | 0 | 4 | 0x04 |
Null | UNIVERSAL | 0 | 5 | 0x05 |
Object identifier | UNIVERSAL | 0 | 6 | 0x06 |
Sequence | UNIVERSAL | 1 | 16 | 0x30 |
Sequence of | UNIVERSAL | 1 | 16 | 0x30 |
Set | UNIVERSAL | 0 | 17 | 0x31 |
Set of | UNIVERSAL | 0 | 17 | 0x31 |
UTCTime | UNIVERSAL | 0 | 23 | 0x17 |
Boolean: Tag: 0x01 Length: 1 byte
value 為 False 時 0x00, True 時為 0xFF
package require asn # encoding 指令會自動標上tag和length,不需使用者另外處理 # decoding 指令會將原資料的一組(tag,length,content)移除 # 這裡的解碼範例都是假設以之資料類型,一般應該使用asnPeekTag 解出tag再套用相對應的指令 puts "Test 1: encoding Boolean 0" hexdump [::asn::asnBoolean 0 ] puts "Test 2: encoding Boolean 1" hexdump [::asn::asnBoolean 1 ] puts "Test 3: decoding 0x0101000101FF" set data [binary format H* 0101000101FF] puts -nonewline "before decoding data ="; hexdump $data puts "decoding 1st" ::asn::asnGetBoolean data bool puts "bool=$bool\n" puts -nonewline "after decoding data ="; hexdump $data puts "decoding 2nd" ::asn::asnGetBoolean data bool puts "bool=$bool\n" puts -nonewline "after decoding data ="; hexdump $data
Integer: Tag:
value: 儲存有號整數,以2的補數表示,並且以最少的bytes數表示,最少 bytes 也就是說,當資料是多 bytes 時,第 1 byte 和第 2 byte 的 MSB
不會同時為 0(正數) 或同時為 1(負數)
package require asn # 2 bytes 整數令人懷念的2個值 puts "Test 1: encoding Integer 32767" hexdump [::asn::asnInteger 32767 ] puts "Test 2: encoding Integer -32768" hexdump [::asn::asnInteger -32768 ] # 當數值超過 8 bytes 時,要用 bignum package require math::bignum puts "Test 3: encoding bigInteger 9223372036854775808 (0x008000000000000000)" set val [::math::bignum::fromstr 9223372036854775808] hexdump [::asn::asnBigInteger $val ] puts "Test 4: decoding 0x02027FFF" set data [binary format H* 02027FFF] ::asn::asnGetInteger data val puts val=$val\n puts "Test 5: decoding 0x02028000" set data [binary format H* 02028000] ::asn::asnGetInteger data val puts val=$val\n # asn package 0.8.3 解bignum有bug,要更新到0.8.4 puts "Test 6: decoding 0x0209008000000000000000" set data [binary format H* 0209008000000000000000] ::asn::asnGetBigInteger data val # 會存成bignum格式 puts val=$val # 要另外轉換回來 set val [::math::bignum::tostr $val] puts val=$val
Bitstring: Tag: 0x03 Length: n byte (n>0)
value: 有兩種編碼方式,一次寫完所有 bits 和分批送 bits 兩種,這邊只記錄第一種
一開始先調整長度,讓長度變成整數 bytes ,做法是在尾端(LSB)補 0,共有8總可能(0~7)
然後在前端補上 1 byte,說明補了幾個 0
例如: 0101
先補0 => 01010000=>0x50
補了4個0
在前面補1 byte => 0x0450
package require asn # 這範例可看出同樣是0x50,但是資料第一byte不同,所表示的bitstring也不同 puts "Test 1: encoding BitString 0101" hexdump [::asn::asnBitString 0101] puts "Test 2: encoding BitString 01010000" hexdump [::asn::asnBitString 01010000] puts "Test 3: decoding 0x03020450" set data [binary format H* 03020450] ::asn::asnGetBitString data val puts val=$val\n puts "Test 4: decoding 0x03020050" set data [binary format H* 03020050] ::asn::asnGetBitString data val puts val=$val\n
Octetstring: Tag: 0x04 Length: n byte (n>=0)
value: 直接填入即可
package require asn puts "Test 1: encoding OctetString \"5566\"" hexdump [::asn::asnOctetString 5566] puts "Test 2: encoding OctetString 0x55 0x66" hexdump [::asn::asnOctetString \x55\x66] # 這個type可以存任意值,若要binary data存取,解碼完可用binary scan做進一步運算 puts "Test 3: decoding 0x040435353636" set data [binary format H* 040435353636] ::asn::asnGetOctetString data val puts val=$val\n puts "Test 4: decoding 0x04025566" set data [binary format H* 04025566] ::asn::asnGetOctetString data val puts val=$val binary scan $val H* valhex puts "val(hex)=$valhex"
Null: Tag: 0x05 Length: 1 byte
value: NULL,這個 type 只有 type 和 length,length 值為 0x00
目前只在snmp封包看過這種type,snmpget的vbind value就是Null
package require asn # Null 的編碼解碼只是補上或移除 0x0500 puts "Test 1: encoding Null" hexdump [::asn::asnNull] puts "Test 2: decoding 0x0500" puts "before decoding data length = [string length $data]" ::asn::asnGetNull data puts "after decoding data length = [string length $data]"
Object identifier: Tag: 0x06 Length: n byte
value:把 oid 的每一個數值轉成 binary 格式,有兩個規則
第一,oid前兩碼混在一起算 X.Y. ... X,Y編成 (X*40)+Y. oid 前2碼範圍 X:0~2, Y:0~39
第二,因為值有可能超過 1 byte,所以把 bit 8 (MSB)拿來當 flag , bit8 == 1 表示這個值還有下一個 byte,一直串下去到足夠表示這個值為止,最後的 byte bit8 為 0
同樣以最短編碼為原則,所以不會出現 0x80 這種值
例如: 1.3.6.1.4.1.4491
1*40+3=43,前兩碼編成0x2B
接著 6.1.4.1 編成 0x06 0x01 0x04 0x01
最後 4491 = 0x118B = b 00010001 10001011 每個byte的MSB要當flag,每7 bit 就要進位
=>00 0100011 0001011 (7bits一組分類前面是00可以省掉,不然就要補0)(轉二進位看就是7 bits一組切一切就好,用算的就是 mod 128 和/ 128 一直算)
=> 10100011 00001011=0xA30B
所以 1.3.6.1.4.1.4491 會編成 0x2B06010401A30B
package require asn # 這兩個處理oid的指令不處理 "." ,使用者要自己加入或移除 puts "Test 1: encoding oid 1.3.6.1.4.1.4491" hexdump [::asn::asnObjectIdentifier [split 1.3.6.1.4.1.4491 .]] puts "Test 2: decoding 0x06072B06010401A30B" set data [binary format H* 06072B06010401A30B] ::asn::asnGetObjectIdentifier data oid puts oid=[join $oid .]
Sequence / Sequence of : Tag: 0x30 Length: n byte
value: Sequence 和 Sequence of 的值是 asn.1 定義的這些值,並且排列是有順序的
兩者不同的是Sequence包含的值可以有多種type,Sequence of包含的值只有一種type,在der編碼下似乎不用特別去區分
package require asn #encoding時不會檢查內容是否符合der格式,所以每以筆資料都要先處理過 puts "Test 1: encoding Sequence { integet 100 , bitstring 01010101}" hexdump [::asn::asnSequence [::asn::asnInteger 100] [::asn::asnBitString 01010101]] #decoding後,sequence裡的每一筆資料都要在另外解碼 puts "Test 2: decoding 0x300702016403020055" set data [binary format H* 300702016403020055] ::asn::asnGetSequence data val binary scan $val H* valhex puts val(hex)=$valhex
Set / Set of : Tag: 0x31 Length: n byte
value: 和Sequence 和 Sequence of 類似,Set / Set of 是用來包沒有順序的值
package require asn #基本上和sequence的差別只有tag不同 puts "Test 1: encoding Set { integet 100 , bitstring 01010101}" hexdump [::asn::asnSet [::asn::asnInteger 100] [::asn::asnBitString 01010101]] puts "Test 2: decoding 0x310702016403020055" set data [binary format H* 310702016403020055] ::asn::asnGetSet data val binary scan $val H* valhex puts val(hex)=$valhex
UTCTime: Tag: 0x17 Length: 13 byte
value:用來標示時間,格式是 YYMMDD000000Z" , 年月日時分秒,24小時制,0:0:0記成000000而不是240000
年的編碼規定沒找到,在openssl裡是小於50的加100,也就是範圍是1950~2049
package require asn puts "Test 1: encoding UTCTime 140719000000Z" hexdump [::asn::asnUTCTime 140719000000Z] puts "Test 2: decoding UTCTime 0x170D3134303731393030303030305A" set data [binary format H* 170D3134303731393030303030305A] ::asn::asnGetUTCTime data val puts val=$val\n # UTCTime encoding/decoding command不檢查內容格式是否正確,使用者須自行判斷 puts "Test 3: encoding UTCTime aAbBcCdDeEfFZ" hexdump [::asn::asnUTCTime aAbBcCdDeEfFZ]
"第一 byte => 0101111 => 0x5F" 似乎少打一個 1 --> 01011111
回覆刪除謝謝指正
刪除Integer: Tag: 0x01 Length: n byte (n>0)
回覆刪除Integer的Tag應該是0x02?
謝謝指正,是0x02
刪除