歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux資訊 >> Linux文化

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沒有錯誤擴散問題。


Copyright © Linux教程網 All Rights Reserved