目 錄版本記錄 1目 錄 1第1章 概述 31.1 背景 31.2 ASN.1概念 31.3 TAG 4第2章 開發工具 42.1 開發庫 42.2 輔助工具 5第3章 javaAsn1Compiler 63.1 定義ASN.1描述文件 63.2 生成java代碼 63.2.1 代碼生成 63.2.2 代碼案例 63.2.3 編解碼 73.3 總結 8第4章 bouncycastle 84.1 編碼 84.1.1 確定編碼的文件格式 84.1.2 構造ASN1映射類 94.2 解碼 114.2.1 定義實體結構 114.2.2 定義實體解析類 114.2.3 外部調用接口 12第1章 概述1.1 背景系統與充值平臺的接口是文件的方式,充值平臺將文件內容以ASN.1方式進行編碼,系統需要根據ASN.1協議進行解碼。關于ASN.1開發的資料,網上資料非常少,特別是涉及到具體的語言,如java,資料、案例及第三方庫更是少之又少。從無到有是很困難的,為了防止后期其他系統還需要做類似接口,將其記錄為文章以便后查,文章會以充值接口作為案例進行介紹。1.2 ASN.1概念在電信和計算機網絡領域,ASN.1(Abstract Syntax Notation one) 是一套標準,是描述數據的表示、編碼、傳輸、解碼的靈活的記法。它提供了一套正式、無歧義和精確的規則以描述獨立于特定計算機硬件的對象結構。ASN.1 包括幾個標準化編碼規則,如基本編碼規則(BER) -X.209 、規范編碼規則(CER)、識別名編碼規則(DER)、壓縮編碼規則(PER)和 xml編碼規則(XER)。這些編碼規則描述了如何對 ASN.1 中定義的數值進行編碼,以便用于傳輸,而不管計算機、編程語言或它在應用程序中如何表示等因素。ASN.1 的編碼方法比許多與之相競爭的標記系統更先進,它支持可擴展信息快速可靠的傳輸 — 在無線寬帶中,這是一種優勢。1984年,ASN.1 就已經成為了一種國際標準,它的編碼規則已經成熟并在可靠性和兼容性方面擁有更豐富的歷程?! 『啙嵉亩M制編碼規則(BER、CER、DER、PER,但不包括 XER)可當作更現代 XML 的替代。然而,ASN.1 支持對數據的語義進行描述,所以它是比 XML 更為高級的語言?! SN.1 的描述可以容易地被映射成 C 或 C++ 或 Java 的數據結構,并可以被應用程序代碼使用,并得到運行時程序庫的支持,進而能夠對編碼和解碼 XML 或 TLV 格式的,或一種非常緊湊的壓縮編碼格式的描述。同時,ASN.1也是一種用于描述結構化實體的結構和內容的語言。如:使用ASN.1語法可以這樣定義一個類:Report ::= SEQUENCE {author OCTET STRING, title OCTET STRING, body OCTET STRING, biblio INTEGER } 詳見《ASN.1編碼規則詳解》:http://wenku.baidu.com/view/33ba22d276eeaeaad1f3304c.html 注:在進行ASN1開發前,需要先閱讀上述文章,了解其中的一些基本概念。1.3 TAG由于TAG在ASN1中非常之重要,而且在對文件進行解析時就是因為TAG的問題導致浪費了很多時間,因此這里對其單獨介紹,不過只是提出概念,詳細描述還需參見相關規范。TAG是對ASN1協議中每個數據域的標識,通過2.2的截圖可以看到,每個結點名稱后面都有一個數字,這個就TAG值。TAG有可分為四大類:UNIVERSAL、Context、PRivate、application。詳見:http://wenku.baidu.com/view/33ba22d276eeaeaad1f3304c.html第2章 開發工具通常情況下,如果使用該協議進行交互,雙方應該規定出一個以ASN.1語法描述的協議文件,類似webservice開發中的wsdl,然后各自系統使用相關工具進行編解碼。2.1 開發庫目前網上能查到的第三方免費工具,主要有JavaAsn1Comiler和bouncycastle子庫:l JavaAsn1Comiler(JAC.jar):n 該工具可根據ASN.1協議描述文件,生成對應的java類,同時提供的API接口非常友好,命名概念同理論基本一致,使用非常方便,但是前提是必須要有完整的ASN.1描述文件,而且非常重要的一點使用限制是,該庫目前支持TAG值在0-127之間,即:如果協議中的數據使用了超過127的TAG值,則該庫無法支持,不可使用(否則會出現編碼錯誤,無法解析)。n 該庫提供了相當豐富的使用案例,可參考其工程下的test目錄。l bouncycastle(bcprov-jdk16-1.46.jar)n 該工具沒有提供自動生成java代碼的能力,如果要進行編解碼,則需要手動對協議中的類進行定義,并且自己調用相應的API實現編解碼。使用起來較JAC復雜,但是該庫對TAG值沒有限制,適合用在TAG值大于127的場景。n 在使用該類進行解析時,由于沒有提供方便的API進行自動解析,因此需要手工編寫解析代碼,比較郁悶的是其幫助文檔也沒有比較詳細的案例,最后的解碼操作還是通過閱讀其ASN1Dump類的實現方才完成。2.2 輔助工具由于經ASN.1編碼后的文件是二進制格式,無法直接閱讀,因此在開發過程中,為了能夠比較直觀的閱讀到其編碼后的記錄,需要借助第三方工具來查看編碼后的文件內容。網上有幾個查看工具,但是最方便、最直觀的工具則是ASN1VE 2.1(未注冊版有功能限制,只能查看編碼后的文件),通過該軟件可以很輕松的查看到文件內容,截圖如下:l 二進制視圖:l XML視圖第3章 JavaAsn1Compiler3.1 定義ASN.1描述文件通過通信雙方約定的數據格式,使用ASN.1語法對其進行定義(參見1.2百度文庫),形成.asn文件,如vc.asn。3.2 生成java代碼3.2.1 代碼生成將編寫好的.asn文件放到JAC.jar目錄,執行:java -jar JAC.jar -d c:/jac_test -p vc vc.asn參數描述:-d:生成java代碼文件的保存目錄;-p:生成java代碼的package;3.2.2 代碼案例以下代碼可從JavaAsn1Compiler工程的test目錄下獲取,ASN1文件內容:MiddleSeq ::= SEQUENCE{status [22] INTEGER,location [APPLICATION 11] INTEGER}生成的java代碼如下:import com.turkcelltech.jac.*;import com.chaosinmotion.asn1.Tag;public class MiddleSeq extends Sequence{public ASN1Integer status = new ASN1Integer("status");public ASN1Integer location = new ASN1Integer("location");publicMiddleSeq(){super();setUpElements();}publicMiddleSeq(String name){super(name);setUpElements();}protected voidsetUpElements(){super.addElement(status);status.setTagClass(Tag.CONTEXT);status.setTagNumber(22);super.addElement(location);location.setTagClass(Tag.APPLICATION);location.setTagNumber(11);/* end of element setup */}}3.2.3 編解碼// 編碼ByteArrayOutputStream outStream = new ByteArrayOutputStream();BerOutputStream out = new BerOutputStream(outStream);MiddleSeq ms = new MiddleSeq();ms.status.setValue(2);ms.location.setValue(314);ms.encode(out);// 解碼ByteArrayInputStream inputStream;BerInputStream in;inputStream = new ByteArrayInputStream(outStream.toByteArray());in = new BerInputStream(inputStream);MiddleSeq decode_ms = new MiddleSeq("decode_ms");decode_ms.decode(in);System.out.println("ms.status=" + decode_ms.status.getValue());可見編解碼非常簡單,如果是嵌套結構,只要對最外層對象執行encode/decode操作即可。3.3 總結使用該庫在有ASN1協議描述文件時,開發ASN1編解碼非常容易,缺點就是不支持超過127的TAG值。第4章 bouncycastlebouncycastle(簡稱bc)包含了一系列的java編解碼工具,ASN1只是其中的一類。在沒有ASN1協議描述文件的情況下,結合ASN1VE工具,可以進行相關的編解碼開發,云南服務質量管理系統與VC充值平臺正是使用這種方式開發的。4.1 編碼在云南服務質量管理系統中,實際上并沒用用到ASN1編碼的知識,但是在從零開始的背景下,為了更好的學習和理解ASN1的編碼格式,這里便開發了一個編碼模型。4.1.1 確定編碼的文件格式由于沒有ASN1文件,只有編碼后的文件,因此需要通過ASN1VE來查看編碼后是什么格式,如圖所示:通過上圖可以看出整個文件的組織結構、每個數據域對應的TAG值以及TAG的類型(Application)。但是ASN1的編碼有多種方式,如:BER/DER/PER等,bc庫提供的API就包含了BER和DER兩種類型,為了確定具體的編碼格式,利用bc庫自帶的ASN1Dump工具,可以將該文件通過文本的方式輸出出來(略),最后獲取的方式為DER,下面就利用bc庫提供的API來構造上述的結構。4.1.2 構造ASN1映射類有了上面分析出的結構再結合對方提供的Word文檔,即可以定義出大概結構,這里只給出其中Header的定義,其他可參考具體代碼:import java.io.IOException;import org.bouncycastle.asn1.ASN1EncodableVector;import org.bouncycastle.asn1.DERApplicationSpecific;import org.bouncycastle.asn1.DEREncodable;import org.bouncycastle.asn1.DERIA5String;import org.bouncycastle.asn1.DERInteger;public class RecordHeader extends DERApplicationSpecific{public RecordHeader(boolean explicit, int tag, DEREncodable object) throws IOException {super(explicit, tag, object);// TODO Auto-generated constructor stub}public RecordHeader(int tagNo, ASN1EncodableVector vec) {super(tagNo, vec);}public RecordHeader(int tag, byte[] octets) {super(tag, octets);// TODO Auto-generated constructor stub}public RecordHeader(int tag, DEREncodable object) throws IOException {super(tag, object);// TODO Auto-generated constructor stub}public static RecordHeader createHeader(int recodeType, String senderCode, String accepterCode,String fileSerialNo, String fileCreateTime, int fileVersionNo){DERInteger d_recodeType = new DERInteger(recodeType);DERIA5String d_senderCode = new DERIA5String(senderCode);DERIA5String d_accepterCode = new DERIA5String(accepterCode);DERIA5String d_fileSerialNo = new DERIA5String(fileSerialNo);DERIA5String d_fileCreateTime = new DERIA5String(fileCreateTime);DERInteger d_fileVersionNo = new DERInteger(fileVersionNo);ASN1EncodableVector vec = new ASN1EncodableVector();try{vec.add(new DERApplicationSpecific(false, 50, d_recodeType));vec.add(new DERApplicationSpecific(false, 51, d_senderCode));vec.add(new DERApplicationSpecific(false, 52, d_accepterCode));vec.add(new DERApplicationSpecific(false, 53, d_fileSerialNo));vec.add(new DERApplicationSpecific(false, 54, d_fileCreateTime));vec.add(new DERApplicationSpecific(false, 55, d_fileVersionNo));}catch(IOException e){e.printStackTrace();}RecordHeader header = new RecordHeader(33, vec);return header;}}代碼中幾個重要概念l 類定義:理論上一個類的定義應該是一個SEQUENCE,但是通過ASN1Dump出來的數據顯示其并不是一個Sequence,而只是一個普通的結點,因此這里不能繼承DERSequence,否則編碼出的文件將會在Application的上或者下多出一個Sequence結點。l DERApplicationSpecific:這個是表示構造Application類型Tag的類,如果協議中沒有規定Tag類型,默認的可以使用DERTaggedObject來定義節點。(說明:而在JAC庫中,如果要定義TAG的類型,實際上只要調用一個set方法即可,這里定義了一個單獨的類,在沒有說明的情況下是很難找到的)l 結構的模擬:為了實現ASN1VE中查看出的結構(一個結點下面的多個App子結點),可以通過bc庫提供的ASN1EncodableVector來進行模擬(見代碼)。4.2 解碼由于bc庫沒有提供直接解析成對象的API(也可能是我沒找到),因此需要自行定義業務實體以及解碼代碼。4.2.1 定義實體結構根據文檔(word)提供的協議的業務描述結合ASN1VE查看出的結構,定義出業務對象,其實就是普通的javabean,如:Root、Header、Tail和Body等,Root結構如下:public class Root {private Header header;private List<Body> bodys;private Tail tail;}4.2.2 定義實體解析類為了將數據解析出一個復合對象,需要針對每個實體定義一個解析類,將二進制數據解析成類對象,如:RootDecoder、HeaderDecoder、BodyDecoder和TailDecoder等,其中HeaderDecoder代碼如下:public class HeaderDecoder {private Header header = new Header();/*** 按Sequence順序解析包頭* @param nodeHeader* @throws IOException*/public HeaderDecoder(DERApplicationSpecific derHeader) throws IOException{ASN1Sequence s = ASN1Sequence.getInstance(derHeader.getObject(DERTags.SEQUENCE));for (Enumeration e = s.getObjects(); e.hasMoreElements();){DERApplicationSpecific derObj = (DERApplicationSpecific)e.nextElement();System.out.println(derObj.getApplicationTag());if (derObj.getApplicationTag() == 50) {DERInteger recodeType = new DERInteger(derObj.getContents());header.setRecodeType(recodeType.getValue().intValue());}else if (derObj.getApplicationTag() == 51){DERIA5String senderCode = new DERIA5String(derObj.getContents()); header.setSenderCode(senderCode.getString());}else if (derObj.getApplicationTag() == 52){DERIA5String accepterCode = new DERIA5String(derObj.getContents());header.setAccepterCode(accepterCode.getString());}else if (derObj.getApplicationTag() == 53){DERIA5String fileSerialNo = new DERIA5String(derObj.getContents());header.setFileSerialNo(fileSerialNo.getString());}else if (derObj.getApplicationTag() == 54){DERIA5String fileCreateTime = new DERIA5String(derObj.getContents());header.setFileCreateTime(fileCreateTime.getString());}else if (derObj.getApplicationTag() == 55){DERInteger fileVersionNo = new DERInteger(derObj.getContents());header.setFileVersionNo(fileVersionNo.getValue().intValue());}}}/*** @return the header*/public Header getHeader() {return header;}}代碼簡介:該Decoder在構造函數中讀取外部傳來的DERApplicationSpecific 對象(該對象使其上層root解析子節點獲取得到的),在利用bc庫的API根據TAG值將其所有的子節點的二進制數據讀出來,最后通過bc庫內置的基本類型將二進制數據再解碼成原始數據。4.2.3 外部調用接口為了方便的實現文件解析,單獨提供一個類(DecodeMan):輸入為需要解碼的ASN1二進制文件,輸出為定義的復合類型Root,最后相關人員再通過對Root的數據進行格式化轉換成項目需要用的數據格式。public class DecodeMan {public static void main(String[] args) throws IOException {Root root = decode("c:/IVCRECORD_20110324871.0058");System.out.println(root.toString());}/*** 文件解碼* @param encodeFile 文件絕對路徑* @return 填充數據的Root實例* @throws IOException*/public static Root decode(String encodeFile) throws IOException{System.out.println("Starting decode file: [" + encodeFile + "]");File file = new File(encodeFile);byte[] byteContents = FileUtils.readFileToByteArray(file);ByteArrayInputStream inputStream = null;try{inputStream = new ByteArrayInputStream(byteContents);ASN1StreamParser asn1Parser = new ASN1StreamParser(inputStream);DERApplicationSpecific derRoot = (DERApplicationSpecific)asn1Parser.readObject();RootDecoder rootDecoder = new RootDecoder(derRoot);System.out.println("Decode file succ: [" + encodeFile + "]");return rootDecoder.getRoot();}finally{IOUtils.closeQuietly(inputStream);}}}
新聞熱點
疑難解答