替换OpenSSL Engine加密之替换EVP_CIPHER结构

替换cipher即替换对称加密算法(数据加解密)。

struct evp_cipher_st 
{ 
  int nid; 
  int block_size; 
  int key_len;  
  int iv_len; 
  unsigned long flags; 
    int (*init)(EVP_CIPHER_CTX *ctx, const unsigned char *key, 
            const unsigned char *iv, int enc); 
  int (*do_cipher)(EVP_CIPHER_CTX *ctx, unsigned char *out, 
        const unsigned char *in, unsigned int inl); 
    int (*cleanup)(EVP_CIPHER_CTX *); /* cleanup ctx */ 
  int ctx_size; 
    int (*set_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *); 
    int (*get_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *); 
    int (*ctrl)(EVP_CIPHER_CTX *, int type, int arg, void *ptr); 
  void *app_data; 
} ; 
typedef struct evp_cipher_st EVP_CIPHER;

该结构用来存放对称加密相关的信息以及算法。主要各项意义如下: 
nid:对称算法 nid; 
block_size:对称算法每次加解密的字节数; 
key_len:对称算法的密钥长度字节数; 
iv_len:对称算法的填充长度;
flags:用于标记; 
init:加密初始化函数,用来初始化 ctx,key 为对称密钥值,iv 为初始化向量,enc
     用于指明是要加密还是解密,这些信息存放在ctx 中; 
do_cipher:对称运算函数,用于加密或解密; 
cleanup:清除上下文函数; 
set_asn1_parameters:设置上下文参数函数; 
get_asn1_parameters:获取上下文参数函数; 
ctrl:控制函数; 
app_data:用于存放应用数据。 
openssl对于各种对称算法实现了上述结构,各个源码位于 cypto/evp 目录下,文件名
     以e_ 开头。Openssl通过这些结构来封装了对称算法相关的运算。 

示例:

static int mytest_init_key(EVP_CIPHER_CTX *ctx, const unsigned char *key, 
    const unsigned char *iv, int enc) 
{
    printf( "mytest_init_key\n" );
    return 1;
}

static int mytest_cipher_enc(EVP_CIPHER_CTX *ctx, unsigned char *out, 
    const unsigned char *in, unsigned int inlen) 
{
    printf( "mytest_cipher_enc\n" );

    // ctx->encrypt: 0: decrypte, 1 : encrypte
    if ( 0 == ctx->encrypt )
    {
        printf( "mytest_cipher_enc decrypte\n" );
    }
    else if ( 1 == ctx->encrypt )
    {
        printf( "mytest_cipher_enc encrypte\n" );
    }
    return 1;
}

static const EVP_CIPHER EVP_mytest_c= 
{ 
    NID_aes_256_cbc, 
    16,
    16,
    0, 
    8, 
    mytest_init_key, 
    mytest_cipher_enc, 
    NULL, 
    1, 
    NULL, 
    NULL, 
    NULL, 
    NULL 
};

const EVP_CIPHER *EVP_mytest_cipher(void) 
{ 
    printf( "EVP_mytest_cipher\n" );
    return(&EVP_mytest_c); 
} 


/*  选择对称计算函数 */ 
static int cipher_nids[] = 
  { NID_aes_256_cbc, 0 };
  

static int mytest_ciphers(ENGINE *e, const EVP_CIPHER **cipher,  const int **nids, int nid) 
{
    printf( "mytest_ciphers\n" );

    if(cipher==NULL) 
    { 
        printf( "mytest_ciphers is null\n" );
        
        *nids = cipher_nids;
        return (sizeof(cipher_nids)-1)/sizeof(cipher_nids[0]); 
    } 
    printf( "mytest_ciphers is not null\n" );
    switch (nid)
    {
        case NID_aes_256_cbc:
            printf( "mytest_ciphers NID_aes_256_cbc\n" );
            *cipher = EVP_mytest_cipher();
            break;            
        default:        
            *cipher = NULL;
            break;
    }
    
    return 1; 
} 

