当前位置:Linux教程 - Linux文化 - OpenSSL中对称加密算法的统一接口详解

OpenSSL中对称加密算法的统一接口详解


  1. 前言

OpenSSL是一个开源的SSL实现,其中集成了多种加密算法。OpenSSL将各算法的各自独特地方封装在内部,对外则使用了统一的算法接口,因此外部应用只需指定使用何种算法,就可以用相同的方法调用加解密函数而不用考虑其差异。这种算法统一封装的方式在其他很多软件中都采用,也给算法扩充提供了方便。

以下代码来自OpenSSL-0.9.7b。

2. EVP接口

2.1 数据结构

Openssl/crypto/evp目录下定义各种算法的接口源文件,这些文件要作的事就是要填写描述算法的EVP_CIPHER结构,每个算法都有一个EVP_CIPHER结构进行描述:

Openssl/crypto/evp/evp.h

struct evp_cipher_st

{

int nid;

int block_size;

int key_len; /* Default value for variable length ciphers */

int iv_len;

unsigned long flags; /* Various flags */

int (*init)(EVP_CIPHER_CTX *ctx, const unsigned char *key,

const unsigned char *iv, int enc); /* init key */

int (*do_cipher)(EVP_CIPHER_CTX *ctx, unsigned char *out,

const unsigned char *in, unsigned int inl);/* encrypt/decrypt data */

int (*cleanup)(EVP_CIPHER_CTX *); /* cleanup ctx */

int ctx_size; /* how big ctx->cipher_data needs to be */

int (*set_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *); /* Populate a ASN1_TYPE with parameters */

int (*get_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *); /* Get parameters from a ASN1_TYPE */

int (*ctrl)(EVP_CIPHER_CTX *, int type, int arg, void *ptr); /* Miscellaneous operations */

void *app_data; /* Application data */

} /* EVP_CIPHER */;

typedef struct evp_cipher_st EVP_CIPHER;

nid:算法的ID号,在include/openssl/object.h中定义;

block_size:加解密的分组长度

key_len:密钥长度

iv_len:初始向量长度

flags:标志

(* init):初始化函数,提供密钥,IV向量,算法上下文CTX,加密还是解密

(* do_cipher):加解密函数,提供算法上下文CTX,输出数据,输入数据和输入数据长度

(* clean_up):资源释放

ctx_size:各算法相关数据大小,实际就是各算法的密钥数据

(*set_asn1_parameters):设置asn1参数

(*get_asn1_parameters):获取asn1参数

(*ctrl):其他控制操作

app_data:算法相关数据

另一个重要的数据结构是描述加密算法的上下文结构EVP_CIPHER_CTX,这个结构是进入算法前由系统根据指定的算法提供的:

struct evp_cipher_ctx_st

{

const EVP_CIPHER *cipher;

ENGINE *engine; /* functional reference if 'cipher' is ENGINE-provided */

int encrypt; /* encrypt or decrypt */

int buf_len; /* number we have left */

unsigned char oiv[EVP_MAX_IV_LENGTH]; /* original iv */

unsigned char iv[EVP_MAX_IV_LENGTH]; /* working iv */

unsigned char buf[EVP_MAX_BLOCK_LENGTH];/* saved partial block */

int num; /* used by cfb/ofb mode */

void *app_data; /* application stuff */

int key_len; /* May change for variable length cipher */

unsigned long flags; /* Various flags */

void *cipher_data; /* per EVP data */

int final_used;

int block_mask;

unsigned char final[EVP_MAX_BLOCK_LENGTH];/* possible final block */

} /* EVP_CIPHER_CTX */;

typedef struct evp_cipher_ctx_st EVP_CIPHER_CTX;

参数为:

cipher:算法指针

engine:加解密引擎

encrypt:加密或解密

buf_len:剩余空间

oiv:原始的初始向量

iv:当前的初始向量

buf:保存的部分块数据

num:cfb/ofb方式时的数据数量

app_data:应用相关数据

key_len:密钥长度

flags:标志

cipher_data:各算法相关部分,主要是各算法的key等

