定義:
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.