首页 > 学院 > 开发设计 > 正文

ASN1编解码实现方法

2019-11-09 19:27:49
字体:
来源:转载
供稿:网友

目 录版本记录 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 就已经成为了一种国际标准,它的编码规则已经成熟并在可靠性和兼容性方面拥有更丰富的历程。  简洁的二进制编码规则(BER、CER、DER、PER,但不包括 XER)可当作更现代 XML 的替代。然而,ASN.1 支持对数据的语义进行描述,所以它是比 XML 更为高级的语言。  ASN.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);}}}


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表