bind_mytest(ENGINE *e)
{
	//const RSA_METHOD *meth1;
	if(!ENGINE_set_id(e, engine_mytest_id)
		|| !ENGINE_set_name(e, engine_mytest_name)
		//|| !ENGINE_set_RSA(e, &mytest_rsa)
		|| !ENGINE_set_ciphers(e, mytest_ciphers)
		//|| !ENGINE_set_digests(e, mytest_digests)
		|| !ENGINE_set_destroy_function(e, mytest_destroy)
		|| !ENGINE_set_init_function(e, mytest_init)
		|| !ENGINE_set_finish_function(e, mytest_finish)
		/* || !ENGINE_set_ctrl_function(e, mytest_ctrl) */
		/* || !ENGINE_set_cmd_defns(e, mytest_cmd_defns) */
		|| !ENGINE_set_load_privkey_function(e, mytest_load_privkey)
		|| !ENGINE_set_load_pubkey_function(e, mytest_load_pubkey) )
	{
	    printf( "bind_sm4 error\n" );
		return 0;
	}
	
	/* Ensure the sm4 error handling is set up */
	ERR_load_mytest_strings();
	return 1;
}

    NID_aes_256_cbc 为算法NID(可用SSL_get_cipher函数获取当前使用的数据加密算法。这个要替换为自己使用的算法的NID), 在bind engine时, 有调用:

    ENGINE_set_ciphers(e, mytest_ciphers)
  

    
    EVP测试代码调用过程如下:
    
    ciph_ctx = EVP_CIPHER_CTX_new();
    _ASSERT(ciph_ctx != NULL);
    //EVP_CIPHER_CTX_init(ciph_ctx); // new后会自动调用init - memset(0)
    
    ret = EVP_EncryptInit_ex(ciph_ctx, cipher, engine, aes_key, aes_iv);
    if (ret != 1) {        
        return -1;
    }
    ret = EVP_EncryptUpdate(ciph_ctx, aes_out, &upd_outlen, pin, pin_len);
    if (ret != 1) {        
        return -2;
    }
    ret = EVP_EncryptFinal(ciph_ctx, aes_out + upd_outlen, &upd_outlen);
    if (ret != 1) {        
        return -3;
    }
    
    EVP_CIPHER_CTX_free(ciph_ctx);
    
    
    EVP_EncryptInit_ex会调用到FMC_ENG_evp_cipher_init
    EVP_EncryptUpdate和EVP_EncryptFinal会调用到FMC_ENG_evp_cipher_do_cipher
    EVP_CIPHER_CTX_free会调用到EVP_CIPHER_CTX_cleanup->FMC_ENG_evp_cipher_cleanup
    
    中间遇到几个问题:
    1 Update不能如此调用:
    while(pin_len > 0) {

        ret = EVP_EncryptUpdate(&ciph_ctx, aes_out + aes_etotal, &upd_outlen, pin, pin_len);
        if (ret != 1) {    
            break;
        } else {
            // yes, correct encypt next block
        }        
        
        aes_etotal += upd_outlen;

        // 后面的属于画蛇添足
        _ASSERT(aes_etotal <= aes_outlen);

        if(upd_outlen == 0) { // all block_size aligned block completed.                                    
            break;
        }

        if(upd_outlen >= pin_len) {
            pin_len = 0;            
            break;    // all encryptupdate completed
        } else {            
            pin_len -= upd_outlen;
            pin += upd_outlen;    
        }        
    }
    
    如果这样, 测试时, plaintext数据长度为0x33, 第一次update后, 返回已加密
    长度为0x30, 接着调update, 就有3个字节被放到了ctx->buf中, 返回的update
    长度为0. 
    然后调用Final函数, 如此: 
    ret = EVP_EncryptFinal(&ciph_ctx, aes_out + aes_etotal, &upd_outlen);
    这样, 又有3个字节会被加入到ctx->buf中, 最后被拷贝到ctx->final buffer
    中, 执行padding方案后, 被加密, 整个加密的长度变成了0x36.
    
    因为测试时, 用硬件Engine和Openssl只带Engine的方式一样, 所以加密出来的
    数据一样, 通过检测. 但解密后数据长度为0x36个字节, 晕菜. 被自己摆了一道.
    
    2 ctx_size
    开始时搞不明白FMC_ENG_evp_cipher::ctx_size是用来干什么的,    后来搞明白了.
    其实这里不用像openssl那样定义, openssl是在EncryptInit是, 按照这个大小, 
    分配了一个AES_KEY+x个字节的memory, 用来存放EncryptInit是用用户输入的
    key产生一个aes key(包含n个roundtable,roundtable用来在aes加密是进行置换, 
    aes的核心就是置换和移位). 我们的硬件引擎之需要分配ctx->cipher->key_size
    个大小的内存, memcpy key到里面即可, 在do_cipher时, key就从里面取出. 
    忘记写了, 分配的内存地址赋值给ctx->cipher_data指针.
    
    3 padding
    在想如何替换Engine时, 主要围绕硬件加密卡提供的API进行考虑, 首先想到的
    就是padding方案. 因为硬件加密卡要求输入的数据必须是按照block_size对齐的
    openssl的evp函数是否会自动进行padding呢? 答案是 - yes.
    
    如: 在输入数据为0x33长度是, update加密, 先加密前面0x30个, 执行final时
    会执行padding方案, 此时ctx->final_used标识会被置1. 调用do_cipher时, 
    传入的数据已经是按照block_size对齐的了.
    
    OpenSSL的Padding方案: 
    差几个对齐, payload后面就填几, 如果对齐了, 就加一个完整的block.
    所以, 加密出来的数据, 可能会比输入数据多一个block, 在分配ciphertext的
    buffer时, 需要注意.
    
    4 编译优化
    为了看openssl的padding方案, 跟到openssl的代码中去, 发现老是符号与代码
    不匹配, 还以为自己不小心动到了openssl的代码, 反复几次重新编译openssl
    均不能解决问题. 百思不得其解, 后trace到汇编里面, 发现在指定padding方案
    时, for(n=bl; n<b; n++) out[n] = n; 被优化成memset(out+n, n, b-n);
    原来是openssl的编译mak文件中, 指定了Ox优化编译选项, 将该选项改为Od, 
    重新编译, OK.


http://www.cnblogs.com/crunchyou/archive/2013/01/19/2867735.html


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值