final_used:

block_mask:块的掩码

final:最后的分组块

1.2.2 算法接口

每种算法就是要填写各自的EVP_CIPHER结构,以RC4为例,定义了两个RC4的EVP_CIPHER结构,只是密钥长度不同,一个是128位(16字节密钥),一个是40位(5字节密钥),而算法都一样:

#ifndef OPENSSL_NO_RC4

#include

#include "cryptlib.h"

#include

#include

#include

/* FIXME: surely this is available elsewhere? */

#define EVP_RC4_KEY_SIZE 16

//这个结构是各加密算法独有的,各算法的各自不同

//也就是EVP_CIPHER_CTX结构中cipher_data

typedef struct

{

RC4_KEY ks; /* working key */

} EVP_RC4_KEY;

#define data(ctx) ((EVP_RC4_KEY *)(ctx)->cipher_data)

static int rc4_init_key(EVP_CIPHER_CTX *ctx, const unsigned char *key,

const unsigned char *iv,int enc);

static int rc4_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out,

const unsigned char *in, unsigned int inl);

static const EVP_CIPHER r4_cipher=

{

NID_rc4,

1,EVP_RC4_KEY_SIZE,0,

EVP_CIPH_VARIABLE_LENGTH,

rc4_init_key,

rc4_cipher,

NULL,

sizeof(EVP_RC4_KEY),

NULL,

NULL,

NULL

};

static const EVP_CIPHER r4_40_cipher=

{

NID_rc4_40,

1,5 /* 40 bit */,0,

EVP_CIPH_VARIABLE_LENGTH,

rc4_init_key,

rc4_cipher,

NULL,

sizeof(EVP_RC4_KEY),

NULL,

NULL,

NULL

};

// 返回算法结构指针

const EVP_CIPHER *EVP_rc4(void)

{

return(&r4_cipher);

}

const EVP_CIPHER *EVP_rc4_40(void)

{

return(&r4_40_cipher);

}

// 密钥初始化函数

// ctx:加解密上下文;key:密钥字符串;iv:初始化向量;enc:加密还是解密

static int rc4_init_key(EVP_CIPHER_CTX *ctx, const unsigned char *key,

const unsigned char *iv, int enc)

{

// RC4算法设置密钥函数

// 一般来说,加解密时算法里用的密钥并不是用户输入的密码字符串本身,因为算法

// 使用的密钥长度要求是固定的,通常为64位或128位,而用户自己定义的密码长度

// 则不确定,所以一般都都要对用户输入的密码进行变换,映射到一个固定长度密钥

// 上,然后算法再使用该密钥加密,所以算法中用的密钥和用户的密码一般是不同的

RC4_set_key(&data(ctx)->ks,EVP_CIPHER_CTX_key_length(ctx),

key);

return 1;

}

// 加解密处理函数

// ctx:加解密上下文;out:输出数据;in:输入数据;inl:输入数据长度

static int rc4_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out,

const unsigned char *in, unsigned int inl)

{

RC4(&data(ctx)->ks,inl,in,out);

return 1;

}

#endif

对于定义好EVP_CIPHER结构的加解密算法,最后通过EVP_add_cipher()函数添加到系统算法链表中:

openssl/evp/c_alle.c

void OpenSSL_add_all_ciphers(void)

