数据序列化:将高级语言代码转变成二进制流的过程称为数据的序列号,数据序列化的目的是物理存储或网络传输。在计算机高级程序编程语言里有许多结构化的结构,比如:数、图和结构体等,如何在网络中传输这些结构化的结构数据呢?我们知道高级语言里面的任何代码最终在网络中都是以二进制流的形式进行传输的,但是对于结构化的数据除了编成二进制码外,他们之间还存有一定的关系,为了在对方仍然知道这些关系以用来还原之前的结构,因此除了对结构化的数据进行编码外还得需要保存好他们的结构关系。
以二叉树为例,在tcp协议传输数据的时候,把网络看作一个“流”(类似管道),数据从一边流进去从另一边流出来,但是二叉树要塞到这个“流”里面怎么塞呢,只能把它变成数组类似的东西,到了另一边再恢复成二叉树,就像运输床的时候要先拆开,到了目的地再把床头啊床板啊什么的装回去。
为了解决高级语言中结构化数据在网络传输中的结构关系能送达目的地进行还原,历史上出现了以下几种数据序列化的方法:ASN.1,XML,Json等(可参考:在维基百科中搜索数据序列号格式)。
上面我们分析了ASN.1的作用(或者说为什么要引入ASN.1),下面看看ASN.1是什么东西?
ASN.1本身只定义了表示信息的抽象句法,但是没有限定其编码的方法。各种ASN.1编码规则提供了由ASN.1描述其抽象句法的数据的值的传送语法(具体表达)。标准的ASN.1编码规则有基本编码规则(BER,Basic Encoding Rules)、规范编码规则(CER,Canonical Encoding Rules)、唯一编码规则(DER,Distinguished Encoding Rules)、压缩编码规则(PER,Packed Encoding Rules)和XML编码规则(XER,XML Encoding Rules)。为了使ASN.1能够描述一些原先没有使用ASN.1定义,因此不适用上述任一编码规则的数据传输和表示的应用和协议,另外制订了ECN来扩展ASN.1的编码形式。ECN可以提供非常灵活的表明方法,但还没有得到普遍应用。
ASN.1与特定的ASN.1编码规则一起通过使用独立于计算机架构和编程语言的方法来描述数据结构,为结构化数据的交互提供了手段,特别是在网络环境的应用程序。
(可参考:http://wc.yooooo.us/wiki/ASN.1 )
关于ASN.1的编码规则就不在多讲了,网上资料很多,其本质就是TLV格式的组建:Tag、Length和Value。在这里我们看看在openssl的内部是如何将一个C语言的结构体编码为asn1的,以X509结构体为例:
在include\openssl\x509.h头文件里:
DECLARE_ASN1_FUNCTIONS(X509),这个宏展开后:
#define DECLARE_ASN1_FUNCTIONS(type) DECLARE_ASN1_FUNCTIONS_name(type, type)
继续将后面的宏DECLARE_ASN1_FUNCTIONS_name(type, type)展开:
#define DECLARE_ASN1_FUNCTIONS_name(type, name) \
DECLARE_ASN1_ALLOC_FUNCTIONS_name(type, name) \
DECLARE_ASN1_ENCODE_FUNCTIONS(type, name, name)
#define DECLARE_ASN1_ALLOC_FUNCTIONS_name(type, name) \
type *name##_new(void); \
void name##_free(type *a);
#define DECLARE_ASN1_ENCODE_FUNCTIONS(type, itname, name) \
type *d2i_##name(type **a, const unsigned char **in, long len); \
int i2d_##name(type *a, unsigned char **out); \
DECLARE_ASN1_ITEM(itname)
到这里我们就能清楚的看到宏DECLARE_ASN1_FUNCTIONS(X509),实际上是声明了关于X509的四个函数(四个函数中所涉及的参数忽略):
X509_new();
X509_free();
i2d_X509();
d2i_X509();
有了声明,就必然有相对应的实现,其实现宏在crypto\asn1\x_x509.c:
IMPLEMENT_ASN1_FUNCTIONS(X509),将该宏展开后:
#define IMPLEMENT_ASN1_FUNCTIONS(stname) IMPLEMENT_ASN1_FUNCTIONS_fname(stname, stname, stname)
继续展开:
#define IMPLEMENT_ASN1_FUNCTIONS_fname(stname, itname, fname) \
IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(stname, itname, fname) \
IMPLEMENT_ASN1_ALLOC_FUNCTIONS_fname(stname, itname, fname)
#define IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(stname, itname, fname) \
stname *d2i_##fname(stname **a, const unsigned char **in, long len) \
{ \
return (stname *)ASN1_item_d2i((ASN1_VALUE **)a, in, len, ASN1_ITEM_rptr(itname));\
} \
int i2d_##fname(stname *a, unsigned char **out) \
{ \
return ASN1_item_i2d((ASN1_VALUE *)a, out, ASN1_ITEM_rptr(itname));\
}
#define IMPLEMENT_ASN1_ALLOC_FUNCTIONS_fname(stname, itname, fname) \
stname *fname##_new(void) \
{ \
return (stname *)ASN1_item_new(ASN1_ITEM_rptr(itname)); \
} \
void fname##_free(stname *a) \
{ \
ASN1_item_free((ASN1_VALUE *)a, ASN1_ITEM_rptr(itname)); \
}
很明显,该实现宏一次实现了上面声明宏所声明的四个函数,但此处要注意,在该实现宏中实现的四个函数中会涉及到一些变量,该变量需要一个下面的宏(crypto\asn1\x_x509.c)来声明:
ASN1_SEQUENCE_ref(X509, x509_cb, CRYPTO_LOCK_X509) = {
ASN1_SIMPLE(X509, cert_info, X509_CINF),
ASN1_SIMPLE(X509, sig_alg, X509_ALGOR),
ASN1_SIMPLE(X509, signature, ASN1_BIT_STRING)
} ASN1_SEQUENCE_END_ref(X509, X509)
该宏实际上是声明了一些了在实现宏中所用到的一些变量和结构体。
综上,我们可以得到如下的结论,在openssl内部对一个C语言中的结构体进行asn1编码的步骤有三步:
1.在头文件中声明DECLARE_ASN1_FUNCTIONS(X509);
2.在实现文件.C文件中声明一些变量和结构体:
ASN1_SEQUENCE_ref(X509, x509_cb, CRYPTO_LOCK_X509) = {
ASN1_SIMPLE(X509, cert_info, X509_CINF),
ASN1_SIMPLE(X509, sig_alg, X509_ALGOR),
ASN1_SIMPLE(X509, signature, ASN1_BIT_STRING)
} ASN1_SEQUENCE_END_ref(X509, X509)
其实ASN1_SEQUENCE跟ASN1_SEQUENCE_ref一样的效果,因此上面的声明等同于下面:
ASN1_SEQUENCE(X509) = {
ASN1_SIMPLE(X509, cert_info, X509_CINF),
ASN1_SIMPLE(X509, sig_alg, X509_ALGOR),
ASN1_SIMPLE(X509, signature, ASN1_BIT_STRING)
} ASN1_SEQUENCE_END(X509)
3.在实现文件.C文件中调用实现宏:
IMPLEMENT_ASN1_FUNCTIONS(X509),该宏会实现声明宏中所声明的四个函数。
有了上面的知识后,我们就可以"任性"的对C语言中的结构体进行asn1编码处理了,比如,这段时间在工作过程中涉及到国密局SM2算法中的公钥结构体:
#define ECCref_MAX_BITS 256
#define ECCref_MAX_LEN ((ECCref_MAX_BITS+7) / 8)
typedef struct ECCrefPublicKey_st
{
unsigned int bits;
unsigned char x[ECCref_MAX_LEN];
unsigned char y[ECCref_MAX_LEN];
} ECCrefPublicKey;
我们可以根据上面的思路对其进行asn1编码处理.
第一步,在头文件中所下声明:
typedefstruct ASN_ECCPUBLICKEY_st
{
ASN1_OCTET_STRING *X;
ASN1_OCTET_STRING *Y;
}ASN_ECCPUBLICKEY;
DECLARE_ASN1_FUNCTIONS(ASN_ECCPUBLICKEY)
第二步和第三步都在实现的.C文件中,放在一起如下:
ASN1_SEQUENCE(ASN_ECCPUBLICKEY) = {
ASN1_SIMPLE(ASN_ECCPUBLICKEY, X, ASN1_OCTET_STRING),
ASN1_SIMPLE(ASN_ECCPUBLICKEY, Y, ASN1_OCTET_STRING),
} ASN1_SEQUENCE_END(ASN_ECCPUBLICKEY);
IMPLEMENT_ASN1_FUNCTIONS(ASN_ECCPUBLICKEY)
int i2d_ECC_PublicKey(const ECCrefPublicKey *cipher, unsigned char **out)
{
ASN_ECCPUBLICKEY *ec = NULL;
int len = 0;
ec = ASN_ECCPUBLICKEY_new();
if (ec == NULL) {
return 0;
}
do {
if (!ASN1_OCTET_STRING_set(ec->X, cipher->x, 32))
break;
if (!ASN1_OCTET_STRING_set(ec->Y, cipher->y, 32))
break;
/* i2d */
len = i2d_ASN_ECCPUBLICKEY(ec, out);
} while (0);
ASN_ECCPUBLICKEY_free(ec);
return len;
}
int d2i_ECC_PublicKey(ECCrefPublicKey *ins, const unsigned char **ppin, long pplen)
{
ASN_ECCPUBLICKEY *ec = NULL;
/* DECODE */
ec = d2i_ASN_ECCPUBLICKEY(NULL, ppin, pplen);
if (ec == NULL) {
return 0;
}
/* check version ? */
do {
if (ec->X->length <= 0 || ec->Y->length <= 0)
break;
memcpy(ins->x, ec->X->data, ec->X->length);
memcpy(ins->y, ec->Y->data, ec->Y->length);
ASN_ECCPUBLICKEY_free(ec);
return 1;
} while(0);
ASN_ECCPUBLICKEY_free(ec);
return 1;
}
上面中的int i2d_ECC_PublicKey(const ECCrefPublicKey *cipher, unsigned char **out)是将输入参数cipher转换为asn1串通过out返回;
int d2i_ECC_PublicKey(ECCrefPublicKey *ins, const unsigned char **ppin, long pplen)是将输入的asn1串ppin转换为公钥结构体ins返回。