在证书编码中,主要使用的是两种编码结构,一个是PEM编码,另一种就是DER编码( Distinguished Encoding Rules,可辨别编码规则 )。而DER编码同时也是ASN.1(抽象语法标记)的一个子集。
一个标准的ASN.1 编码对象有四个域:对象标识域、数据长度域、数据域以及结束标志(可选,在长度不可知情况下需要,openssl中没有该标志)。详细的解释请看百度百科: http://baike.baidu.com/view/100318.htm 。
本篇就简单的对一个DER格式的文件进行解析,并介绍相应的工具类。
创建一个工具类,类名就是DerAnalysis,创建构造方法:
public DerAnalysis(byte abyte[]) {
mRecord = abyte; //需要解析的DER格式文件转成的数组
mTlvOffset = 0; //数据指定位
mCurOffset = 0; // 数据偏移索引
mObjLength = abyte.length; //待解析数据长度
mIsDer = parseCurrentData(); // 判断是否满足Der编码结构
}
在构造方法中将数据进行初始化,保存数据的长度及默认偏移位和数据指定位,其中数据指定位指的是可以再次声明构造方法,将数据指定区域向后偏移一定的长度,这属于对当前类的扩展了,就不做说明了。
private boolean parseCurrentData() {
try
{
if (this.mRecord[this.mCurOffset] != 0)
{
// 如果指定位置是 0xFF 直接结束
if ((0xFF & this.mRecord[this.mCurOffset]) == 255)
return false;
// 如果指定位置+1位小于 0x81 ,说明指定位置之后紧接着的字节就是数据的长度值。
if ((0xFF & this.mRecord[(1 + this.mCurOffset)]) < 128)
{
this.mCurDataLength = (0xFF & this.mRecord[(1 + this.mCurOffset)]);
this.mCurDataOffset = (2 + this.mCurOffset);
return true;
}
// 如果指定位置+1 位大于等于 0x81 (也就是表示长度位 是一位还是二位 )
while (this.mCurDataLength + this.mCurDataOffset <= this.mTlvOffset + this.mObjLength)
{
//
//如果 mCurOffset + 1 位 是0x81 ,长度是一个字节表示
if ((0xFF & this.mRecord[(1 + this.mCurOffset)]) == 129){
this.mCurDataLength = (0xFF & this.mRecord[(2 + this.mCurOffset)]);
this.mCurDataOffset = (3 + this.mCurOffset);
return true;
}
//如果 mCurOffset + 1 位 是0x82 ,长度是二个字节表示
if ((0xFF & this.mRecord[(1 + this.mCurOffset)]) == 130) {
this.mCurDataLength = ((0xFF & this.mRecord[(2 + this.mCurOffset)]) << 8 | 0xFF & this.mRecord[(3 + this.mCurOffset)]);
this.mCurDataOffset = (4 + this.mCurOffset);
return true;
}
break;
}
}
return false;
}
catch (ArrayIndexOutOfBoundsException localArrayIndexOutOfBoundsException)
{
return false;
}
}
当前方法是将DER格式的数据进行相应解析,重点是将数据位及数据长度位的指定索引进行读取。从上述方法中,可以看出,我只做了长度位在两个字节以内的文件进行判断,因为在一般的证书文件中,长度位一般都不会超过2个字节,要知道2个字节的长度就达到65535字节了。
同时,在DER格式标准中,长度位是高位优先的,所以在计算长度的时候将首位进行向左位移8位。
数据长度区域索引、数据区域索引都已经找到了。就只需要提供几个方法,将解析的结果对外暴露了。
public boolean isDir()
{
return this.mIsDer;
}
这个方法在解析时,是第一个被调用的,先判断是否满足DER结构,否则就不要进行下去了。
public int getTag()
{
if (!this.mIsDer)
return 0;
return (0xFF & this.mRecord[this.mCurOffset]);
}
此方法是获取对象标识符数据,在当前类中,我默认标记位都只是一个字节,如果各位的需求文档中表明不止一个字节表明对象标识符,那么就需要进行相应的修改了,这里只是作为一个基本的写法,就不扩展了。
拿到对象标识符进行判断是否和认为的标识符一样,如果一样就可以提取数据了。
public byte[] getData()
{
if(!mIsDer)
{
return null;
} else{
try {
byte abyte1[] = null;
byte abyte0[] = null;
if ((0xFF & mRecord[mCurDataOffset]) == 0) {
abyte1= new byte[mCurDataLength-1];
System.arraycopy(mRecord , mCurDataOffset+1 , abyte1 , 0,mCurDataLength-1);
} else {
abyte0 = new byte[mCurDataLength];
System.arraycopy(mRecord, mCurDataOffset, abyte0, 0, mCurDataLength);
}
return abyte1 != null ? abyte1 : abyte0;
}catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
此方法就是获取数据的方法,在方法中会有个判断,如果获取到的数据域第一个数据是0x00,那么认为此字节是补位的,并非是真正的数据,所以从此字节之后的数据进行提取。
在DER解析时,数据域不可能只有一个数据块的,所以需要进行下一个数据块的解析,如果从工具类外部进行操作的话,显得很是冗余,所以应该在工具类中进行下一个数据块的解析。
public boolean nextObject()
{
if (!this.mIsDer)
return false;
this.mCurOffset = (this.mCurDataOffset + this.mCurDataLength);
this.mIsDer = parseCurrentData();
return this.mIsDer;
}
执行此方法,将数据偏移向后,便可以开始下一个数据块的解析了。具体的方法还是按照上述提供的方法。
在工具类进行解析DER格式数据的时候,在同一个层级的数据块向后解析时,调用的是nextObject方法,而如果是当前数据块中包含其他的数据块,如果是这种结构,就需要重新将获取到的数据(getData方法)作为新的要解析的数据,重新执行构造方法了。
上述便是简单解析DER格式数据的工具类说明,代码比较简单,所需要的是要对DER格式文件有一定的了解。如果需要扩展请自行定义。