{

......

#ifndef OPENSSL_NO_RC4

EVP_add_cipher(EVP_rc4());

EVP_add_cipher(EVP_rc4_40());

#endif

......

以后外部函数要调用RC4算法,实际就是找到RC4的EVP_CIPHER结构指针,进行初始化后进行加解密处理:

如openssl命令:

openssl enc –rc4 –in aaa.txt –out aaa.enc

执行顺序如下:

openssl/apps/openssl.c

main() -> do_cmd()->MAIN() (apps/enc.c) -> EVP_get_cipherbyname(), EVP_BytesToKey(), BIO_set_cipher(),

BIO_set_cipher -> EVP_CipherInit_ex -> ctx->cipher->init

而加密算法作为BIO的一个环节被BIO_push到BIO中,这样就通过直接BIO读写操作就调用了相应的加解密算法。

3. 块加密算法定义宏

对于块加密算法,如AES、CAST、Blowfish等,其加解密函数都是类似的,openssl更是定义了一系列宏来简化算法的定义,如对于Blowfish算法接口:

/* openssl/evp/e_bf.c */

#ifndef OPENSSL_NO_BF

#include

#include "cryptlib.h"

#include

#include "evp_locl.h"

#include

#include

static int bf_init_key(EVP_CIPHER_CTX *ctx, const unsigned char *key,

const unsigned char *iv, int enc);

typedef struct

{

BF_KEY ks;

} EVP_BF_KEY;

#define data(ctx) EVP_C_DATA(EVP_BF_KEY,ctx)

//定义块加密算法的宏

IMPLEMENT_BLOCK_CIPHER(bf, ks, BF, EVP_BF_KEY, NID_bf, 8, 16, 8, 64,

EVP_CIPH_VARIABLE_LENGTH, bf_init_key, NULL,

EVP_CIPHER_set_asn1_iv, EVP_CIPHER_get_asn1_iv, NULL)

// 显式只需要定义一个初始化密钥函数就可以了

static int bf_init_key(EVP_CIPHER_CTX *ctx, const unsigned char *key,

const unsigned char *iv, int enc)

{

BF_set_key(&data(ctx)->ks,EVP_CIPHER_CTX_key_length(ctx),key);

return 1;

}

#endif

在openssl/evp/evp_locl.h中该宏定义为:

#define IMPLEMENT_BLOCK_CIPHER(cname, ksched, cprefix, kstruct, nid, \

block_size, key_len, iv_len, cbits, \

flags, init_key, \

cleanup, set_asn1, get_asn1, ctrl) \

BLOCK_CIPHER_all_funcs(cname, cprefix, cbits, kstruct, ksched) \

BLOCK_CIPHER_defs(cname, kstruct, nid, block_size, key_len, iv_len, \

cbits, flags, init_key, cleanup, set_asn1, \

get_asn1, ctrl)

1) 宏BLOCK_CIPHER_all_funcs用来定义加解密函数,定义为:

#define BLOCK_CIPHER_all_funcs(cname, cprefix, cbits, kstruct, ksched) \

BLOCK_CIPHER_func_cbc(cname, cprefix, kstruct, ksched) \

BLOCK_CIPHER_func_cfb(cname, cprefix, cbits, kstruct, ksched) \

BLOCK_CIPHER_func_ecb(cname, cprefix, kstruct, ksched) \

BLOCK_CIPHER_func_ofb(cname, cprefix, cbits, kstruct, ksched)

其中BLOCK_CIPHER_func_cbc定义为:

#define BLOCK_CIPHER_func_cbc(cname, cprefix, kstruct, ksched) \

static int cname##_cbc_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out, const unsigned char *in, unsigned int inl) \

{\

cprefix##_cbc_encrypt(in, out, (long)inl, &((kstruct *)ctx->cipher_data)->ksched, ctx->iv, ctx->encrypt);\

return 1;\

}

此处注意一个#define的技巧:可以用#define替换变量或函数名,参数在打头或结尾处时,可分别在参数后或参数前用##;参数在中间, 参数开头结尾都用##;

这样对于blowfish定义来说,上面这个宏实际定义这样一个函数:

static int bf_cbc_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out, const unsigned char *in, unsigned int inl)

{

bf_cbc_encrypt(in, out, (long)inl, &((kstruct *)ctx->cipher_data)->ksched, ctx->iv, ctx->encrypt);

return 1;

}

其他几个宏定义类似,定义不同模式的bf加密算法,包括cbc,cfb,ecb,ofb。

2) 宏BLOCK_CIPHER_defs用于定义算法结构,定义为:

#define BLOCK_CIPHER_defs(cname, kstruct, \

nid, block_size, key_len, iv_len, cbits, flags, \

init_key, cleanup, set_asn1, get_asn1, ctrl) \

