zoukankan      html  css  js  c++  java
  • [原]替换OpenSSL Engine为硬件加密卡之替换EVP_CIPHER结构

    定义:
        static const EVP_CIPHER FMC_ENG_evp_cipher=
        {
            NID_aes_128_cbc,                /*nid*/
            16,                                /*block_size*/
            16,                                /*key_len*/
            16,                                /*iv_len*/
            EVP_CIPH_CBC_MODE,                /*Various flags*/
            FMC_ENG_evp_cipher_init,        /*init*/
            FMC_ENG_evp_cipher_do_cipher,    /*do_cipher*/
            FMC_ENG_evp_cipher_cleanup,        /*cleanup*/
            sizeof(AES_KEY) + 16,            /*ctx_size*/
            NULL,                            /*set_asn1_parameters*/
            NULL,                            /*get_asn1_parameters*/
            NULL,                            /*Miscellaneous operations*/
            NULL                            /*app_data*/
        };
        首先解释:
        NID_aes_128_cbc 为算法NID, 在bind engine时, 有调用:
        ret = ENGINE_set_ciphers(e, FMC_ENG_ciphers);
        // 参见: crypto\engine\eng_cryptodev.c  line:608
        static int

        FMC_ENG_ciphers(ENGINE *e, const EVP_CIPHER **cipher, const int **nids, int nid)
        {   
            if(cipher == NULL) // yes, refer to get_cryptodev_ciphers
            {
                *nids = FMC_ENG_cipher_nids;
                return (sizeof(FMC_ENG_cipher_nids)-1)/sizeof(FMC_ENG_cipher_nids[0]);
            }

            switch (nid)
            {
            case NID_aes_128_cbc:
                *cipher = FMC_ENG_get_evp_cipher();
                break;           
            default:           
                *cipher = NULL;
                break;
            }
            return (*cipher != NULL);
        }
        const EVP_CIPHER *FMC_ENG_get_evp_cipher(void)
        {   
            return(&FMC_ENG_evp_cipher);
        }
        在调用EVP_CIPHER* ciphter = (EVP_CIPHER *)(EVP_aes_128_cbc());
        时, FMC_ENG_ciphers函数会被调用, 就在此时, 返回我们自己定义的EVP_CIPHER
        结构.
       
        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.

  • 相关阅读:
    [计算机基础]回调函数
    [Android学习笔记]双缓冲绘图技术
    [数据结构和算法]折半插入排序算法笔记
    为Eclipse添加Java和Android SDK源代码
    Eclipse Tips
    Eclipse常用插件
    [数据结构]基本概念2
    Nginx中让 重写后的路径 自动增加斜线 /
    mysql 断电 启动不了 start: Job failed to start
    uglifyjs 压缩js
  • 原文地址:https://www.cnblogs.com/crunchyou/p/2867735.html
Copyright © 2011-2022 走看看