BLOCK_CIPHER_def_cbc(cname, kstruct, nid, block_size, key_len, iv_len, flags, \

init_key, cleanup, set_asn1, get_asn1, ctrl) \

BLOCK_CIPHER_def_cfb(cname, kstruct, nid, key_len, iv_len, cbits, \

flags, init_key, cleanup, set_asn1, get_asn1, ctrl) \

BLOCK_CIPHER_def_ofb(cname, kstruct, nid, key_len, iv_len, cbits, \

flags, init_key, cleanup, set_asn1, get_asn1, ctrl) \

BLOCK_CIPHER_def_ecb(cname, kstruct, nid, block_size, key_len, iv_len, flags, \

init_key, cleanup, set_asn1, get_asn1, ctrl)

其中BLOCK_CIPHER_def_cbc定义为:

#define BLOCK_CIPHER_def_cbc(cname, kstruct, nid, block_size, key_len, \

iv_len, flags, init_key, cleanup, set_asn1, \

get_asn1, ctrl) \

BLOCK_CIPHER_def1(cname, cbc, cbc, CBC, kstruct, nid, block_size, key_len, \

iv_len, flags, init_key, cleanup, set_asn1, get_asn1, ctrl)

而BLOCK_CIPHER_def1定义为:

#define BLOCK_CIPHER_def1(cname, nmode, mode, MODE, kstruct, nid, block_size, \

key_len, iv_len, flags, init_key, cleanup, \

set_asn1, get_asn1, ctrl) \

static const EVP_CIPHER cname##_##mode = { \

nid##_##nmode, block_size, key_len, iv_len, \

flags | EVP_CIPH_##MODE##_MODE, \

init_key, \

cname##_##mode##_cipher, \

cleanup, \

sizeof(kstruct), \

set_asn1, get_asn1,\

ctrl, \

NULL \

}; \

const EVP_CIPHER *EVP_##cname##_##mode(void) { return &cname##_##mode; }

所以宏BLOCK_CIPHER_def_cbc展开后就是blowfish的cbc算法结构定义:

static const EVP_CIPHER bf_cbc = {

NID_bf_cbc, 8, 16, 8,

EVP_CIPH_VARIABLE_LENGTH | EVP_CIPH_CBC_MODE,

bf_init_key,

bf_cbc_cipher,

NULL,

sizeof(EVP_BF_KEY),

EVP_CIPHER_set_asn1_iv, EVP_CIPHER_get_asn1_iv,

NULL,

NULL

};

const EVP_CIPHER *EVP_bf_cbc(void) { return &bf_cbc; }

其他几个宏定义类似,定义不同模式的bf加密算法的EVP_CIPHER数据结构,包括cbc,cfb,ecb,ofb。

4. 结论

openssl使用了统一的EVP_CIPHER算法结构,很好地封装了各种对称加密算法,实现了算法的对象化。

5. 附录

cbc,cfb,ecb,ofb等并不是新的加密算法,而是对加密算法的应用模式。

ECB:Electronic Code Book,电子密码本模式,最基本的加密模式,也就是通常理解的加密,相同的明文将永远加密成相同的密文,无初始向量,容易受到密码本重放攻击,一般情况下很少用。

CBC:Cipher Block Chaining,密码分组链接,明文被加密前要与前面的密文进行异或运算后再加密,因此只要选择不同的初始向量,相同的密文加密后会形成不同的密文,这是目前应用最广泛的模式。CBC加密后的密文是上下文相关的,但明文的错误不会传递到后续分组,但如果一个分组丢失,后面的分组将全部作废(同步错误)。

CFB:Cipher FeedBack,密码反馈,类似于自同步序列密码,分组加密后,按8位分组将密文和明文进行移位异或后得到输出同时反馈回移位寄存器,优点最小可以按字节进行加解密,也可以是n位的,CFB也是上下文相关的,CFB模式下,明文的一个错误会影响后面的密文(错误扩散)。

OFB:Output Feedback,输出反馈,将分组密码作为同步序列密码运行,和CFB相似,不过OFB用的是前一个n位密文输出分组反馈回移位寄存器,OFB没有错误扩散问题。