openssl 编程¶
2 OpenSSL 简介¶
#include <openssl/crypto.h>
#include <stdio.h>
int main()
{
// 打印openSSL 版本号
printf( "%s \n ", SSLeay_version(SSLEAY_VERSION));
}
3 堆栈¶
man DEFINE_STACK_OF
- https://www.openssl.org/docs/man1.1.1/man3/sk_TYPE_num.html
- https://blog.csdn.net/as3luyuan123/article/details/17081581
3.1 openssl堆栈¶
堆栈是一种先进后出的数据结构。 openssl大量采用堆栈来存放数据。
它实现了一个通用的堆栈,可以方便的存储任意数据。
它实现了许多基本的堆栈操作,主要有:
构建新堆栈(sk_new_null,sk_new)、
堆栈拷贝(sk_dup)、
插入数据(sk_insert)、 删除数据(sk_delete)、
查找数据(sk_find,sk_find_ex)、
入栈(sk_push)、 出栈(sk_pop)、
获取堆栈元素个数(sk_num)、 获取堆栈值(sk_value)、
设置堆栈值(sk_set)和堆栈排序(sk_sort)。
// 通用堆栈
//创建一个空栈,参数可指定排序方法,因为openssl不知道里面存放的是什么类型的数据,
// 所以排序方法需要用户实现,当参数为NULL,同下方法
OPENSSL_STACK *OPENSSL_sk_new(OPENSSL_sk_compfunc cmp);
//创建一个空栈,
OPENSSL_STACK *OPENSSL_sk_new_null(void);
//释放栈,并不释放栈内元素内存
void OPENSSL_sk_free(OPENSSL_STACK *);
//删除并释放所有栈内元素,最后释放栈,可以指定回调函数,栈每次释放一个元素都会回调该回调函数
void OPENSSL_sk_pop_free(OPENSSL_STACK *st, void (*func) (void *));
//栈深copy,
OPENSSL_STACK *OPENSSL_sk_deep_copy( const OPENSSL_STACK *,
OPENSSL_sk_copyfunc c,
OPENSSL_sk_freefunc f);
//在栈指定位置插入元素,成功返回该栈所有元素的个数
int OPENSSL_sk_insert(OPENSSL_STACK *sk, const void *data, int where);
//删除栈指定位置元素,成功返回删除的该元素
void *OPENSSL_sk_delete(OPENSSL_STACK *st, int loc);
//删除栈指定元素,成功返回删除的该元素
void *OPENSSL_sk_delete_ptr(OPENSSL_STACK *st, const void *p);
//在栈中查找指元素,成功返回该元素位置
int OPENSSL_sk_find(OPENSSL_STACK *st, const void *data);
//同上,
int OPENSSL_sk_find_ex(OPENSSL_STACK *st, const void *data);
//在栈顶添加一个元素,成功返回栈元素总数
int OPENSSL_sk_push(OPENSSL_STACK *st, const void *data);
//在栈位置0次添加一个元素,类似 insert(st,0);
int OPENSSL_sk_unshift(OPENSSL_STACK *st, const void *data);
//移出栈位置0处的元素,类似pop
void *OPENSSL_sk_shift(OPENSSL_STACK *st);
//在栈顶移出一个元素,并释放该元素内存,
void *OPENSSL_sk_pop(OPENSSL_STACK *st);
//设置栈元素为0,不释放栈
void OPENSSL_sk_zero(OPENSSL_STACK *st);
//设置栈的排序方法
OPENSSL_sk_compfunc OPENSSL_sk_set_cmp_func( OPENSSL_STACK *sk,
OPENSSL_sk_compfunc cmp);
//copy 栈
OPENSSL_STACK *OPENSSL_sk_dup(const OPENSSL_STACK *st);
//栈排序
void OPENSSL_sk_sort(OPENSSL_STACK *st);
//栈是否排序
int OPENSSL_sk_is_sorted(const OPENSSL_STACK *st);
3.2 数据结构¶
openssl堆栈数据结构在stack.h中定义如下:
typedef struct stack_st
{
int num;
char **data;
int sorted;
int num_alloc;
int (*comp)(const char * const *, const char * const *);
} STACK;
各项意义如下:
num : 堆栈中存放数据的个数。
data : 用于存放数据地址,每个数据地址存放在data[0]到data[num-1]中。
sorted : 堆栈是否已排序,如果排序则值为1,否则为0,堆栈数据一般是无序的,
只有当用户调用了sk_sort操作,其值才为1。
comp : 堆栈内存放数据的比较函数地址,此函数用于排序和查找操作;
当用户生成一个新堆栈时,可以指定comp为用户实现的一个比较函数;
或当堆栈已经存在时通过调用sk_set_cmp_func函数来重新指定比较函数。
注意,用户不需要调用底层的堆栈函数(sk_sort、sk_set_cmp_func等),而是调用他通过宏实现的各个函数。
3.3 源码¶
openssl堆栈实现源码位于crypto/stack目录下。下面分析了部分函数。
1) sk_set_cmp_func
此函数用于设置堆栈存放数据的比较函数。
由于堆栈不知道用户存放的是什么数据,
所以,比较函数必须由用户自己实现。
2) sk_find
根据数据地址来查找它在堆栈中的位置。
当堆栈设置了比较函数时,它首先对堆栈进行排序,然后通过二分法进行查找。
如果堆栈没有设置比较函数,它只是简单的比较数据地址来查找.
3)sk_sort
本函数对堆栈数据排序。
它首先根据sorted来判断是否已经排序,
如果未排序则调用了标准C函数qsort进行快速排序。
4)sk_pop_free
本函数用于释放堆栈内存放的数据以及堆栈本身,
它需要一个由用户指定的针对具体数据的释放函数。
如果用户仅调用sk_free函数,则只会释放堆栈本身所用的内存,而不会释放数据内存。
3.4 定义用户自己的堆栈¶
STACK_OF(TYPE)
DEFINE_STACK_OF(TYPE)
DEFINE_STACK_OF_CONST(TYPE)
DEFINE_SPECIAL_STACK_OF(FUNCTYPE, TYPE)
DEFINE_SPECIAL_STACK_OF_CONST(FUNCTYPE, TYPE)
/*
test/stack_test.c
*/
3.5 编程示例¶
3.5.1 通用堆栈¶
#include <stdio.h>
#include <openssl/safestack.h>
void popfreeCallBack(void *arg)
{
printf("pop and free %d \n",*(int*)arg);
}
/* ****************
* 通用链表
***************** */
int test_stack()
{
//创建一个空的栈
OPENSSL_STACK *st = OPENSSL_sk_new_null();
int a = 10;
OPENSSL_sk_push(st, &a);
int b = 100;
OPENSSL_sk_push(st, &b);
char *c = "hello";
OPENSSL_sk_push(st, c);
char d[10] = {0};
OPENSSL_sk_push(st, d);
char e = 'A';
OPENSSL_sk_push(st, &e);
//返回栈内数据个数
if (OPENSSL_sk_num(st) == 5) {
printf("sk_num PASS\n");
}
//获取指定index数据
int *getb = OPENSSL_sk_value(st, 1);
if (*getb==b) {
printf("sk_value PASS\n");
}
//获取指定数据,返回index
if (OPENSSL_sk_find(st,"hello") == 2) {
printf("sk_find PASS \n");
}
//在位置2插入一个 数据 TAOBAO,返回总数
if (OPENSSL_sk_insert(st, "TAOBAO", 2) == 6) {
printf("sk_insert PASS\n");
}
int ff = 88;
OPENSSL_sk_push(st, &ff);
//在栈顶移出一个数据, 返回删除的元素
char *popDT = OPENSSL_sk_pop(st);
if (*popDT==ff) {
printf("sk_pop PASS\n");
}
//从栈中移出所有的元素,并释放内存,并且释放st;
//每删除一个元素,回调一次popfreeCallBack回调函数
OPENSSL_sk_pop_free(st, popfreeCallBack);
return 0;
}
int main()
{
test_stack(); // 通用链表
return 0;
}
3.5.2 自定义堆栈¶
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/safestack.h>
//#define sk_Student_new(st) OPENSSL_sk_new(st)
//#define sk_Student_new_null() OPENSSL_sk_new_null()
//#define sk_Student_free(st) OPENSSL_sk_free(st)
//#define sk_Student_num(st) OPENSSL_sk_num(st)
//#define sk_Student_value(st, i) OPENSSL_sk_value((st), i)
//#define sk_Student_set(st, i, val) OPENSSL_sk_set((st), (i), (val))
//#define sk_Student_zero(st) OPENSSL_sk_zero(Student, (st))
//#define sk_Student_push(st, val) OPENSSL_sk_push((st),(val))
//#define sk_Student_unshift(st, val) OPENSSL_sk_unshift((st), (val))
//#define sk_Student_find(st, val) OPENSSL_sk_find((st), (val))
//#define sk_Student_delete(st, i) OPENSSL_sk_delete((st), (i))
//#define sk_Student_delete_ptr(st, ptr) OPENSSL_sk_delete_ptr((st), (ptr))
//#define sk_Student_insert(st, val, i) OPENSSL_sk_insert((st), (val), (i))
//#define sk_Student_set_cmp_func(st, cmp) OPENSSL_sk_set_cmp_func((st), (cmp))
//#define sk_Student_dup(st) OPENSSL_sk_dup(Student, st)
//#define sk_Student_pop_free(st, free_func) OPENSSL_sk_pop_free((st), (free_func))
//#define sk_Student_shift(st) OPENSSL_sk_shift(st)
//#define sk_Student_pop(st) OPENSSL_sk_pop(st)
//#define sk_Student_sort(st) OPENSSL_sk_sort(st)
typedef struct Student_st {
char *name;
int age;
char *otherInfo;
}Student;
typedef STACK_OF(Student) Students;
DEFINE_STACK_OF(Student) // 重点
Student *Student_Malloc()
{
Student *a=malloc(sizeof(Student));
a->name=malloc(20);
strcpy(a->name,"zcp");
a->otherInfo=malloc(20);
strcpy(a->otherInfo,"no info");
return a;
}
void Student_Free(Student *a)
{
free(a->name);
free(a->otherInfo);
free(a);
}
static int Student_cmp(const Student * const *a, const Student *const *b)
{
int ret;
printf("%s cpmp %s \n",(*a)->name,(*b)->name);
ret=strcmp((*a)->name,(*b)->name);
return ret;
}
int main()
{
Students *s, *snew;
Student *s1, *one, *s2;
int i, num;
s = sk_Student_new_null();
//snew = sk_Student_new((sk_Student_compfunc)Student_cmp);
snew = sk_Student_new(Student_cmp);
s2=Student_Malloc();
sk_Student_push(snew,s2);
i=sk_Student_find(snew,s2);
printf("at : %d \n" ,i );
s1=Student_Malloc();
sk_Student_push(s,s1);
num=sk_Student_num(s);
for(i=0;i<num;i++) {
one=sk_Student_value(s,i);
printf("student name : %s\n",one->name);
printf("sutdent age : %d\n",one->age);
printf("student otherinfo : %s\n\n",one->otherInfo);
}
sk_Student_pop_free(s,Student_Free);
sk_Student_pop_free(snew,Student_Free);
return 0;
}
4 哈希表¶
man lhash
4.1 哈希表¶
在一般的数据结构如线性表和树中,录在结构中的相对位置是与记录的关键字之间不存在确定的关系,
在结构中查找记录时需进行一系列的关键字比较.
这一类查找方法建立在"比较"的基础上, 查找的效率与比较次数密切相关。
理想的情况是能直接找到需要的记录,因此必须在记录的存储位置和它的关键字之间建立确定的对应关系,
使每个关键字和结构中一个唯一的存储位置相对应.
在查找时,只需根据这个对应关系找到给定值.
这种对应关系既是哈希函数,按这个思想建立的表为哈希表。
哈希表存在冲突现象:
不同的关键字可能得到同一哈希地址。
在建造哈希表时不仅要设定一个好的哈希函数,而且要设定一种处理冲突的方法。
4.2 哈希表数据结构¶
openssl函数使用哈希表来加快查询操作,并能存放任意形式的数据,比如配置文件的读取、内存分配中被分配内存的信息等。其源码在crypto/lhash目录下。
openssl中的哈希表数据结构在lhash.h中定义如下:
typedef struct lhash_node_st {
void *data;
struct lhash_node_st *next;
#ifndef OPENSSL_NO_HASH_COMP
unsigned long hash;
#endif
} LHASH_NODE;
本结构是一个单链表。
其中, data用于存放数据地址,
next为下一个数据地址,
hash为数据哈希计算值。
typedef struct lhash_st {
LHASH_NODE **b;
LHASH_COMP_FN_TYPE comp;
LHASH_HASH_FN_TYPE hash;
unsigned int num_nodes;
unsigned int num_alloc_nodes;
unsigned int p;
unsigned int pmax;
unsigned long up_load; /* load times 256 */
unsigned long down_load; /* load times 256 */
unsigned long num_items;
unsigned long num_expands;
unsigned long num_expand_reallocs;
unsigned long num_contracts;
unsigned long num_contract_reallocs;
unsigned long num_hash_calls;
unsigned long num_comp_calls;
unsigned long num_insert;
unsigned long num_replace;
unsigned long num_delete;
unsigned long num_no_delete;
unsigned long num_retrieve;
unsigned long num_retrieve_miss;
unsigned long num_hash_comps;
int error;
} LHASH;
其中,
b指针数组用于存放所有的数据,数组中的每一个值为数据链表的头指针;
comp用于存放数据比较函数地址;
hash用于存放计算哈希值函数的地址;
num_nodes为链表个数;
num_alloc_nodes为b分配空间的大小。
基本的结构如下示图

4.3 函数说明¶
1) LHASH *lh_TYPE_new(LHASH_HASH_FN_TYPE h, LHASH_COMP_FN_TYPE c)
功能 : 生成哈希表
源文件: lhash.c
说明 : 输入参数
h 为哈希函数,
c 为比较函数。
这两个函数都是回调函数。
因为哈希表用于存放任意的数据结构,
哈希表存放、查询、删除等操作都需要比较数据和进行哈希运算,
而哈希表不知道用户数据如何进行比较,也不知道用户数据结构中需要对哪些关键项进行散列运算.
所以,用户必须提供这两个回调函数。
2) void *lh_TYPE_delete(LHASH *lh, const void *data)
源文件:lhash.c
功能:删除散列表中的一个数据
说明:data为数据结构指针。
3) void lh_TYPE_doall(LHASH *lh, LHASH_DOALL_FN_TYPE func)
源文件:lhash.c
功能:处理哈希表中的所有数据
说明:func为外部提供的回调函数,本函数遍历所有存储在哈希表中的数据,每个数据被func处理。
4) void lh_TYPE_doall_arg(LHASH *lh, LHASH_DOALL_ARG_FN_TYPE func, void *arg)
源文件:lhash.c
功能:处理哈希表中所有数据
说明:此参数类似于lh_doall 函数, func为外部提供的回调函数,
arg为传递给func函数的参数。
本函数遍历所有存储在哈希表中的数据,每个数据被func处理。
5) void lh_TYPE_free(LHASH *lh)
源文件:lhash.c
功能:释放哈希表。
6)void *lh_TYPE_insert(LHASH *lh, void *data)
源文件:lhash.c
功能:往哈希表中添加数据。
说明:data为需要添加数据结构的指针地址。
7)void *lh_retrieve(LHASH *lh, const void *data)
源文件:lhash.c
功能:查询数据。
说明:从哈希表中查询数据,data为数据结构地址,
此数据结构中必须提供关键项(这些关键项对应于用户提供的哈希函数和比较函数)以供查询,
如果查询成功,返回数据结构的地址,否则返回NULL。
比如SSL握手中服务端查询以前存储的SESSION时,
它需要提供其中关键的几项:
SSL_SESSION *ret = NULL,data;
data.ssl_version = s->version;
data.session_id_length = len;
memcpy(data.session_id,session_id,len);
ret = (SSL_SESSION *)lh_retrieve(s->ctx->sessions,&data);
8)void lh_node_stats_bio(const LHASH *lh, BIO *out)
源文件:lh_stats.c
功能:将哈希表中每个链表下的数据状态输出到BIO中。
9)void lh_node_stats(const LHASH *lh, FILE *fp)
源文件:lh_stats.c
功能:将哈希表中每个链表下数据到个数输出到FILE中。
说明:此函数调用了lh_node_stats_bio函数。
10)void lh_node_usage_stats_bio(const LHASH *lh, BIO *out)
源文件:lh_stats.c
功能:将哈希表的使用状态输出到BIO中。
11)void lh_node_usage_stats(const LHASH *lh, FILE *fp)
源文件:lh_stats.c
功能:将哈希表的使用状态输出到FILE中
说明:此函数调用了lh_node_usage_stats_bio函数
12)unsigned long lh_num_items(const LHASH *lh)
源文件:lhash. c
功能:获取哈希表中元素的个数。
13)void lh_stats_bio(const LHASH *lh, BIO *out)
源文件:lh_stats.c
功能:输出哈希表统计信息到BIO中
14)void lh_stats(const LHASH *lh, FILE *fp)
源文件:lh_stats.c
功能:打印哈希表的统计信息,此函数调用了lh_stats_bio。
15)unsigned long lh_strhash(const char *c)
源文件:lhash.c
功能:计算文本字符串到哈希值。
DEFINE_LHASH_OF(Student);
IMPLEMENT_LHASH_DOALL_ARG(Student, int);
4.4 编程示例¶
#include <string.h>
#include <openssl/lhash.h>
typedef struct Student_st
{
char name[20];
int age;
char otherInfo[200];
}Student;
static int Student_cmp(const Student *a, const Student *b)
{
const char *namea = a->name;
const char *nameb = b->name;
return strcmp(namea,nameb);
}
/* 打印每个值*/
static void PrintValue(Student *a)
{
printf("\nname :%s\n",a->name);
printf("age :%d\n",a->age);
printf("otherInfo : %s\n",a->otherInfo);
}
static void PrintValue_arg(Student *a,int *b)
{
int flag = *b;
printf("\n用户输入参数为:%d\n",flag);
printf("name :%s\n",a->name);
printf("age :%d\n",a->age);
printf("otherInfo : %s\n",a->otherInfo);
}
DEFINE_LHASH_OF(Student);
IMPLEMENT_LHASH_DOALL_ARG(Student, int);
int main()
{
int flag = 11;
Student s1 = {"zcp",28,"hu bei"},
s2 = {"forxy",28,"no info"},
s3 = {"skp",24,"student"},
s4 = {"zhao_zcp",28,"zcp's name"},
*s5;
Student *data;
LHASH_OF(Student) *h = lh_Student_new(NULL,Student_cmp);
if(h == NULL) {
printf("err.\n");
return -1;
}
data = &s1;
lh_Student_insert(h,data);
data = &s2;
lh_Student_insert(h,data);
data = &s3;
lh_Student_insert(h,data);
data = &s4;
lh_Student_insert(h,data);
/* 打印*/
lh_Student_doall(h,PrintValue);
printf("\n\n");
lh_Student_doall_int(h,PrintValue_arg,&flag);
data = lh_Student_retrieve(h,(const void*)"skp");
if(data == NULL) {
printf("can not look up skp!\n");
lh_Student_free(h);
return -1;
}
s5 = data;
printf("\n\nstudent name :%s\n",s5->name);
printf("sutdent age : %d\n",s5->age);
printf("student otherinfo :%s\n",s5->otherInfo);
lh_Student_free(h);
//getchar();
return 0;
}
5 内存分配¶
man openssl_malloc
5.1 openssl内存分配¶
用户在使用内存时,容易犯的错误就是内存泄露。
当用户调用内存分配和释放函数时,查找内存泄露比较麻烦。
openssl提供了内置的内存分配/释放函数。
如果用户完全调用openssl的内存分配和释放函数,可以方便的找到内存泄露点。
openssl分配内存时,在其内部维护一个内存分配哈希表,用于存放已经分配但未释放的内存信息。
当用户申请内存分配时,在哈希表中添加此项信息,内存释放时删除该信息。
当用户通过openssl函数查找内存泄露点时,只需查询该哈希表即可。
用户通过openssl回调函数还能处理那些泄露的内存。
openssl供用户调用的内存分配等函数主要在crypto/mem.c中实现,其内置的分配函数在crypto/mem_dbg.c中实现。
默认情况下 mem.c 中的函数调用 mem_dbg.c 中的实现。
如果用户实现了自己的内存分配函数以及查找内存泄露的函数,
可以通过调用 CRYPTO_set_mem_functions 函数和 CRYPTO_set_mem_debug_functions 函数来设置。
5.2 内存数据结构¶
openssl内存分配数据结构是一个内部数据结构,定义在crypto/mem_dbg.c中。
如下所示:
typedef struct app_mem_info_st {
unsigned long thread;
const char *file;
int line;
const char *info;
struct app_mem_info_st *next; /* tail of thread's stack */
int references;
} APP_INFO;
typedef struct mem_st {
void *addr;
int num;
const char *file;
int line;
unsigned long thread;
unsigned long order;
time_t time;
APP_INFO *app_info;
} MEM;
各项意义:
addr : 分配内存的地址。
num : 分配内存的大小。
file : 分配内存的文件。
line : 分配内存的行号。
thread : 分配内存的线程ID。
order : 第几次内存分配。
time : 内存分配时间。
app_info : 用于存放用户应用信息,为一个链表,里面存放了文件、行号以及线程ID等信息。
references : 被引用次数。
5.3 主要函数¶
1) CRYPTO_mem_ctrl
本函数主要用于控制内存分配时,是否记录内存信息。
如果不记录内存信息,将不能查找内存泄露。
开启内存记录调用CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON),
关闭内存记录调用CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF)。
一旦CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON)被调用,
直到用户调用CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF)前,
用户所有的opessl内存分配都会被记录。
2) CRYPTO_is_mem_check_on
查询内存记录标记是否开启。
3)CRYPTO_dbg_malloc
本函数用于分配内存空间,如果内存记录标记开启,则记录用户申请的内存。
当需要记录内存信息时,该函数本身也需要申请内存插入哈希表,为了防止递归申请错误,
它申请内存记录信息前必须暂时关闭内存记录标记,申请完毕再放开。
4)CRYPTO_dbg_free
释放内存,如果内存记录标记开启,还需要删除哈希表中对应的记录。
5)CRYPTO_mem_leaks
将内存泄露输出到BIO中。
6)CRYPTO_mem_leaks_fp
将内存泄露输出到FILE中(文件或者标准输出),
该函数调用了CRYPTO_mem_leaks。
7)CRYPTO_mem_leaks_cb
处理内存泄露,输入参数为用户自己实现的处理内存泄露的函数地址。
该函数只需要处理一个内存泄露,
openssl通过lh_doall_arg调用用户函数来处理所有记录(泄露的内存)。
5.4 编程示例¶
1¶
#include <string.h>
#include <openssl/crypto.h>
int main()
{
char *p;
int i;
p=OPENSSL_malloc(4);
//p=OPENSSL_remalloc(p,40);
p=OPENSSL_realloc(p,32);
for(i=0;i<32;i++)
memset(&p[i],i,1);
/* realloc时将以前的内存区清除(置乱) */
//p=OPENSSL_realloc_clean(p,32,77);
//p=OPENSSL_remalloc(p,40);
//OPENSSL_malloc_locked(3);
OPENSSL_free(p);
return 0;
}
/*
上述示例使用了基本的openssl内存分配和释放函数。
OPENSSL_malloc: 分配内存空间。
OPENSSL_remalloc: 重新分配内存空间。
OPENSSL_realloc_clean:重新分配内存空间,将老的数据进行拷贝,置乱老的数据空间并释放。
OPENSSL_malloc_locked: 与锁有关。
OPENSSL_free: 释放空间。
*/
2¶
#include <string.h>
#include <openssl/crypto.h>
int main()
{
char *p;
int i;
p=OPENSSL_malloc(4);
//p=OPENSSL_remalloc(p,40);
p=OPENSSL_realloc(p,32);
for(i=0;i<32;i++)
memset(&p[i],i,1);
/* realloc时将以前的内存区清除(置乱) */
//p=OPENSSL_realloc_clean(p,32,77);
//p=OPENSSL_remalloc(p,40);
//OPENSSL_malloc_locked(3);
OPENSSL_free(p);
return 0;
}
/*
上述示例使用了基本的openssl内存分配和释放函数。
OPENSSL_malloc: 分配内存空间。
OPENSSL_remalloc: 重新分配内存空间。
OPENSSL_realloc_clean:重新分配内存空间,将老的数据进行拷贝,置乱老的数据空间并释放。
OPENSSL_malloc_locked: 与锁有关。
OPENSSL_free: 释放空间。
*/
6 动态模块加载¶
6.1 动态库加载¶
动态库加载函数能让用户在程序中加载所需要的模块,各个平台下的加载函数是不一样的。
动态加载函数一般有如下功能:
1)加载动态库
windows下的函数LoadLibraryA ;
linux 下的函数dlopen ;
这些函数一般需要动态库的名字作为参数。
2)获取函数地址
windows下的函数 GetProcAddress
linux 下的函数 dlsym。
这些函数一般需要函数名作为参数,回函数地址。
3)卸载动态库
windows下的函数 FreeLibrary
linux 下的函数 dlclose。
6.2 DSO概述¶
DSO 可以让用户动态加载动态库来进行函数调用。
各个平台下加载动态库的函数是不一样的,
openssl的DSO对各个平台台下的动态库加载函数进行了封装,增加了源码的可移植性。
Openssl的DSO功能主要用于动态加载压缩函数(ssl协议)和 engine(硬件加速引擎)。
Openssl的DSO功能除了封装基本的功能外还有其他辅助函数,
主要用于解决不同系统下路径不同的表示方式以及动态库全名不一样的问题。
比如windows系统下路径可以用“\\”和“/”表示,而linux下只能使用“/”;
windows下动态库的后缀为 .dll 而 linux 下动态库名字一般为 libxxx.so。
6.3 数据结构¶
dso数据结定义在crypto/dso/dso.h中,如下所示:
struct dso_st {
DSO_METHOD *meth;
STACK *meth_data;
int references;
int flags;
CRYPTO_EX_DATA ex_data;
DSO_NAME_CONVERTER_FUNC name_converter;
DSO_MERGER_FUNC merger;
char *filename;
char *loaded_filename;
};
meth: 指出了操作系统相关的动态库操作函数。
meth_data:堆栈中存放了加载动态库后的句柄。
references: 引用计数,
DSO_new 时置1,
DSO_up_ref 时加1,
DSO_free 时减1。
当调用DSO_free时,
只有当前的 references 为 1 时
才真正释放meth_data中存放的句柄。
flags: 与加载动态库时加载的文件名以及加载方式有关,用于DSO_ctrl函数。
DSO_convert_filename: 当加载动态库时会调用DSO_convert_filename函数来确定所加载的文件.
而DSO_convert_filename函数会调用各个系统
自己的convert函数来获取这个文件名。
对于flags有三种种操作命令:设置、读取和或的关系,对应定义如下:
#define DSO_CTRL_GET_FLAGS 1
#define DSO_CTRL_SET_FLAGS 2
#define DSO_CTRL_OR_FLAGS 3
而flags可以设置的值有如下定义:
#define DSO_FLAG_NO_NAME_TRANSLATION 0x01
#define DSO_FLAG_NAME_TRANSLATION_EXT_ONLY 0x02
#define DSO_FLAG_UPCASE_SYMBOL 0x10
#define DSO_FLAG_GLOBAL_SYMBOLS 0x20
意义说明如下:
DSO_FLAG_NO_NAME_TRANSLATION
加载的文件名与指定的文件名一致,不加后缀.dll(windows)或.so(linux或unix)。
DSO_FLAG_NAME_TRANSLATION_EXT_ONLY
加载的文件名会加上lib串,比如用户加载eay32,真正加载时会加载libeay32(适用于linux或unix)。
DSO_FLAG_UPCASE_SYMBOL
适用于OpenVMS。
DSO_FLAG_GLOBAL_SYMBOLS
适用于unix,当在unix下调用加载函数dlopen时,参数会被或上RTLD_GLOBAL。
ex_data:扩展数据,没有使用。
name_converter::指明了具体系统需要调用的名字计算函数。
loaded_filename:指明了加载动态库的全名。
6.4 编程示例¶
1¶
//#include <openssl/dso.h>
#include "internal/dso.h"
#include <openssl/bio.h>
#include <stdio.h>
int main()
{
DSO *d;
void (*f1)();
void (*f2)();
BIO *(*BIO_newx)(BIO_METHOD *a);
BIO *(*BIO_freex)(BIO_METHOD *a);
BIO *test;
d=DSO_new();
//d=DSO_load(d,"libeay32",NULL,0);
d=DSO_load(d,"/usr/local/openssl/lib/libcrypto.so",NULL,0);
if ( d == NULL ) {
perror("not fond libcrypto.so");
return 1;
}
f1 = DSO_bind_func(d,"BIO_new");
f2 = DSO_bind_func(d,"BIO_free");
BIO_newx = (BIO *(*)(BIO_METHOD *))f1;
BIO_freex = (BIO *(*)(BIO_METHOD *))f2;
test = BIO_newx(BIO_s_file());
BIO_set_fp(test,stdout,BIO_NOCLOSE);
BIO_puts(test,"abd\n\n");
BIO_freex(test);
DSO_free(d);
return 0;
}
/*
本例动态加载libeay32动态库,获取BIO_new和BIO_free的地址并调用。
*/
2¶
//#include <openssl/dso.h>
#include "crypto/dso/dso_locl.h"
#include "internal/dso.h"
#include <openssl/bio.h>
int main()
{
DSO *d;
void (*f)();
BIO *(*BIO_newx)(BIO_METHOD *a);
BIO *test;
char *load_name;
const char *loaded_name;
int flags;
d = DSO_new();
#if 0
DSO_set_name_converter
DSO_ctrl(d,DSO_CTRL_SET_FLAGS,DSO_FLAG_NO_NAME_TRANSLATION,NULL);
DSO_ctrl(d,DSO_CTRL_SET_FLAGS,DSO_FLAG_NAME_TRANSLATION_EXT_ONLY,NULL);
DSO_ctrl(d,DSO_CTRL_SET_FLAGS,DSO_FLAG_GLOBAL_SYMBOLS,NULL);
/* 最好写成libeay32而不是libeay32.dll,
* 除非前面调用了DSO_ctrl(d,DSO_CTRL_SET_FLAGS,DSO_FLAG_NO_NAME_TRANSLATION,NULL)
* 否则它会加载libeay32.dll.dll
*/
load_name = DSO_merge(d,"libeay32","D:\\zcp\\OpenSSL\\openssl-0.9.8b\\out32dll\\Debug");
#endif
//d = DSO_load(d,"libeay32",NULL,0);
d = DSO_load(d,"/usr/local/openssl/lib/libcrypto.so",NULL,0);
if(d == NULL)
{
printf("err\n");
return -1;
}
//loaded_name = DSO_get_loaded_filename(d);
loaded_name = DSO_get_filename(d);
if(loaded_name != NULL)
{
printf("loaded file is %s\n",loaded_name);
}
flags = DSO_flags(d);
printf("current falgs is %d\n",flags);
DSO_up_ref(d);
f = (void (*)())DSO_bind_func(d,"BIO_new");
BIO_newx = (BIO *(*)(BIO_METHOD *))f;
test = BIO_newx(BIO_s_file());
BIO_set_fp(test,stdout,BIO_NOCLOSE);
BIO_puts(test,"abd\n\n");
BIO_free(test);
printf("filename : %s\n",d->filename);
printf("loaded_filename : %s\n",d->loaded_filename);
//printf("handle in dso number is : %d\n",d->meth_data->num);
DSO_free(d);
//DSO_free(d);
//printf("handle in dso number is : %d\n",d->meth_data->num);
return 0;
}
/*
本例主要演示了DSO的控制函数。
*/
7 抽象IO¶
7.1 openssl 抽象 IO¶
openssl 抽象 IO(I/O abstraction,即 BIO)是 openssl 对于 io 类型的抽象封装,
包括:内存、 文件、日志、标准输入输出、socket(TCP/UDP)、加/解密、摘要和 ssl 通道等。
Openssl BIO 通过回调函数为用户隐藏了底层实现细节,所有类型的 bio 的调用大体上是类似的。
Bio 中的数据能从一个 BIO 传送到另外一个 BIO 或者是应用程序。
其实包含了很多种接口,用通用的函数接口,主要控制在BIO_METHOD中的不同实现函数控制,
包括6种filter型和8种source/sink型。
source/sink类型的BIO是数据源,
例如,sokect BIO 和 文件BIO。
而filter BIO就是把数据从一个BIO转换到另外一个BIO或应用接口,
在转换过程中,些数据可以不修改(如信息摘要BIO),也可以进行转换.
例如在加密BIO中,如果写操作,数据就会被加密,如果是读操作,数据就会被解密。
BIO是封装了许多类型I/O接口细节的一种应用接口,
可以和SSL连接、 非加密的网络连接以及文件IO进行透明的连接。
BIO可以连接在一起成为一个BIO链(单个的BIO就是一个环节的BIO链的特例),
如下是BIO的结构定义,可以看到它有上下环节。
一个BIO链通常包括一个source BIO和一个或多个filter BIO,数据从第一个BIO读出或写入,
然后经过一系列BIO变化到输出(通常是一个source/sink BIO)。
BIO目录文件的简要说明:
bio.h: 主定义的头文件,包括了很多通用的宏的定义。
bio_lib.c: 主要的BIO操作定义文件,是比较上层的函数了。
bss_*系列:是soruce/sink型BIO具体的操作实现文件
bf_*系列: 是filter型BIO具体的操作实现文件
bio_err.c: 是错误信息处理文件
bio_cb.c: 是callback函数的相关文件
b_print.c: 是信息输出的处理函数文件
b_socket.c: 是Socket连接的一些相关信息处理文件
b_dump.c: 是对内存内容的存储操作处理
7.2 数据结构¶
BIO 数据结构主要有 2 个,在 crypto/bio.h 中定义如下:
- BIO_METHOD
typedef struct bio_method_st {
int type;
const char *name;
int (*bwrite)(BIO *, const char *, int);
int (*bread)(BIO *, char *, int);
int (*bputs)(BIO *, const char *);
int (*bgets)(BIO *, char *, int);
long (*ctrl)(BIO *, int, long, void *);
int (*create)(BIO *);
int (*destroy)(BIO *);
long (*callback_ctrl)(BIO *, int, bio_info_cb *);
} BIO_METHOD;
该结构定义了 IO 操作的各种回调函数,根据需要,具体的 bio 类型必须实现其中的一种或多种回调函数,各项意义如下:
type: 具体 BIO 类型;
name: 具体 BIO 的名字;
bwrite: 具体 BIO 写操作回调函数;
bread: 具体 BIO 读操作回调函数;
bputs: 具体 BIO 中写入字符串回调函数;
bgets: 具体 BIO 中读取字符串函数;
ctrl: 具体 BIO 的控制回调函数;
create: 生成具体 BIO 回调函数;
destroy: 销毁具体 BIO 回调函数;
callback_ctrl: 具体 BIO 控制回调函数,与 ctrl 回调函数不一样,
该函数可由调用者(而不是实现者)来实现,然后通过
BIO_set_callback 等函数来设置。
- BIO
truct bio_st {
BIO_METHOD *method;
/* bio, mode, argp, argi, argl, ret */
long (*callback)(struct bio_st *,int,const char *,int, long,long);
char *cb_arg; /* first argument for the callback */
int init;
int shutdown;
int flags; /* extra storage */
int retry_reason;
int num;
void *ptr;
structbio_st *next_bio; /*usedbyfilterBIOs*/
struct bio_st *prev_bio; /* used by filter BIOs */
int references;
nsigned long num_read;
unsigned long num_write;
CRYPTO_EX_DATA ex_data;
};
/*
主要项含义:
init: 具体句柄初始化标记,初始化后为1。
比如:文件 BIO 中,通过 BIO_set_fp 关联一个文件指针时,该标记则置 1 ;
socket BIO中,通过 BIO_set_fd 关联一个链接时,设置该标记为 1。
shutdown: BIO 关闭标记,当该值不为 0 时,释放资源; 该值可以通过控制函 数来设置。
flags: 有些 BIO 实现需要它来控制各个函数的行为。
比如文件 BIO 默认该值为 BIO_FLAGS_UPLINK,
这时文件读操作调用UP_fread 函数而不是调用fread 函数。
retry_reason: 重试原因,主要用在 socket 和 ssl BIO 的异步阻塞。
比如 socket bio 中,遇到 WSAEWOULDBLOCK 错误时,
openssl 告诉用户的操作需要重试。
num: 该值因具体 BIO 而异,比如 socket BIO 中 num 用来存放链接字。
ptr: 指针,体 bio 有不同含义。比如:
文件 BIO 中它用来存放文件句柄;
mem BIO 中它用来存放内存地址;
connect BIO 中它用来存放 BIO_CONNECT 数据,
accept BIO 中它用来存放 BIO_ACCEPT 数据。
next_bio: 下一个 BIO 地址,BIO 数据可以从一个BIO传送到另一个BIO,
该值指明了下一个 BIO 的地址。
references: 被引用数量。
num_read: BIO 中已读取的字节数。
num_write: BIO 中已写入的字节数。
ex_data: 用于存放额外数据。
*/
typedef struct bio_st BIO;
struct bio_st
{
BIO_METHOD *method; //BIO方法结构,是决定BIO类型和行为的重要参数,各种BIO的不同之处主要也正在于此项。
long (*callback)(struct bio_st *,int,const char *,int, long,long); //BIO回调函数
char *cb_arg; //回调函数的第一个参量
int init; //初始化标志,初始化了为1,否则为0。比如文件BIO 中,通过BIO_set_fp
//关联一个文件指针时,该标记则置1。
int shutdown; //BIO开关标志,如果为BIO_CLOSE,则释放BIO时自动释放持有的资源,否则不自动释放持有资源
int flags; //有些BIO 实现需要它来控制各个函数的行为。比如文件BIO 默认该值为BIO_FLAGS_UPLINK,
//这时文件读操作调用UP_fread 函数而不是调用fread 函数。
int retry_reason; //重试原因,主要用在socket 和ssl BIO 的异步阻塞。比如socketbio 中,遇到
//WSAEWOULDBLOCK 错误时,openssl 告诉用户的操作需要重试
int num; //该值因具体BIO 而异,比如socket BIO 中num 用来存放链接字。
void *ptr; //ptr:指针,具体bio 有不同含义。比如文件BIO中它用来存放文件句柄;mem bio 中它用来存放
//内存地址;connect bio 中它用来存放BIO_CONNECT 数据,acceptbio 中它
//用来存放BIO_ACCEPT数据。
struct bio_st *next_bio; //BIO链中下一个BIO 地址,BIO 数据可以从一个BIO 传送到另一个BIO。
struct bio_st *prev_bio; //BIO链中上一个BIO 地址,
int references; //引用计数
unsigned long num_read; //已读出的数据长度
unsigned long num_write; //已写入的数据长度
CRYPTO_EX_DATA ex_data; //额外数据
};
7.3 BIO 函数¶
BIO 各个函数定义在 crypto/bio.h 中。所有的函数都由 BIO_METHOD 中的回调函 数来实现。函数主要分为几类:
1) 具体BIO相关函数
比如:BIO_new_file(生成新文件)和 BIO_get_fd(设置网络链接)等。
2) 通用抽象函数
比如 BIO_read 和 BIO_write 等。
另外,有很多函数是由宏定义通过控制函数 BIO_ctrl 实现,
比如 BIO_set_nbio、BIO_get_fd 和 BIO_eof 等等。
在BIO的所用成员中,method可以说是最关键的一个成员,它决定了BIO的类型,
可以看到,在定义一个新的BIO结构时,总是使用下面的函数:
BIO* BIO_new(BIO_METHOD *type);
在源代码可以看出,BIO_new函数除了给一些初始变量赋值外,
主要就是把type中的各个变量赋值给BIO结构中的method成员。
一般来说,上述type参数是以一个返回值为BIO_METHOD类型的函数提供的,
如生成一个mem型的BIO结构,就使用下面的语句:
BIO *mem = BIO_new(BIO_s_mem());
// 【source/sink型】
BIO_METHOD* BIO_s_accept() //一个封装了类似TCP/IP socket Accept规则的接口,并且使TCP/IP操作对于BIO接口透明。
BIO_METHOD* BIO_s_connect() //一个封装了类似TCP/IP socket Connect规则的接口,并且使TCP/IP操作对于BIO接口透明
BIO_METHOD* BIO_s_socket() //封装了socket接口的BIO类型
BIO_METHOD* BIO_s_bio() //封装了一个BIO对,数据从其中一个BIO写入,从另外一个BIO读出
BIO_METHOD* BIO_s_fd() //是一个封装了文件描述符的BIO接口,提供类似文件读写操作的功能
BIO_METHOD* BIO_s_file() //封装了标准的文件接口的BIO,包括标准的输入输出设备如stdin等
BIO_METHOD* BIO_s_mem() //封装了内存操作的BIO接口,包括了对内存的读写操作
BIO_METHOD* BIO_s_null() //返回空的sink型BIO接口,写入这种接口的所有数据读被丢弃,读的时候总是返回EOF
//【filter型】
BIO_METHOD* BIO_f_base64() //封装了base64编码方法的BIO,写的时候进行编码,读的时候解码
BIO_METHOD* BIO_f_cipher() //封装了加解密方法的BIO,写的时候加密,读的时候解密
BIO_METHOD* BIO_f_md() //封装了信息摘要方法的BIO,通过该接口读写的数据都是已经经过摘要的。
BIO_METHOD* BIO_f_ssl() //封装了openssl 的SSL协议的BIO类型,也就是为SSL协议增加了一些BIO操作方法。
BIO_METHOD* BIO_f_null() //一个不作任何事情的BIO,对它的操作都简单传到下一个BIO去了,相当于不存在。
BIO_METHOD* BIO_f_buffer() //封装了缓冲区操作的BIO,写入该接口的数据一般是准备传入下一个BIO接口的,从该——
//接口读出的数据一般也是从另一个BIO传过来的
7.4 编程示例¶
7.4.1 mem BIO¶
#include <stdio.h>
#include <string.h>
#include <openssl/bio.h>
int main()
{
BIO *b=NULL;
int len=0;
char *out=NULL;
b=BIO_new(BIO_s_mem());
len=BIO_write(b,"openssl",4);
//len=BIO_write(b,"openssl",7);
len=BIO_printf(b,"%s","zcp");
len=BIO_ctrl_pending(b);
out=(char *)OPENSSL_malloc(len);
len=BIO_read(b,out,len);
printf(" %zu : %s \n" ,strlen(out), out);
//printf(" %d : %s \n" ,len, out);
OPENSSL_free(out);
BIO_free(b);
return 0;
}
说明:
b=BIO_new(BIO_s_mem()); 生成一个mem 类型的BIO。
len=BIO_write(b,"openssl",7); 将字符串"openssl"写入 bio。
len=BIO_printf(b,"bio test",8); 将字符串"bio test"写入 bio。
len=BIO_ctrl_pending(b); 得到缓冲区中待读取大小。
len=BIO_read(b,out,50); 将bio中的内容写入out缓冲区。
7.4.2 file bio¶
#include <stdio.h>
#include <openssl/bio.h>
int main()
{
BIO *b=NULL;
int len=0,outlen=0;
char *out=NULL;
b=BIO_new_file("bf.txt","w");
len=BIO_write(b,"openssl",4);
len=BIO_printf(b,"%s","zcp");
BIO_free(b);
b=BIO_new_file("bf.txt","r");
len=BIO_pending(b);
len=50;
out=(char *)OPENSSL_malloc(len);
len=1;
while(len>0) {
len=BIO_read(b,out+outlen,1);
outlen+=len;
}
printf("outlen = %d \n" , outlen);
BIO_free(b);
free(out);
return 0;
}
7.4.3 socket BIO¶
( 待补充 )
7.4.4 md BIO¶
#include <openssl/bio.h>
#include <openssl/evp.h>
int main()
{
BIO *bmd=NULL,*b=NULL;
const EVP_MD *md=EVP_md5();
int len, i;
char tmp[1024]={0};
bmd=BIO_new(BIO_f_md());
BIO_set_md(bmd,md);
b= BIO_new(BIO_s_null());
b=BIO_push(bmd,b);
len=BIO_write(b,"openssl",7);
len=BIO_gets(b,tmp,1024);
for ( i=0 ; i < 16; i++)
printf("0x%02x: ", (uint8_t)tmp[i]);
BIO_free(b);
return 0;
}
说明: 本示例用 md BIO 对字符串"opessl"进行 md5 摘要。
bmd=BIO_new(BIO_f_md());生成一个 md BIO。
BIO_set_md(bmd,md); 设置 md BIO 为 md5 BIO。
b= BIO_new(BIO_s_null()); 生成一个 null BIO。
b=BIO_push(bmd,b); 构造BIO 链,md5 BIO 在顶部。
len=BIO_write(b,"openssl",7); 将字符串送入 BIO 做摘要。
len=BIO_gets(b,tmp,1024); 将摘要结果写入 tmp 缓冲区
7.4.5 cipher BIO¶
#include <string.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
int main()
{
/* 加密 */
BIO *bc=NULL,*b=NULL;
const EVP_CIPHER *c = EVP_des_ecb();
int len,i;
char tmp[1024]={0};
unsigned char key[8],iv[8];
/* init key iv */
for(i=0;i<8;i++) {
memset(&key[i],i+1,1);
memset(&iv[i], i+1,1);
}
bc=BIO_new(BIO_f_cipher());
BIO_set_cipher(bc,c,key,iv,1);
b= BIO_new(BIO_s_null());
b=BIO_push(bc,b);
len=BIO_write(b,"openssl",7);
len=BIO_read(b,tmp,1024);
BIO_free(b);
/*print */
for (i =0; i < 8; i++){
printf("%x ", (uint8_t) tmp[i]);
}
printf("\n");
/* 解密 */
BIO *bdec=NULL,*bd=NULL;
const EVP_CIPHER*cd=EVP_des_ecb();
bdec=BIO_new(BIO_f_cipher());
BIO_set_cipher(bdec,cd,key,iv,0);
bd= BIO_new(BIO_s_null());
bd=BIO_push(bdec,bd);
len=BIO_write(bdec,tmp,len);
len=BIO_read(bdec,tmp,1024);
BIO_free(bdec);
/*print */
tmp[len]='\0';
printf("len = %d, %s \n", len, tmp);
return 0;
}
说明:本示例采用 cipher BIO 对字符串"openssl"进行加密和解密;
关键说明:
BIO_set_cipher(bc,c,key,iv,1);设置加密 BI。
BIO_set_cipher(bdec,cd,key,iv,0);设置解密 BIO。
其中 key 为对称密钥,iv 为初始化向量。 加/解密结果通过 BIO_read 获取。
7.4.6 SSl BIO¶
#include <openssl/bio.h>
#include <openssl/ssl.h>
int main()
{
BIO*sbio, *out;
int len;
char tmpbuf[1024];
SSL_CTX *ctx;
SSL *ssl;
SSLeay_add_ssl_algorithms();
OpenSSL_add_all_algorithms();
//ctx = SSL_CTX_new(SSLv3_client_method());
ctx = SSL_CTX_new(SSLv23_client_method());
sbio = BIO_new_ssl_connect(ctx);
BIO_get_ssl(sbio, &ssl);
if(!ssl) {
fprintf(stderr, "Can not locate SSL pointer\n");
return 0;
}
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
//BIO_set_conn_hostname(sbio, "mybank.icbc.com.cn:https");
BIO_set_conn_hostname(sbio, "www.baidu.com:https");
out = BIO_new_fp(stdout, BIO_NOCLOSE);
BIO_printf(out,"链接中...\n");
if(BIO_do_connect(sbio) <= 0) {
fprintf(stderr, "Error connecting to server\n");
return 0;
}
if(BIO_do_handshake(sbio) <= 0){
fprintf(stderr, "Error establishing SSL connection\n");
return 0;
}
BIO_puts(sbio, "GET / HTTP/1.0\n\n");
for(;;)
{
len = BIO_read(sbio, tmpbuf, 1024);
if(len <= 0) break;
BIO_write(out, tmpbuf, len);
}
BIO_free_all(sbio);
BIO_free(out);
return 0;
}
/*
* 本函数用ssl bio来链接mybank.icbc.com.cn的https服务,并请求首页文件。
* 其中SSLeay_add_ssl_algorithms和OpenSSL_add_all_algorithms函数必不可少,
* 否则不能找到ssl加密套件并且不能找到各种算法。
*/
7.4.7 其他¶
#include <openssl/bio.h>
#include <openssl/asn1.h>
int main()
{
int ret,len,indent;
BIO *bp;
char *pp,buf[5000];
FILE *fp;
bp = BIO_new(BIO_s_file());
BIO_set_fp(bp,stdout,BIO_NOCLOSE);
fp = fopen("der.cer","rb");
len = fread(buf,1,5000,fp);
fclose(fp);
pp = buf;
indent = 5;
ret = BIO_dump_indent(bp,pp,len,indent);
BIO_free(bp);
return 0;
}
8 配置文件¶
8.1 概述¶
Openssl 采用自定义的配置文件来获取配置信息。
Openssl的配置文件主要由如下内容组成:
注释信息 : 注释信息由 # 开头;
段信息 : 段信息由 [xxx] 来表示,其中xxx为段标识;
属性-值信息: 表示方法为 a = b,这种信息可以在一个段内也可以不属于任何段。
典型配置文件为apps/openssl.cnf(同时该文件也是openssl最主要的配置文件)。
摘取部分内容如下:
# OpenSSL example configuration file.
oid_section = new_oids
[ CA_default ]
dir = ./demoCA # Where everything is kept
certs = $dir/certs # Where the issued certs are kept
default_days = 365 # 注意,这里是一个数字
8.2 OpenSSl 配置¶
OpenSSL 读取配置文件的实现源码在crypto/conf中,
要函数定义在conf.h中。
函数一般以 CONF 或 NCONF(new conf,新函数)开头。
本文主要介绍了新的conf函数的使用方。
主要的数据结构在crypto/conf.h中定义如下:
typedef struct
{
char *section;
char *name;
char *value;
} CONF_VALUE;
section 表明配置文件的段,
name 表示这个段中的一个属性,
value 则是这个属性的值。
Openssl采用哈希表来存放这些信息,便于快速查找。
8.3 主要函数¶
1) NCONF_new
生成一个CONF结构。
2) CONF_free
释放空间,以及释放存储在散列表中的数据。
3) CONF_load
函数定义:
LHASH *CONF_load(LHASH *conf, const char *file, long *eline),
该函数根据输入配置文件名,读取信息存入散列表,如果有错,eline为错误行。
4) CONF_load_bio/ CONF_load_fp
根据bio或者文件句柄读取配置信息并存入散列表。
5) CONF_get_section
给定段信息,得到散列表中的所有对应值。
用于获取配置文件中指定某个段下的所有信息,
这些信息存放在CONF_VALUE的堆栈中。
6) CONF_get_string
给定段以及属性值,得到对应的字符串信息。
7) CONF_get_number
给定段和属性值,获取对应的数值信息。
8)CONF_get1_default_config_file
获取默认的配置文件名,比如openssl.cnf。
8.4 编程示例¶
示例 1¶
#include <openssl/conf.h>
int main()
{
CONF *conf;
long eline,result;
int ret;
char *p;
BIO *bp;
conf = NCONF_new(NULL);
#if 0
bp=BIO_new_file("openssl.cnf","r");
NCONF_load_bio(conf,bp,&eline);
#else
ret = NCONF_load(conf,"openssl.cnf",&eline);
//ret = CONF_modules_load_file
if(ret != 1)
{
printf("err!\n");
return -1;
}
#endif
p = NCONF_get_string(conf,NULL,"certs");
if(p == NULL){
printf("no global certs info\n");
}
p=NCONF_get_string(conf,"CA_default","certs");
printf("%s\n",p);
p=NCONF_get_string(conf,"CA_default","default_days");
printf("%s\n",p);
ret=NCONF_get_number_e(conf,"CA_default","default_days",&result);
printf("%d\n",result);
ret=NCONF_get_number(conf,"CA_default","default_days",&result);
printf("%d\n",result);
NCONF_free(conf);
return 0;
}
示例 2¶
// NCONF_get_section 的用法:
#include <openssl/conf.h>
int main()
{
CONF *conf;
BIO *bp;
STACK_OF(CONF_VALUE) *v;
CONF_VALUE *one;
int i,num;
long eline;
conf = NCONF_new(NULL);
bp = BIO_new_file("../1/openssl.cnf","r");
if(bp == NULL) {
printf("err!\n");
return -1;
}
NCONF_load_bio(conf,bp,&eline);
v = NCONF_get_section(conf,"CA_default");
num = sk_CONF_VALUE_num(v);
printf("section CA_default :\n");
for(i = 0;i < num;i ++)
{
one = sk_CONF_VALUE_value(v,i);
printf("%s = %s\n",one->name,one->value);
}
BIO_free(bp);
printf("\n");
return 0;
}
9 随机数¶
9.1 随机数¶
随机数是一种无规律的数,但是真正做到完全无规律也较困难,
所以一般将它称之为伪随机数。随机数在密码学用的很多,
比如ssl握手中的客户端hello和服务端hello消息中都有随机数;
ssl握手中的预主密钥是随机数;RSA密钥生成也用到随机数。
如果随机数有问题,会带来很大的安全隐患。
软件生成随机数一般预先设置随机数种子,再生成随机数。
设置随机数种子可以说是对生成随机数过程的一种扰乱,让产生的随机数更加无规律可循。
生成随机数有多种方法,可以是某种算法也可以根据某种或多种随机事件来生成。
比如,鼠标的位置、系统的当前时间、本进程/线程相关信息以及机器噪声等。
安全性高的应用一般都采用硬件方式(随机数发生器)来生成随机数。
9.2 OpenSSl 随机数数据结构与源码¶
openssl生成随机数的源码位于crypto/rand目录下。
rand.h定义了许多与随机数生成相关的函数。
openssl通过使用摘要算法来生成随机数,可用的摘要算法有:sha1、md5、mdc2和md2。具体采用那种摘要算法在crypto/rand_lcl.h中由宏来控制。Openssl维护一个内部随机状态数据(md_rand.c中定义的全局变量state和md),通过对这些内部数据计算摘要来生成随机数。
Openssl随机数相关的数据结构如下,定义在rand.h中:
struct rand_meth_st
{
void (*seed)(const void *buf, int num);
int (*bytes)(unsigned char *buf, int num);
void (*cleanup)(void);
void (*add)(const void *buf, int num, double entropy);
int (*pseudorand)(unsigned char *buf, int num);
int (*status)(void);
};
本结构主要定义了各种回调函数,如果用户需要实现自己的随机数生成函数,
他需要实现本结构中的各个函数。
Openssl给出了一个默认的基于摘要的rand_meth实现(crypto/md_rand.c)。各项意义如下:
seed:种子函数,为了让openssl内部维护的随机数据更加无序,可使用本函数。
buf为用户输入的随机数地址, num为其字节数。
Openssl将用户提供的buf中的随机内容与其内部随机数据进行摘要计算,
更新其内部随机数据。本函数无输出;
bytes:生成随机数,openssl根据内部维护的随机数状态来生成结果。
buf用于存放生成的随机数。num为输入参数,用来指明生成随机数的字节长度;
cleanup:清除函数,本函数将内部维护的随机数据清除;
add:与seed类似,也是为了让openssl内部随机数据更加无序,
其中entropy(信息熵)可以看作用户本次加入的随机数的个数。
Openssl默认的随机数熵为32字节,
在rand_lcl.h中由ENTROPY_NEEDED定义。
Openssl给出随机数之前,用户提供的所有的随机种子数之和必须达到32字节。
在openssl实现的md_rand中,即使用户不调用种子函数来直接生成随机数,
openssl也会调用RAND_poll函数来完成该操作。
pseudorand:本函数与bytes类似也是来生成随机数。
status:查看熵值是否达到预定值,openssl中为32字节,如果达到则返回1,否则返回0。
在openssl实现的md_rand中该函数会调用RAND_poll函数来使熵值合格。
如果本函数返回0,则说明此时用户不应生成随机数,需要调用seed和add函数来添加熵值。
cypto/rand目录下的主要源码有:
1) md_rand.c
它实现了基于摘要的随机数生成。
2) rand_lib.c
该文件中的源码简单调用了rand_meth中的回调函数。
3) rand_win.c/rand_unix.c/rand_os2.c等
这些源码主要提供了平台相关的RAND_poll函数实现和其他系统特有函数的实现。
比如rand_win.c实现了RAND_screen函数,用户根据屏幕来设置随机数种子。
4) randfile.c
用于从随机文件中加载种子、生成随机数文件以及获取随机文件名。
比如默认的随机数文件为.rnd文件,如果找不到该文件,
openbsd可能会返回/dev/arandom。
9.3 主要函数¶
1) int RAND_load_file(const char *file, long bytes)
本函数将file指定的随机数文件中的数据读取bytes字节(如果bytes大于1024,则读取1024字节),
调用RAND_add进行计算,生成内部随机数。
2) RAND_write_file
生成一个随机数文件。
3) const char *RAND_file_name(char *file,size_t num)
获取随机数文件名,如果随机数文件长度小于num则返回空,否则返回文件名。
4) RAND_poll
用于计算内部随机数,各个平台有各自的实现。
5) RAND_screen/RAND_event
Windows特有函数,用来计算内部随机数,他们调用了RAND_seed。
6) RAND_seed/RAND_add
用来计算内部随机数。
7) RAND_bytes/RAND_pseudo_bytes
用来计算随机数。
8) RAND_cleanup
清除内部随机数。
10) RAND_set_rand_method
用来设置rand_meth,
当用户实现了自己的随机数生成函数时(实现rand_meth中的回调函数),
调用该方法来替换openssl 所提供的随机数功能。
11) RAND_status
用来查看内部随机数熵值是否已达到预定值,
如果未达到,则不应该生成随机数。
9.4 编程示例¶
#include <stdio.h>
#include <openssl/bio.h>
#include <openssl/rand.h>
int main()
{
char buf[20],*p;
unsigned char out[20],filename[50];
int ret,len;
BIO *print;
//RAND_screen();
strcpy(buf,"我的随机数");
RAND_add(buf,20,strlen(buf));
strcpy(buf,"23424d");
RAND_seed(buf,20);
while(1) {
ret = RAND_status();
if(ret==1) {
printf("seeded enough!\n");
break;
}
else {
printf("not enough sedded!\n");
RAND_poll();
}
}
p = RAND_file_name(filename,50);
if(p == NULL) {
printf("can not get rand file\n");
return -1;
}
ret = RAND_write_file(p);
len = RAND_load_file(p,1024);
ret = RAND_bytes(out, 20);
if(ret != 1) {
printf("err.\n");
return -1;
}
print = BIO_new(BIO_s_file());
BIO_set_fp(print,stdout,BIO_NOCLOSE);
BIO_write(print,out,20);
BIO_write(print,"\n",2);
BIO_free(print);
RAND_cleanup();
return 0;
}
10 文本数据库¶
10.1 概述¶
Openss实现了一个简单的文本数据库,它可以从文件读取数据和将数据写到文件中,
并且可以根据关键字段来查询数据。
Openssl的文本数据库供apps/目录下的文件调用,比如apps.c、ca.c和ocsp.c。
openssl文本数据库典型的例子为apps/demoCA/index.txt。
文本数据库一行代表数据库的一行,各个列之间必须用一个\t隔开,用#进行注释(#必须在开始位置),以空行结束。
比如下面的例子:
赵春平 28 湖北
zcp 28 荆门
文本数据库的查找用到了哈希表。
openssl读取的所有行数据存放在堆栈中,并为每一列数据建立一个单独的哈希表。
每个哈希表中存放了所有行数据的地址。
查询时,用户指定某一列,openssl根据对应的哈希表进行查找。
10.2 数据结构¶
数据结构在crypto/txt_db/txt_db.h中定义,如下:
typedef struct txt_db_st
{
int num_fields;
STACK *data;
LHASH **index;
int (**qual)(char **);
long error;
long arg1;
long arg2;
char **arg_row;
} TXT_DB;
意义如下:
num_fields: 表明文本数据库的列数。
data: 用来存放数据,
每一行数据组织成为一个字符串数组(每个数组值对应该行的一列),
并将此数组地址push到堆栈中。
index: 哈希表数组,每一列对应一个哈希表。每一列都可以建哈希表,如果不建哈希表将不能查找该列数据。
qual: 一个函数地址数组,数组的每个元素对应一列, 进行插入该列哈希表前的过滤。
这些函数用于判断一行数据的一列或者多列是否满足某种条件,
如果满足将不能插入到哈希表中去(但是能存入堆栈)。
每一列都可以设置一个这样的函数。这些函数由用户实现。
比如,一个文本数据库中,有名字列和年龄列,并且要求名字长度不能小于2, 年龄不能小于0和大于200。
用户为名字列实现了一个qual函数,只用来检查名字长度,对于年龄列实现一个qual函数,只用来检查年龄。 当用户要插入一条记录,名字长度为1,但是年龄合法,那么该记录能插入到年龄列对应的哈希表中,
而不能插入名字列对应的哈希表。
error、arg1、arg2和arg_row用于存放错误信息。
10.3 函数说明¶
1)TXT_DB *TXT_DB_read(BIO *in, int num)
用于从BIO中读入数据,转换为TXT_DB,num用于明确指明列数,本函数不建立哈希表。
2)long TXT_DB_write(BIO *out, TXT_DB *db)
将TXT_DB内容写入BIO;
3)int TXT_DB_create_index( TXT_DB *db,
int field,
int (*qual)(char **),
LHASH_HASH_FN_TYPE hash,
LHASH_COMP_FN_TYPE cmp )
给field指定的列建立哈希表。
db为需要建索引的TXT_DB,
hash为一行数据的hash运算回调函数,
cmp为一行数据的比较函数。
4)char **TXT_DB_get_by_index(TXT_DB *db, int idx, char **value)
根据关键字段来查询数据,查询结果返回一行数据db为文本数据库,
idx表明采用哪一列的哈希表来查找;value为查询条件。
5)int TXT_DB_insert(TXT_DB *db,char **value)
往TXT_DB中插入一行数据。value数组以NULL表示结束。
6) void TXT_DB_free(TXT_DB *db)
清除TXT_DB。
10.4 编程示例¶
/* txtdb.dat的内容
赵春平 28 湖北 无
zcp 28 荆门 无
*/
#include <openssl/bio.h>
#include <openssl/txt_db.h>
#include <openssl/lhash.h>
/* 名字过滤 */
static int name_filter(char **in)
{
if(strlen(in[0])<2)
return 0;
return 1;
}
static unsigned long index_name_hash(const char **a)
{
const char *n;
n=a[0];
while (*n == '0') n++;
return(lh_strhash(n));
}
static int index_name_cmp(const char **a, const char **b)
{
const char *aa,*bb;
for (aa = a[0]; *aa == '0'; aa++);
for (bb = b[0]; *bb == '0'; bb++);
return(strcmp(aa,bb));
}
int main()
{
TXT_DB *db = NULL,*out = NULL;
BIO *in;
int num,ret;
char **added = NULL,**rrow = 0,**row = NULL;
in = BIO_new_file("txtdb.dat","r");
num = 1024;
db = TXT_DB_read(in,4);
added = (char **)OPENSSL_malloc(sizeof(char *)*(3+1));
added[0] = (char *)OPENSSL_malloc(10);
#if 1
strcpy(added[0],"skp");
#else
strcpy(added[0],"a"); /* 不能插入名字对应的哈希表 */
#endif
added[1] = (char *)OPENSSL_malloc(10);
strcpy(added[1],"22");
added[2] = (char *)OPENSSL_malloc(10);
strcpy(added[2],"chairman");
added[3] = NULL;
ret = TXT_DB_insert(db,added);
if(ret != 1) {
printf("err!\n");
return -1;
}
ret = TXT_DB_create_index(db,0, name_filter,index_name_hash,index_name_cmp);
if(ret != 1) {
printf("err\n");
return 0;
}
row = (char **)malloc(2*sizeof(char *));
row[0] = (char *)malloc(10);
strcpy(row[0],"skp");
row[1] = NULL;
rrow = TXT_DB_get_by_index(db,0,row);
if(rrow != NULL){
printf("%s %s %s\n",rrow[0],rrow[1],rrow[2]);
}
out = BIO_new_file("txtdb2.dat","w");
ret = TXT_DB_write(out,db);
TXT_DB_free(db);
BIO_free(in);
BIO_free(out);
return 0;
}
/*
* 本示例只对第一列做了哈希。
* 需要注意的是,added数组及其元素申请空间时尽量采用OPENSSL_malloc而不是malloc,
* 且其申请的空间由TXT_DB_free(调用OPENSSL_free)释放
*/
11 大数¶
11.1 介绍¶
大数一般指的是位数很多的数。 计算机表示的数的大小是有限的,精度也是有限的,它不能支持大数运算。 密码学中采用了很多大数计算,为了让计算机实现大数运算,用户需要定义自己的大数表示方式并及实现各种大数运算。 Openssl为我们提供了这些功能,主要用于非对称算法。
11.2 openssl大数表示¶
crypto/bn.h中定义了大数的表示方式,如下:
struct bignum_st { BN_ULONG *d; int top; int dmax; int neg; int flags; }; /* 各项意义如下: d :BN_ULONG(应系统而异,win32下为4个字节)数组指针首地址, 大数就存放在这里面,不过是倒放的。 比如,用户要存放的大数为 12345678000(通过BN_bin2bn放入), 则 d 的内容如下: 0x30 0x30 0x30 0x38 0x37 0x36 0x35 0x34 0x33 0x32 0x31 ; top :用来指明大数占多少个BN_ULONG空间,上例中top为3。 dmax:d数组的大小。 neg :是否为负数, 如果 为1,则是负数,为0,则为正数。 flags:用于存放一些标记, 比如flags 含有 BN_FLG_STATIC_DATA 时, d 的内存是静态分配的; 含有 BN_FLG_MALLOCED 时,d 的内存是动态分配的。 */
11.3 大数函数¶
大数函数一般都能根据函数名字知道其实现的功能。下面简单介绍了几个函数。
1)
int BN_rand(BIGNUM *rnd, int bits, int top, int bottom);
int BN_priv_rand(BIGNUM *rnd, int bits, int top, int bottom);
int BN_pseudo_rand(BIGNUM *rnd, int bits, int top, int bottom);
生成一个随机的大数。
2)
int BN_rand_range(BIGNUM *rnd, BIGNUM *range);
int BN_priv_rand_range(BIGNUM *rnd, BIGNUM *range);
int BN_pseudo_rand_range(BIGNUM *rnd, BIGNUM *range);
生成随机数,但是给出了随机数的范围。
3)
BIGNUM *BN_copy(BIGNUM *to, const BIGNUM *from);
BIGNUM *BN_dup(const BIGNUM *from);
void BN_with_flags(BIGNUM *dest, const BIGNUM *b, int flags);
大数复制。
4)
int BN_generate_prime_ex(BIGNUM *ret, int bits, int safe, const BIGNUM *add,
const BIGNUM *rem, BN_GENCB *cb);
#if OPENSSL_API_COMPAT < 0x00908000L
BIGNUM *BN_generate_prime(BIGNUM *ret, int num, int safe, BIGNUM *add,
BIGNUM *rem, void (*callback)(int, int, void *),
void *cb_arg);
生成素数。
5) int BN_add_word(BIGNUM *a, BN_ULONG w)
给大数a加上w,
BN_add_word() adds w to a ("a+=w").
BN_sub_word() subtracts w from a ("a-=w").
BN_mul_word() multiplies a and w ("a*=w").
BN_div_word() divides a by w ("a/=w") and returns the remainder.
BN_mod_word() returns the remainder of a divided by w ("a%w").
For BN_div_word() and BN_mod_word(), w must not be 0.
return 1 for success, 0 on error
6) 转换
6.1
BIGNUM * BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret)
将 s 中的换为大数,
入参
s : 为内存地址,
len : 为数据长度,
出参:
ret : 为返回值。
6.2
int BN_bn2bin(const BIGNUM *a, unsigned char *to)
将大数转换为内存形式。
入参:
a : 大数,
to : 为输出缓冲区地址,缓冲区需要预先分配,可以为NULL,
return : 返回冲区的长度。
BN_bn2bin() returns the length of the big-endian number placed at to.
BN_bin2bn() returns the BIGNUM, NULL on error
6.3
char *BN_bn2dec(const BIGNUM *a)
将大数转换成整数字符串。
return : 返回值中存放 整数字符串,它由内部分配空间,
用户必须在外部用OPENSSL_free函数释放该空间。
int BN_dec2bn(BIGNUM **a, const char *str); //将整数字符串转换成大数。
6.4
char *BN_bn2hex(const BIGNUM *a)
将大数转换为十六进制字符串。
return: 返回值为生成的十六进制字符串,
外部需要用OPENSSL_free函数释放
int BN_hex2bn(BIGNUM **a, const char *str); // 将十六进制字符串转换为大数
BN_bn2hex() and BN_bn2dec() return a null-terminated string, or NULL on error.
BN_hex2bn() and BN_dec2bn() return the number of characters used in parsing,
or 0 on error, in which case no new BIGNUM will be created.
10) 比较
int BN_cmp(BIGNUM *a, BIGNUM *b); 比较两个大数。
int BN_ucmp(BIGNUM *a, BIGNUM *b); 比较两个大数绝对值。
int BN_is_zero(BIGNUM *a);
int BN_is_one(BIGNUM *a);
int BN_is_word(BIGNUM *a, BN_ULONG w);
int BN_is_odd(BIGNUM *a);
11)BIGNUM *BN_mod_inverse( BIGNUM *in,
const BIGNUM *a,
const BIGNUM *n,
BN_CTX *ctx)
计算ax=1(mod n)。
用户使用openssl函数编程时, 一般用不着进行大数运算。
BN_bin2bn、BN_hex2bn、BN_dec2bn、BN_bin2bn、BN_bn2bin、BN_bn2hex和BN_bn2dec比较常用。
比如给定RSA密钥的内存形式,用户可以调用BN_bin2bn来构造RSA密钥的大数元素来进行RSA运算,
或者已经生成了RSA密钥,用户调用BN_bn2bin将RSA各个元素导出到内存中再写入密钥文件。
#include <openssl/bio.h>
#include <openssl/bn.h>
int main()
{
int ret;
BIGNUM *a;
BN_ULONG w;
a=BN_new();
//BN_zero(a);
//BN_one(a);
//BN_set_word(a,16);
//BN_set_word(a,256);
w=2685550010;
//w=0x2685550010;
ret=BN_add_word(a,w);
if(ret!=1) {
printf("a+=w err!\n");
BN_free(a);
return -1;
}
BIO *bio_out;
bio_out = BIO_new_fp(stdout, BIO_NOCLOSE);
//int BN_print(BIO *fp, const BIGNUM *a);
BIO_printf(bio_out, "-------------------\n");
BN_print(bio_out, a);
BIO_printf(bio_out, "\n-------------------\n");
int bits = BN_num_bits(a);
BIO_printf(bio_out, "bits = %d \n" ,bits);
bits = BN_num_bytes(a);
BIO_printf(bio_out, "bytes = %d \n" ,bits);
BN_free(a);
return 0;
}
#include <openssl/bio.h>
#include <openssl/bn.h>
int main()
{
BIGNUM *ret1,*ret2;
ret1=BN_new();
ret1=BN_bin2bn("242424ab",8, ret1);
ret2=BN_bin2bn("242424ab",8, NULL);
// printf
BIO *bio_out;
bio_out = BIO_new_fp(stdout, BIO_NOCLOSE);
BN_print(bio_out, ret1); // 16进制打印
BIO_printf(bio_out, "\n");
BN_print(bio_out, ret2);
BIO_printf(bio_out, "\n");
BIO_free(bio_out);
// free
BN_free(ret1);
BN_free(ret2);
return 0;
}
#include <openssl/bio.h>
#include <openssl/bn.h>
int main()
{
BIGNUM *ret = NULL;
char bin[50]={'s'}, *buf = NULL;
int len;
ret = BN_bin2bn("242424ab",8, NULL);
len = BN_bn2bin(ret,bin);
len = BN_num_bytes(ret);
buf = (char *) malloc(len + 1);
len = BN_bn2bin(ret,buf);
// new bio
BIO *bio_out;
bio_out = BIO_new_fp(stdout, BIO_NOCLOSE);
// bio printf
BN_print(bio_out, ret); // 16进制打印
BIO_printf(bio_out, "\n");
BIO_printf(bio_out,"%s\n", buf);
BIO_free(bio_out);
free (buf);
BN_free(ret);
return 0;
}
#include <openssl/bn.h>
#include <openssl/crypto.h>
int main()
{
BIGNUM *ret1 = NULL;
char *p = NULL;
int len = 0;
ret1 = BN_bin2bn("242424ab",8, NULL);
p = BN_bn2dec(ret1);
printf("%s\n",p); /* 3617571600447332706 */
BN_free(ret1);
OPENSSL_free(p);
//getchar();
return 0;
}
/************************************************************************
* 转换
int BN_bn2bin(const BIGNUM *a, unsigned char *to);
int BN_bn2binpad(const BIGNUM *a, unsigned char *to, int tolen);
BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret);
int BN_bn2lebinpad(const BIGNUM *a, unsigned char *to, int tolen);
BIGNUM *BN_lebin2bn(const unsigned char *s, int len, BIGNUM *ret);
char *BN_bn2hex(const BIGNUM *a);
char *BN_bn2dec(const BIGNUM *a);
int BN_hex2bn(BIGNUM **a, const char *str);
int BN_dec2bn(BIGNUM **a, const char *str);
int BN_print(BIO *fp, const BIGNUM *a);
int BN_print_fp(FILE *fp, const BIGNUM *a);
int BN_bn2mpi(const BIGNUM *a, unsigned char *to);
BIGNUM *BN_mpi2bn(unsigned char *s, int len, BIGNUM *ret);
************************************************************************/
#include <openssl/bio.h>
#include <openssl/bn.h>
#include <string.h>
int main()
{
BIGNUM *ret1 = NULL, * ret2 = BN_new();
char *p = NULL;
int len = 0;
int i;
// new bio
BIO *bio_out;
bio_out = BIO_new_fp(stdout, BIO_NOCLOSE);
BIO_printf(bio_out,"\n---- BN_bin2bn ----\n");
ret1 = BN_bin2bn("242424ab",8, NULL); /* 二进制 转 bn*/
char arr[1024] = {6};
memset(arr,8,100);
BN_bn2bin(ret1,arr);
for ( i = 0; i < 10; i ++){
printf("%02x " ,arr[i]);
}
p = BN_bn2hex(ret1); /* bn 转 16进制字符串*/
//printf("\n0x%s\n",p);
BIO_printf(bio_out,"\n0x%s\n", p);
OPENSSL_free(p); // free
int ret = 0;
BIO_printf(bio_out,"\n---- BN_hex2bn / BN_bn2hex ----\n");
BIGNUM * a = BN_new();
//const char * pstr = "ABFE12";
const char * pstr = "ABFE12";
ret = BN_hex2bn(&a,pstr); // 将十六进制字符串转换为大数
OPENSSL_assert(ret == strlen(pstr));
BN_print(bio_out, a); // 十六进制打印
p = BN_bn2hex(a); // 大数 转 十六进制字符串/
BIO_printf(bio_out,"\n0x%s\n", p);
OPENSSL_free(p); // free
BN_free(a);
/*hex to bn*/
BIO_printf(bio_out,"\n---- BN_dec2bn ----\n");
BN_dec2bn(&ret2,"254"); /* 10进制字符串 转换 bn*/
BN_print(bio_out, ret2); // 16进制打印
BIO_printf(bio_out,"\n");
BN_free(ret1);
BN_free(ret2);
//getchar();
return 0;
}
11.4 使用示例¶
- 示例1
#include <openssl/bn.h>
#include <openssl/bio.h>
#include <string.h>
int main()
{
BIGNUM *bn;
BIO *b;
char a[20];
int ret;
bn = BN_new();
strcpy(a,"32");
//ret = BN_dec2bn(&bn,a); /* 10进制字符串 转换 bn*/
ret = BN_hex2bn(&bn,a); // 16进制字符串 转换 bn
b = BIO_new(BIO_s_file());
ret = BIO_set_fp(b,stdout,BIO_NOCLOSE);
BN_print(b,bn);
BIO_write(b,"\naaa",4);
BIO_printf(b,"\nbbb\n");
BN_free(bn);
return 0;
}
- 示例2 : 加法运算
/******************
示例2:
加法运算
*****************/
#include <openssl/bn.h>
#include <string.h>
#include <openssl/bio.h>
int main()
{
BIGNUM *a, *b, *add;
BIO *out;
char c[20], d[20];
int ret;
a = BN_new();
strcpy(c,"32");
ret = BN_hex2bn(&a,c); // 16进制字符串 转换 bn
b = BN_new();
strcpy(d,"100");
ret = BN_hex2bn(&b,d);
out = BIO_new(BIO_s_file());
ret = BIO_set_fp(out,stdout,BIO_NOCLOSE);
add = BN_new();
ret = BN_add(add,a,b);
if(ret!=1) {
printf("err.\n");
return -1;
}
BIO_puts(out,"bn 0x32 + 0x100 = 0x");
BN_print(out,add);
BIO_puts(out,"\n");
BN_free(a);
BN_free(b);
BN_free(add);
BIO_free(out);
return 0;
}
- 示例3 : 减法运算
/******************
示例3 :
减法运算
*****************/
#include <openssl/bn.h>
#include <string.h>
#include <openssl/bio.h>
int main()
{
BIGNUM *a, *b, *sub;
BIO *out;
char c[20], d[20];
int ret;
a = BN_new();
strcpy(c,"100");
ret = BN_hex2bn(&a, c); // 16进制字符串 转换 bn
b = BN_new();
strcpy(d,"32");
ret = BN_hex2bn(&b, d);
out = BIO_new(BIO_s_file());
ret = BIO_set_fp(out, stdout, BIO_NOCLOSE);
sub = BN_new();
ret = BN_sub(sub, a, b);
if(ret != 1) {
printf("err.\n");
return -1;
}
BIO_puts(out, "bn : 0x100 - 0x32 = 0x");
BN_print(out, sub);
BIO_puts(out, "\n");
BN_free(a);
BN_free(b);
BN_free(sub);
BIO_free(out);
return 0;
}
- 示例4 : 乘法运算
/******************
示例4 :
乘法运算
*****************/
#include <openssl/bn.h>
#include <string.h>
#include <openssl/bio.h>
int main()
{
BIGNUM *a, *b, *mul;
BN_CTX *ctx;
BIO *out;
char c[20],d[20];
int ret;
ctx = BN_CTX_new();
a = BN_new();
strcpy(c,"32");
ret = BN_hex2bn(&a,c); // 16进制字符串 转换 bn
b = BN_new();
strcpy(d,"100");
ret = BN_hex2bn(&b,d);
out = BIO_new(BIO_s_file());
ret = BIO_set_fp(out,stdout,BIO_NOCLOSE);
mul = BN_new();
ret = BN_mul(mul,a,b,ctx);
if(ret != 1) {
printf("err.\n");
return -1;
}
BIO_puts(out,"bn : 0x32 * 0x100 = 0x");
BN_print(out,mul);
BIO_puts(out,"\n");
BN_free(a);
BN_free(b);
BN_free(mul);
BIO_free(out);
BN_CTX_free(ctx);
return 0;
}
- 示例5 : 除法运算
/******************
示例5:
除法运算
*****************/
#include <openssl/bn.h>
#include <string.h>
#include <openssl/bio.h>
int main()
{
BIGNUM *a, *b, *div, *rem;
BN_CTX *ctx;
BIO *out;
char c[20], d[20];
int ret;
ctx = BN_CTX_new();
a = BN_new();
strcpy(c,"100");
ret = BN_hex2bn(&a,c); // 16进制字符串 转换 bn
b = BN_new();
strcpy(d,"17");
ret = BN_hex2bn(&b,d);
out = BIO_new(BIO_s_file());
ret = BIO_set_fp(out,stdout,BIO_NOCLOSE);
div = BN_new();
rem = BN_new();
ret = BN_div(div,rem,a,b,ctx);
if(ret != 1){
printf("err.\n");
return -1;
}
BIO_puts(out,"bn : 0x100 / 0x17 = 0x");
BN_print(out,div);
BIO_puts(out,"\n");
BIO_puts(out,"bn : 0x100 % 0x17 = 0x");
BN_print(out,rem);
BIO_puts(out,"\n");
BN_free(a);
BN_free(b);
BN_free(div);
BN_free(rem);
BIO_free(out);
BN_CTX_free(ctx);
return 0;
}
- 示例6 : 平方运算
/******************
示例6:
平方运算
*****************/
#include <openssl/bn.h>
#include <string.h>
#include <openssl/bio.h>
int main()
{
BIGNUM *a, *sqr;
BN_CTX *ctx;
BIO *out;
char c[20];
int ret;
ctx = BN_CTX_new();
a = BN_new();
strcpy(c,"100");
ret = BN_hex2bn(&a,c); // 16进制字符串 转换 bn
sqr = BN_new();
out = BIO_new(BIO_s_file());
ret = BIO_set_fp(out,stdout,BIO_NOCLOSE);
ret = BN_sqr(sqr,a,ctx);
if(ret != 1) {
printf("err.\n");
return -1;
}
BIO_puts(out,"bn : 0x100 sqr = 0x");
BN_print(out,sqr);
BIO_puts(out,"\n");
BN_free(a);
BN_free(sqr);
BIO_free(out);
BN_CTX_free(ctx);
return 0;
}
- 示例7 : 次方运算
/******************
示例7 :
次方运算
*****************/
#include <openssl/bn.h>
#include <string.h>
#include <openssl/bio.h>
int main()
{
BIGNUM *a, *exp, *b;
BN_CTX *ctx;
BIO *out;
char c[20],d[20];
int ret;
ctx = BN_CTX_new();
a = BN_new();
strcpy(c,"100");
ret = BN_hex2bn(&a,c); // 16进制字符串 转换 bn
b = BN_new();
strcpy(d,"3");
ret = BN_hex2bn(&b,d);
exp = BN_new();
out = BIO_new(BIO_s_file());
ret = BIO_set_fp(out,stdout,BIO_NOCLOSE);
ret = BN_exp(exp,a,b,ctx);
if(ret !=1 ) {
printf("err.\n");
return -1;
}
BIO_puts(out,"bn : 0x100 exp 0x3 = 0x");
BN_print(out,exp);
BIO_puts(out,"\n");
BN_free(a);
BN_free(b);
BN_free(exp);
BIO_free(out);
BN_CTX_free(ctx);
return 0;
}
1.初始化函数
BIGNUM *BN_new(void); 新生成一个BIGNUM结构
void BN_free(BIGNUM *a); 释放一个BIGNUM结构,释放完后a=NULL;
void BN_init(BIGNUM *); 初始化所有项均为0,一般为BN_ init(&c)
void BN_clear(BIGNUM *a); 将a中所有项均赋值为0,但是内存并没有释放
void BN_clear_free(BIGNUM *a); 相当与将BN_free和BN_clear综合,要不就赋值0,要不就释放空间。
2.上下文情景函数,存储计算中的中间过程
BN_CTX *BN_CTX_new(void);申请一个新的上下文结构
void BN_CTX_init(BN_CTX *c);将所有的项赋值为0,一般BN_CTX_init(&c)
void BN_CTX_free(BN_CTX *c);释放上下文结构,释放完后c=NULL;
3.复制以及交换函数
BIGNUM *BN_copy(BIGNUM *a, const BIGNUM *b);将b复制给a,正确返回a,错误返回NULL
BIGNUM *BN_dup(const BIGNUM *a);新建一个BIGNUM结构,将a复制给新建结构返回,错误返回NULL
BIGNUM *BN_swap(BIGNUM *a, BIGNUM *b);交换a,b
4.取位函数
int BN_num_bytes(const BIGNUM *a);返回a的位数,大量使用
int BN_num_bits(const BIGNUM *a);
int BN_num_bits_word(BN_ULONG w);他返回有意义比特的位数,例如0x00000432 为11。
5.基本计算函数
int BN_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b);r=a+b
int BN_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b);r=a-b
int BN_mul(BIGNUM *r, BIGNUM *a, BIGNUM *b, BN_CTX *ctx);r=a*b
int BN_sqr(BIGNUM *r, BIGNUM *a, BN_CTX *ctx);r=a*a,效率高于bn_mul(r,a,a)
int BN_div(BIGNUM *dv, BIGNUM *rem, const BIGNUM *a, const BIGNUM *d,
BN_CTX *ctx);d=a/b,r=a%b
int BN_mod(BIGNUM *rem, const BIGNUM *a, const BIGNUM *m, BN_CTX *ctx);r=a%b
int BN_nnmod(BIGNUM *rem, const BIGNUM *a, const BIGNUM *m, BN_CTX *ctx);r=abs(a%b)
int BN_mod_add(BIGNUM *ret, BIGNUM *a, BIGNUM *b, const BIGNUM *m,
BN_CTX *ctx);r=abs((a+b)%m))
int BN_mod_sub(BIGNUM *ret, BIGNUM *a, BIGNUM *b, const BIGNUM *m,
BN_CTX *ctx); r=abs((a-b)%m))
int BN_mod_mul(BIGNUM *ret, BIGNUM *a, BIGNUM *b, const BIGNUM *m,
BN_CTX *ctx); r=abs((a*b)%m))
int BN_mod_sqr(BIGNUM *ret, BIGNUM *a, const BIGNUM *m, BN_CTX *ctx); r=abs((a*a)%m))
int BN_exp(BIGNUM *r, BIGNUM *a, BIGNUM *p, BN_CTX *ctx);r=pow(a,p)
int BN_mod_exp(BIGNUM *r, BIGNUM *a, const BIGNUM *p,
const BIGNUM *m, BN_CTX *ctx); r=pow(a,p)%M
int BN_gcd(BIGNUM *r, BIGNUM *a, BIGNUM *b, BN_CTX *ctx);r=a,b最大公约数
int BN_add_word(BIGNUM *a, BN_ULONG w);
int BN_sub_word(BIGNUM *a, BN_ULONG w);
int BN_mul_word(BIGNUM *a, BN_ULONG w);
BN_ULONG BN_div_word(BIGNUM *a, BN_ULONG w);
BN_ULONG BN_mod_word(const BIGNUM *a, BN_ULONG w);
BIGNUM *BN_mod_inverse(BIGNUM *r, BIGNUM *a, const BIGNUM *n,
BN_CTX *ctx);模逆,((a*r)%n==1).
6.比较函数
int BN_cmp(BIGNUM *a, BIGNUM *b); -1 if a < b, 0 if a == b and 1 if a > b.
int BN_ucmp(BIGNUM *a, BIGNUM *b); 比较a,b觉得值,返回值和上同。
int BN_is_zero(BIGNUM *a);
int BN_is_one(BIGNUM *a);
int BN_is_word(BIGNUM *a, BN_ULONG w);
int BN_is_odd(BIGNUM *a); 上面四个返回1,假如条件成立,否则将返回0
7.设置函数
int BN_zero(BIGNUM *a); 设置a为0
int BN_one(BIGNUM *a); 设置a为1
const BIGNUM *BN_value_one(void); 返回一个为1的大数
int BN_set_word(BIGNUM *a, unsigned long w); 设置a为w
unsigned long BN_get_word(BIGNUM *a); 假如a能表示为long型,那么返回一个long型数
8.随机数函数
int BN_rand(BIGNUM *rnd, int bits, int top, int bottom);
产生一个加密用的强bits的伪随机数,
若top=-1,最高位为0,top=0,最高位为1,top=1,最高位和次高位为1,bottom为真,随机数为偶数
int BN_pseudo_rand(BIGNUM *rnd, int bits, int top, int bottom);
产生一个伪随机数,应用于某些目的。
int BN_rand_range(BIGNUM *rnd, BIGNUM *range); 产生的0<rnd<range
int BN_pseudo_rand_range(BIGNUM *rnd, BIGNUM *range); 同上面道理
9.产生素数函数
BIGNUM *BN_generate_prime(BIGNUM *ret, int bits,int safe, BIGNUM *add,
BIGNUM *rem, void (*callback)(int, int, void *), void *cb_arg);
产生一个bits位的素数,后面几个参数都可以为NULL
int BN_is_prime(const BIGNUM *p, int nchecks,
void (*callback)(int, int, void *), BN_CTX *ctx, void *cb_arg);
判断是否为素数,返回0表示成功,1表示错误概率小于0。25,-1表示错误
10.位数函数
int BN_set_bit(BIGNUM *a, int n); 将a中的第n位设置为1,假如a小于n位将扩展
int BN_clear_bit(BIGNUM *a, int n); 将a中的第n为设置为0,假如a小于n位将出错
int BN_is_bit_set(const BIGNUM *a, int n);测试是否已经设置,1表示已设置
int BN_mask_bits(BIGNUM *a, int n); 将a截断至n位,假如a小于n位将出错
int BN_lshift(BIGNUM *r, const BIGNUM *a, int n); a左移n位,结果存于r
int BN_lshift1(BIGNUM *r, BIGNUM *a); a左移1位,结果存于r
int BN_rshift(BIGNUM *r, BIGNUM *a, int n); a右移n位,结果存于r
int BN_rshift1(BIGNUM *r, BIGNUM *a); a左移1位,结果存于r
11.与字符串的转换函数
int BN_bn2bin(const BIGNUM *a, unsigned char *to);将abs(a)转化为字符串存入to,to的空间必须大于BN_num_bytes(a)
BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret);将s中的len位的正整数转化为大数
char *BN_bn2hex(const BIGNUM *a);转化为16进制字符串
char *BN_bn2dec(const BIGNUM *a);转化为10进制字符串
int BN_hex2bn(BIGNUM **a, const char *str);同上理
int BN_dec2bn(BIGNUM **a, const char *str);同上理
int BN_print(BIO *fp, const BIGNUM *a);将大数16进制形式写入内存中
int BN_print_fp(FILE *fp, const BIGNUM *a); 将大数16进制形式写入文件
int BN_bn2mpi(const BIGNUM *a, unsigned char *to);
BIGNUM *BN_mpi2bn(unsigned char *s, int len, BIGNUM *ret);
12.其他函数
下面函数可以进行更有效率的模乘和模除,假如在重复在同一模下重复进行模乘和模除计算,计算r=(a*b)%m 利用了recp=1/m
BN_RECP_CTX *BN_RECP_CTX_new(void);
void BN_RECP_CTX_init(BN_RECP_CTX *recp);
void BN_RECP_CTX_free(BN_RECP_CTX *recp);
int BN_RECP_CTX_set(BN_RECP_CTX *recp, const BIGNUM *m, BN_CTX *ctx);
int BN_mod_mul_reciprocal(BIGNUM *r, BIGNUM *a, BIGNUM *b,
BN_RECP_CTX *recp, BN_CTX *ctx);
下面函数采用蒙哥马利算法进行模幂计算,可以提高效率,他也主要应用于在同一模下进行多次幂运算
BN_MONT_CTX *BN_MONT_CTX_new(void);
void BN_MONT_CTX_init(BN_MONT_CTX *ctx);
void BN_MONT_CTX_free(BN_MONT_CTX *mont);
int BN_MONT_CTX_set(BN_MONT_CTX *mont, const BIGNUM *m, BN_CTX *ctx);
BN_MONT_CTX *BN_MONT_CTX_copy(BN_MONT_CTX *to, BN_MONT_CTX *from);
int BN_mod_mul_montgomery(BIGNUM *r, BIGNUM *a, BIGNUM *b,
BN_MONT_CTX *mont, BN_CTX *ctx);
int BN_from_montgomery(BIGNUM *r, BIGNUM *a, BN_MONT_CTX *mont,
BN_CTX *ctx);
int BN_to_montgomery(BIGNUM *r, BIGNUM *a, BN_MONT_CTX *mont,
BN_CTX *ctx);
12 BASE64编解码¶
12.1 BASE64编码介绍¶
BASE64编码是一种常用的将十六进制数据转换为可见字符的编码。
与ASCII码相比,它占用的空间较小。BASE64编码在rfc3548中定义。
12.2 BASE64编解码原理¶
将数据编码成BASE64编码时,以3字节数据为一组,转换为24bit的二进制数,
将24bit的二进制数分成四组,每组6bit。
对于每一组,得到一个数字:0-63。然后根据这个数字查表即得到结果。
表如下:
Value Encoding Value Encoding Value Encoding Value Encoding
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 +
12 M 29 d 46 u 63 /
13 N 30 e 47 v
14 O 31 f 48 w (pad) =
15 P 32 g 49 x
16 Q 33 h 50 y
比如有数据:0x30 0x82 0x02
编码过程如下:
1)得到16进制数据: 30 82 02
2)得到二进制数据: 00110000 10000010 00000010
3)每6bit分组: 001100 001000 001000 000010
4)得到数字: 12 8 8 2
5)根据查表得到结果 : M I I C
BASE64填充:在不够的情况下在右边加0。
有三种情况:
1) 输入数据比特数是24的整数倍(输入字节为3字节整数倍),则无填充;
2) 输入数据最后编码的是1个字节(输入数据字节数除3余1),即8比特,则需要填充2个"==",
因为要补齐6比特,需要加2个00;
3) 输入数据最后编码是2个字节(输入数据字节数除3余2),
则需要填充1个"=",因为补齐6比特,需要加一个00。
举例如下:
对0x30编码:
1) 0x30的二进制为:00110000
2) 分组为:001100 00
3) 填充2个00:001100 000000
4) 得到数字:12 0
5) 查表得到的编码为MA,另外加上两个==
所以最终编码为:MA==
base64解码是其编码过程的逆过程。
解码时,将base64编码根据表展开,根据有几个等号去掉结尾的几个00,
然后每8比特恢复即可。
12.3 BASE64编解码原理¶
Openssl中用于base64编解码的函数主要有:
1)编码函数
EVP_EncodeInit
编码前初始化上下文。
EVP_EncodeUpdate
进行BASE64编码,本函数可多次调用。
EVP_EncodeFinal
进行BASE64编码,并输出结果。
EVP_EncodeBlock
进行BASE64编码。
2) 解码函数
EVP_DecodeInit
解码前初始化上下文。
EVP_DecodeUpdate
BASE64解码,本函数可多次调用。
EVP_DecodeFinal
BASE64解码,并输出结果。
EVP_DecodeBlock
BASE64解码,可单独调用。
12.4 编程示例¶
示例1¶
#include <string.h>
#include <openssl/evp.h>
int main()
{
EVP_ENCODE_CTX *ectx = NULL,*dctx = NULL;
ectx = EVP_ENCODE_CTX_new();
dctx = EVP_ENCODE_CTX_new();
unsigned char in[500],out[800],d[500];
int inl,outl,i,total,ret,total2;
EVP_EncodeInit(ectx);
for(i = 0;i < 500;i ++){
memset(&in[i],i,1);
}
inl = 500;
total = 0;
EVP_EncodeUpdate(ectx,out,&outl,in,inl);
total += outl;
EVP_EncodeFinal(ectx,out+total,&outl);
total += outl;
printf("%s\n",out);
EVP_DecodeInit(dctx);
outl = 500;
total2 = 0;
ret=EVP_DecodeUpdate(dctx,d,&outl,out,total);
if(ret < 0) {
printf("EVP_DecodeUpdate err!\n");
return -1;
}
total2 += outl;
ret=EVP_DecodeFinal(dctx,d,&outl);
total2 += outl;
EVP_ENCODE_CTX_free(ectx);
EVP_ENCODE_CTX_free(dctx);
return 0;
}
/*
*
本例中先编码再解码。
编码调用次序为EVP_EncodeInit、EVP_EncodeUpdate(可以多次)和EVP_EncodeFinal。
解码调用次序为EVP_DecodeInit、EVP_DecodeUpdate(可以多次)和EVP_DecodeFinal。
注意:采用上述函数BASE64编码的结果不在一行,解码所处理的数据也不在一行。
用上述函数进行BASE64编码时,输出都是格式化输出。
特别需要注意的是,BASE64解码时如果某一行字符格式超过80个,会出错。
如果要BASE64编码的结果不是格式化的,可以直接调用函数:EVP_EncodeBlock。
同样对于非格式化数据的BASE64解码可以调用EVP_DecodeBlock函数,
不过用户需要自己去除后面填充的0。
*/
示例2¶
#include <string.h>
#include <openssl/evp.h>
int main()
{
unsigned char in[500],out[800],d[500],*p;
int inl,i,len,pad;
for(i = 0;i < 500;i ++){
memset(&in[i],i,1);
}
printf("please input how much(<500) to base64 : \n");
scanf("%d",&inl);
len = EVP_EncodeBlock(out,in,inl);
printf("%s\n",out);
p = out + len - 1;
pad = 0;
for(i = 0;i < 4;i ++) {
if(*p == '='){
pad ++;
}
p--;
}
len = EVP_DecodeBlock(d,out,len);
len -= pad;
if((len != inl) || (memcmp(in,d,len))){
printf("err!\n");
}
printf("test ok.\n");
return 0;
}
13 ASN1库¶
13.1 ASN1简介¶
13.2 DER编码¶
13.3 ASN1基本类型示例¶
13.4 openssl的ASN.1 库¶
13.5 用openssl的ASN.1 库 DER编解码¶
13.6 openssl的ASN.1 宏¶
13.7 ASN1常用函数¶
13.8 属性证书编码¶
14 错误处理¶
15 摘要与HAMC¶
16 数据压缩¶
17 RSA¶
17.1 RSA介绍¶
RSA算法是一个广泛使用的公钥算法。
其密钥包括公钥和私钥。它能用于数字签名、身份认证以及密钥交换。
RSA密钥长度一般使用1024位或者更高。RSA密钥信息主要包括[1]:
n : 模数
e : 公钥指数
d : 私钥指数
p : 最初的大素数
q : 最初的大素数
dmp1 : e*dmp1 = 1 (mod (p-1))
dmq1 : e*dmq1 = 1 (mod (q-1))
iqmp : q*iqmp = 1 (mod p )
其中,公钥为n和e;私钥为n和d。
在实际应用中,公钥加密一般用来协商密钥;私钥加密一般用来签名。
17.2 openssl的RSA实现¶
Openssl的RSA实现源码在crypto/rsa目录下。它实现了RSA PKCS1标准。主要源码如下:
1) rsa.h
定义RSA数据结构以及RSA_METHOD,定义了RSA的各种函数。
2) rsa_asn1.c
实现了RSA密钥的DER编码和解码,包括公钥和私钥。
3) rsa_chk.c
RSA密钥检查。
4) rsa_eay.c
Openssl实现的一种RSA_METHOD,作为其默认的一种RSA计算实现方式。
此文件未实现rsa_sign、rsa_verify和rsa_keygen回调函数。
5)rsa_err.c
RSA错误处理。
6)rsa_gen.c
RSA密钥生成,如果RSA_METHOD中的rsa_keygen回调函数不为空,则调用它,
否则调用其内部实现。
7)rsa_lib.c
主要实现了RSA运算的四个函数(公钥/私钥,加密/解密),
它们都调用了RSA_METHOD中相应都回调函数。
8)rsa_none.c
实现了一种填充和去填充。
9)rsa_null.c
实现了一种空的RSA_METHOD。
10) rsa_oaep.c
实现了oaep填充与去填充。
11)rsa_pk1.
实现了pkcs1填充与去填充。
12)rsa_sign.c
实现了RSA的签名和验签。
13)rsa_ssl.c
实现了ssl填充。
14)rsa_x931.c
实现了一种填充和去填充。
17.3 RSA签名与验证过程¶
RSA签名过程如下:
1) 对用户数据进行摘要;
2)构造X509_SIG结构并DER编码,其中包括了摘要算法以及摘要结果。
3)对2)的结果进行填充,填满RSA密钥长度字节数。
比如1024位RSA密钥必须填满128字节。具体的填充方式由用户指定。
4)对3)的结果用RSA私钥加密。
RSA_eay_private_encrypt函数实现了3)和4)过程。
RSA验签过程是上述过程的逆过程,如下:
1) 对数据用RSA公钥解密,得到签名过程中2)的结果。
2) 去除1)结果的填充。
3) 从2)的结果中得到摘要算法,以及摘要结果。
4) 将原数据根据3)中得到摘要算法进行摘要计算。
5)比较4)与签名过程中1)的结果。
RSA_eay_public_decrypt实现了1)和2)过程。
17.4 数据结构¶
RSA主要数据结构定义在crypto/rsa/rsa.h中:
17.4.1 RSA_METHOD¶
struct rsa_meth_st {
const char *name;
int (*rsa_pub_enc)(int flen, const unsigned char *from, unsigned char *to, RSA *rsa,int padding);
int (*rsa_pub_dec)(int flen, const unsigned char *from, unsigned char *to, RSA *rsa,int padding);
int (*rsa_priv_enc)(int flen, const unsigned char *from, unsigned char *to, RSA *rsa,int padding);
int (*rsa_priv_dec)(int flen, const unsigned char *from, unsigned char *to, RSA *rsa,int padding);
/* 其他函数 */
int (*rsa_sign)(int type, const unsigned char *m, unsigned int m_length,
unsigned char *sigret, unsigned int *siglen, const RSA *rsa);
int (*rsa_verify)(int dtype,const unsigned char *m, unsigned int m_length,
unsigned char *sigbuf, unsigned int siglen, const RSA *rsa);
int (*rsa_keygen)(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb);
};
主要项说明:
name : RSA_METHOD名称;
rsa_pub_enc : 公钥加密函数,padding为其填充方式,输入数据不能太长,否则无法填充;
rsa_pub_dec : 公钥解密函数,padding为其去除填充的方式,输入数据长度为RSA密钥长度的字节数;
rsa_priv_enc : 私钥加密函数,padding为其填充方式,输入数据长度不能太长,否则无法填充;
rsa_priv_dec : 私钥解密函数,padding为其去除填充的方式,输入数据长度为RSA密钥长度的字节数;
rsa_sign : 签名函数;
rsa_verify : 验签函数;
rsa_keygen : RSA密钥对生成函数。
用户可实现自己的RSA_METHOD来替换openssl提供的默认方法。
17.4.2 RSA¶
RSA数据结构中包含了公/私钥信息(如果仅有 n 和 e,则表明是公钥),定义如下:
struct rsa_st {
/* 其他 */
const RSA_METHOD *meth;
ENGINE *engine;
BIGNUM *n;
BIGNUM *e;
BIGNUM *d;
BIGNUM *p;
BIGNUM *q;
BIGNUM *dmp1;
BIGNUM *dmq1;
BIGNUM *iqmp;
CRYPTO_EX_DATA ex_data;
int references;
/* 其他数据项 */
};
各项意义:
meth :RSA_METHOD结构,指明了本RSA密钥的各种运算函数地址;
engine:硬件引擎;
n,e,d,p,q,dmp1,dmq1,iqmp:RSA密钥的各个值;
ex_data:扩展数据结构,用于存放用户数据;
references:RSA结构引用数。
17.5 主要函数¶
1) RSA_check_key
检查RSA密钥。
2)RSA_new
生成一个RSA密钥结构,并采用默认的rsa_pkcs1_eay_meth RSA_METHOD方法。
3)RSA_free
释放RSA结构。
4) RSA *RSA_generate_key( int bits,
unsigned long e_value,
void (*callback)(int,int,void *),
void *cb_arg )
生成RSA密钥.
bits : 是模数比特数,
e_value : 是公钥指数e,
callback : 回调函数由用户实现,用于干预密钥生成过程中的一些运算,可为空。
5)RSA_get_default_method
获取默认的 RSA_METHOD,为 rsa_pkcs1_eay_meth。
6)RSA_get_ex_data
获取扩展数据。
7)RSA_get_method
获取RSA结构的 RSA_METHOD。
8)RSA_padding_add_none
RSA_padding_add_PKCS1_OAEP
RSA_padding_add_PKCS1_type_1(私钥加密的填充)
RSA_padding_add_PKCS1_type_2(公钥加密的填充)
RSA_padding_add_SSLv23
各种填充方式函数。
9)RSA_padding_check_none
RSA_padding_check_PKCS1_OAEP
RSA_padding_check_PKCS1_type_1
RSA_padding_check_PKCS1_type_2
RSA_padding_check_SSLv23
RSA_PKCS1_SSLeay
各种去除填充函数。
10)int RSA_print(BIO *bp, const RSA *x, int off)
将RSA信息输出到 BIO 中,off 为输出信息在BIO中的偏移量,
比如是屏幕BIO,则表示打印信息的位置离左边屏幕边缘的距离。
11)int DSA_print_fp(FILE *fp, const DSA *x, int off)
将RSA信息输出到FILE中,off为输出偏移量。
12)RSA_public_decrypt
RSA公钥解密。
13)RSA_public_encrypt
RSA公钥加密。
14)RSA_set_default_method/ RSA_set_method
设置RSA结构中的method,当用户实现了一个RSA_METHOD时,
调用此函数来设置,使RSA运算采用用户的方法。
15)RSA_set_ex_data
设置扩展数据。
16)RSA_sign
RSA签名。
17)RSA_sign_ASN1_OCTET_STRING
另外一种RSA签名,不涉及摘要算法,它将输入数据作为 ASN1_OCTET_STRING 进行 DER 编码,
然后直接调用 RSA_private_encrypt 进行计算。
18)RSA_size
获取RSA密钥长度字节数。
19)RSA_up_ref
给RSA密钥增加一个引用。
20)RSA_verify
RSA验证。
21)RSA_verify_ASN1_OCTET_STRING
另一种 RSA 验证,不涉及摘要算法,与 RSA_sign_ASN1_OCTET_STRING 对应。
22)RSAPrivateKey_asn1_meth
获取 RSA 私钥的 ASN1_METHOD,包括 i2d、d2i、new 和 free 函数地址。
23)RSAPrivateKey_dup
复制RSA私钥。
24)RSAPublicKey_dup
复制RSA公钥。
TYPE *d2i_TYPE(TYPE **a, unsigned char **ppin, long length);
TYPE *d2i_TYPE_bio(BIO *bp, TYPE **a);
TYPE *d2i_TYPE_fp(FILE *fp, TYPE **a);
int i2d_TYPE(TYPE *a, unsigned char **ppout);
int i2d_TYPE_fp(FILE *fp, TYPE *a);
int i2d_TYPE_bio(BIO *bp, TYPE *a);
Type :
d2i_RSAPrivateKey, d2i_RSAPrivateKey_bio, d2i_RSAPrivateKey_fp,
d2i_RSAPublicKey, d2i_RSAPublicKey_bio, d2i_RSAPublicKey_fp,
d2i_RSA_OAEP_PARAMS,
d2i_RSA_PSS_PARAMS,
d2i_RSA_PUBKEY, d2i_RSA_PUBKEY_bio, d2i_RSA_PUBKEY_fp
i2d_RSAPrivateKey, i2d_RSAPrivateKey_bio, i2d_RSAPrivateKey_fp,
i2d_RSAPublicKey, i2d_RSAPublicKey_bio, i2d_RSAPublicKey_fp,
i2d_RSA_OAEP_PARAMS,
i2d_RSA_PSS_PARAMS,
i2d_RSA_PUBKEY, i2d_RSA_PUBKEY_bio, i2d_RSA_PUBKEY_fp
17.6 编程示例¶
17.6.1 密钥生成¶
#include <openssl/rsa.h>
int main()
{
RSA *r;
int bits=512,ret;
unsigned long e=RSA_3;
BIGNUM *bne;
r=RSA_generate_key(bits,e,NULL,NULL); //不推荐使用
RSA_print_fp(stdout,r,11);
RSA_free(r);
bne=BN_new();
ret=BN_set_word(bne,e);
r=RSA_new();
ret=RSA_generate_key_ex(r,bits,bne,NULL);
if(ret!=1) {
printf("RSA_generate_key_ex err!\n");
return -1;
}
printf("\n-------------\n");
RSA_print_fp(stdout,r,11);
RSA_free(r);
return 0;
}
17.6.2 RSA加解密运算¶
#include <openssl/rsa.h>
#include <openssl/sha.h>
int main()
{
RSA *r;
int bits=1024, ret,len, flen, padding,i;
unsigned long e = RSA_3;
BIGNUM *bne;
unsigned char *key,*p;
BIO *b;
unsigned char from[500],to[500],out[500];
bne=BN_new();
ret=BN_set_word(bne,e);
r=RSA_new();
ret=RSA_generate_key_ex(r,bits,bne,NULL);
if(ret!=1) {
printf("RSA_generate_key_ex err!\n"); return -1;
}
/* 私钥 i2d */
b=BIO_new(BIO_s_mem());
ret=i2d_RSAPrivateKey_bio(b,r);
key=malloc(1024);
len=BIO_read(b,key,1024);
BIO_free(b);
b=BIO_new_file("rsa.key","w");
ret=i2d_RSAPrivateKey_bio(b,r);
BIO_free(b);
/* 私钥 d2i */
/* 公钥 i2d */
/* 公钥 d2i */
/* 私钥加密 */
flen=RSA_size(r);
printf("please select private enc padding : \n");
printf("1.RSA_PKCS1_PADDING\n");
printf("3.RSA_NO_PADDING\n");
printf("5.RSA_X931_PADDING\n");
scanf("%d",&padding);
if(padding==RSA_PKCS1_PADDING)
flen-=11;
else if(padding==RSA_X931_PADDING)
flen-=2;
else if(padding==RSA_NO_PADDING)
flen=flen;
else{
printf("rsa not surport !\n"); return -1;
}
for(i=0;i<flen;i++)
memset(&from[i],i,1);
len=RSA_private_encrypt(flen,from,to,r,padding);
if(len<=0){
printf("RSA_private_encrypt err!\n");
return -1;
}
len=RSA_public_decrypt(len,to,out,r,padding);
if(len<=0){
printf("RSA_public_decrypt err!\n");
return -1;
}
if(memcmp(from,out,flen)) {
printf("err!\n");
return -1;
}
/* */
printf("please select public enc padding : \n");
printf("1.RSA_PKCS1_PADDING\n");
printf("2.RSA_SSLV23_PADDING\n");
printf("3.RSA_NO_PADDING\n");
printf("4.RSA_PKCS1_OAEP_PADDING\n");
scanf("%d",&padding);
flen=RSA_size(r);
if(padding==RSA_PKCS1_PADDING)
flen-=11;
else if(padding==RSA_SSLV23_PADDING)
flen-=11;
else if(padding==RSA_X931_PADDING)
flen-=2;
else if(padding==RSA_NO_PADDING)
flen=flen;
else if(padding==RSA_PKCS1_OAEP_PADDING)
flen=flen-2 * SHA_DIGEST_LENGTH-2 ;
else {
printf("rsa not surport !\n"); return -1;
}
for(i=0;i<flen;i++)
memset(&from[i],i+1,1);
len=RSA_public_encrypt(flen,from,to,r,padding);
if(len<=0){
printf("RSA_public_encrypt err!\n");
return -1;
}
len=RSA_private_decrypt(len,to,out,r,padding);
if(len<=0) {
printf("RSA_private_decrypt err!\n");
return -1;
}
if(memcmp(from,out,flen)) {
printf("err!\n");
return -1;
}
printf("test ok!\n"); RSA_free(r); return 0;
}
17.6.3 签名与验证¶
#include <string.h>
#include <openssl/objects.h>
#include <openssl/rsa.h>
int main()
{
int ret;
RSA *r;
int i, bits=1024, signlen, datalen, alg, nid;
unsigned long e = RSA_3;
BIGNUM *bne;
unsigned char data[100],signret[200];
bne=BN_new();
ret=BN_set_word(bne,e);
r=RSA_new();
ret=RSA_generate_key_ex(r,bits,bne,NULL);
if(ret!=1) {
printf("RSA_generate_key_ex err!\n");
return -1;
}
for(i = 0;i < 100;i ++){
memset(&data[i], i+1, 1);
}
printf("please select digest alg: \n");
printf("1.NID_md5\n");
printf("2.NID_sha\n");
printf("3.NID_sha1\n");
printf("4.NID_md5_sha1\n");
scanf("%d",&alg);
if(alg == 1) {
datalen = 55;
nid=NID_md5;
}
else if(alg == 2) {
datalen = 55;
nid=NID_sha;
}
else if(alg == 3) {
datalen = 55;
nid=NID_sha1;
}
else if(alg == 4) {
datalen = 36;
nid=NID_md5_sha1;
}
ret=RSA_sign(nid,data,datalen,signret,&signlen,r);
if(ret != 1) {
printf("RSA_sign err!\n");
RSA_free(r);
return -1;
}
ret=RSA_verify(nid,data,datalen,signret,signlen,r);
if(ret!=1) {
printf("RSA_verify err!\n");
RSA_free(r);
return -1;
}
RSA_free(r);
printf("test ok!\n");
return 0;
}
注意:本示例并不是真正的数据签名示例,因为没有做摘要计算。
ret=RSA_sign(nid,data,datalen,signret,&signlen,r)
将需要运算的数据放入X509_ALGOR数据结构并将其DER编码,
编码结果做RSA_PKCS1_PADDING 再进行私钥加密。
被签名数据应该是摘要之后的数据,
而本例没有先做摘要,接将数据拿去做运算.
因此 datalen 不能太长,保证 RSA_PKCS1_PADDING 私钥加密运算时输入数据的长度限制。
ret = RSA_verify(nid,data,datalen,signret,signlen,r)用来验证签名。
- 参考文献:
- [1] PKCS #1 v2.1: RSA Cryptography Standard
20 椭圆曲线¶
20.1 ECC介绍¶
椭圆曲线算法可以看作是定义在特殊集合下数的运算,满足一定的规则。
椭圆曲线在如 下两个域中定义:Fp 域和 F2m 域。
Fp 域,素数域,p 为素数;
F2m 域:特征为 2 的有限域,称之为二元域或者二进制扩展域。该域中,元素的个数为 2m 个。
椭圆曲线标准文档如下:
1) X9.62
Public Key Cryptography For The Financial Services Industry: The Elliptic Curve
Digital Signature Algorithm (ECDSA);
2) SEC1
SEC 1:Elliptic Curve Cryptography;
3) SEC2
SEC 2: Recommended Elliptic Curve Domain Parameters;
4) NIST
(U.S.) National Institute of Standards and Technology,美国国家标准。
这些标准一般都描述了 Fp 域和 F2m 域、椭圆曲线参数、数据转换、密钥生成以及
推荐了多种椭圆曲线。
一些术语说明:
1) 椭圆曲线的阶(order of a curve)
椭圆曲线所有点的个数,包括无穷远点;
2) 椭圆曲线上点的阶(order of a point)
P 为椭圆曲线上的点,nP=无穷远点,n 取最小整数,既是 P 的阶;
3) 基点(base point)
椭圆曲线参数之一,用 G 表示,是椭圆曲线上都一个点;
4) 余因子(cofactor)
椭圆曲线的余因子,用 h 表示,为椭圆曲线点的个数/基点的阶
5) 椭圆曲线参数:
素数域:
(p,a,b,G,n,h)
其中,p 为素数,确定 Fp,a 和 b 确定椭圆曲线方程,G 为基点,
n 为 G 的阶, h 为余因子。
二进制扩展域:
(m,f(x),a,b,G,n,h)
其中,m 确定 F2m,f(x)为不可约多项式,a 和 b 用于确定椭圆曲线方程,G 为基点,
n 为 G 的阶, h 为余因子。
6) 椭圆曲线公钥和私钥
椭圆曲线的私钥是一个随机整数,小于 n;
椭圆曲线的公钥是椭圆曲线上的一个点: Q = 私钥 * G。
20.2 openssl的ECC实现¶
Openssl实现了ECC算法。
ECC算法系列包括三部分:
1. ECC算法 (crypto/ec)、
2. 椭圆曲线数字签名算法 ECDSA (crypto/ecdsa)
3. 圆曲线密钥交换算法 ECDH (crypto/dh)
研究椭圆曲线需要注意的有:
1) 密钥数据结构
主要是 公钥 和 私钥 数据结构。
椭圆曲线密钥数据结构如下,定义在/crypto/ec/ec_lcl.h:272 中,对用户是透明的。
struct ec_key_st {
int version;
EC_GROUP *group;
EC_POINT *pub_key;
BIGNUM *priv_key;
/* 其他项 */
}
EC_POINT据结构如下,定义在/crypto/ec/ec_lcl.h: 287 中,
struct ec_point_st {
const EC_METHOD *meth;
/* NID for the curve if known */
int curve_name;
/*
* All members except 'meth' are handled by the method functions, even if
* they appear generic
*/
BIGNUM *X;
BIGNUM *Y;
BIGNUM *Z; /* Jacobian projective coordinates: * (X, Y,
* Z) represents (X/Z^2, Y/Z^3) if Z != 0 */
int Z_is_one; /* enable optimized point arithmetics for
* special case */
};
include/openssl/ec.h:46
typedef struct ec_point_st EC_POINT;
2) 密钥生成
对照公钥和私钥的表示方法,非对称算法不同有各自的密钥生成过程。
椭圆曲线的密钥生成实现在crytpo/ec/ec_key.c中。
Openssl中,椭圆曲线密钥生成时,首先用户需要选取一种椭圆曲线
(openssl的crypto/ec_curve.c中内置实现了67种,调用EC_get_builtin_curves获取该列表),
然后根据选择的椭圆曲线计算密钥生成参数group,最后根据密钥参数group来生公私钥。
3)签名值数据结构
非对称算法不同,签名的结果表示也不一样。
与DSA签名值一样,ECDSA的签名结果表示为两项。
ECDSA的签名结果数据结构定义在crypto/ecdsa/ecdsa.h中,如下:
typedef struct ECDSA_SIG_st {
BIGNUM *r;
BIGNUM *s;
} ECDSA_SIG;
4) 签名与验签
对照签名结果,研究其是如何生成的。
crypto/ecdsa/ ecs_sign.c实现了签名算法,crypto/ecdsa/ ecs_vrf.c实现了验签。
5)密钥交换
研究其密钥交换是如何进行的;crypto/ecdh/ech_ossl.c实现了密钥交换算法。
20.3 主要函数¶
20.3.1 参数设置¶
1) int EC_POINT_set_affine_coordinates_GF2m(
const EC_GROUP *group,
EC_POINT *point,
const BIGNUM *x,
const BIGNUM *y,
BN_CTX *ctx )
说明: 设置二进制域椭圆曲线上点 point 的几何坐标;
2) int EC_POINT_set_affine_coordinates_GFp(
const EC_GROUP *group,
EC_POINT *point,
const BIGNUM *x,
const BIGNUM *y,
BN_CTX *ctx )
说明: 设置素数域椭圆曲线上点 point 的几何坐标;
) int EC_POINT_set_affine_coordinates(const EC_GROUP *group, EC_POINT *p,
const BIGNUM *x, const BIGNUM *y,
BN_CTX *ctx);
说明:设置素数域椭圆曲线上点point的几何坐标;
The functions EC_POINT_set_affine_coordinates_GFp() and EC_POINT_set_affine_coordinates_GF2m()
are synonyms for EC_POINT_set_affine_coordinates().
They are defined for backwards compatibility only and should not be used.
3) int EC_POINT_set_compressed_coordinates_GF2m(
const EC_GROUP *group,
EC_POINT *point,
const BIGNUM *x,
int y_bit,
BN_CTX *ctx )
说明: 二进制域椭圆曲线,给定压缩坐标x和y_bit参数,设置point的几何坐标;
用于将 Octet-String转化为椭圆曲线上的点;
4) int EC_POINT_set_compressed_coordinates_GFp(
const EC_GROUP *group,
EC_POINT *point,
const BIGNUM *x,
int y_bit,
BN_CTX *ctx)
说明: 素数域椭圆曲线,给定压缩坐标x和y_bit参数,设置point的几何坐标;
用于将 Octet-String转化为椭圆曲线上的点;
5) int EC_POINT_set_Jprojective_coordinates_GFp(
const EC_GROUP *group,
EC_POINT *point,
const BIGNUM *x,
const BIGNUM *y,
const BIGNUM *z,
BN_CTX *ctx )
说明: 素数域椭圆曲线group,设置点point的投影坐标系坐标x、y和z;
6) int EC_POINT_set_to_infinity(const EC_GROUP *group, EC_POINT *point)
说明:将点 point 设为无穷远点
7) int EC_GROUP_set_curve_GF2m(
EC_GROUP *group,
const BIGNUM *p,
const BIGNUM *a,
const BIGNUM *b,
BN_CTX *ctx )
说明: 设置二进制域椭圆曲线参数;
8) int EC_GROUP_set_curve_GFp( EC_GROUP *group,
const BIGNUM *p,
const BIGNUM *a,
const BIGNUM *b,
BN_CTX *ctx )
说明: 设置素数域椭圆曲线参数;
9) int EC_GROUP_set_generator( EC_GROUP *group,
const EC_POINT *generator,
const BIGNUM *order,
const BIGNUM *cofactor)
说明: 设置椭圆曲线的基G; generator、order和cofactor为输入参数;
10) size_t EC_GROUP_set_seed(EC_GROUP *group, const unsigned char *p, size_t len)
说明: 设置椭圆曲线随机数,用于生成a和b;
11) EC_GROUP *EC_GROUP_new_curve_GF2m( const BIGNUM *p,
const BIGNUM *a,
const BIGNUM *b,
BN_CTX *ctx)
说明: 生成二进制域上的椭圆曲线,输入参数为p,a和b;
12) EC_GROUP *EC_GROUP_new_curve_GFp( const BIGNUM *p,
const BIGNUM *a,
const BIGNUM *b,
BN_CTX *ctx)
说明: 生成素数域上的椭圆曲线。
13) int EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n,
const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx);
点乘
EC_POINT_mul is a convenient interface to EC_POINTs_mul: it calculates the value generator * n + q * m and stores the result in r. The
value n may be NULL in which case the result is just q * m (variable point multiplication). Alternatively, both q and m may be NULL, and n
non-NULL, in which case the result is just generator * n (fixed point multiplication). When performing a single fixed or variable point
multiplication, the underlying implementation uses a constant time algorithm, when the input scalar (either n or m) is in the range [0,
ec_group_order).
14) int EC_POINTs_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, size_t num,
const EC_POINT *p[], const BIGNUM *m[], BN_CTX *ctx);
EC_POINTs_mul calculates the value generator * n + q[0] * m[0] + ... + q[num-1] * m[num-1]. As for EC_POINT_mul the value n may be NULL or
num may be zero. When performing a fixed point multiplication (n is non-NULL and num is 0) or a variable point multiplication (n is NULL
and num is 1), the underlying implementation uses a constant time algorithm, when the input scalar (either n or m[0]) is in the range [0,
ec_group_order).
20.3.2 参数获取¶
1) const EC_POINT *EC_GROUP_get0_generator(const EC_GROUP *group)
说明: 获取椭圆曲线的基(G);
2) unsigned char *EC_GROUP_get0_seed(const EC_GROUP *group)
说明: 获取椭圆曲线参数的随机数,该随机数可选,用于生成椭圆曲线参数中的a和b;
3) int EC_GROUP_get_basis_type(const EC_GROUP *group)
说明: 获取二进制域多项式的类型;
4) int EC_GROUP_get_cofactor(const EC_GROUP *group,
BIGNUM *cofactor,
BN_CTX *ctx)
说明: 获取椭圆曲线的余因子。cofactor为X9.62中定义的h,
值为椭圆曲线点的个数/基 点的阶,即:cofactor = #E(Fq)/n。
5) int EC_GROUP_get_curve_GF2m( const EC_GROUP *group,
BIGNUM *p,
BIGNUM *a,
BIGNUM *b,
BN_CTX *ctx)
说明: 获取二元域椭圆曲线的三个参数,其中p可表示多项式;
6) int EC_GROUP_get_curve_GFp( const EC_GROUP *group,
BIGNUM *p,
BIGNUM *a,
BIGNUM *b,
BN_CTX *ctx)
说明: 获取素数域椭圆曲线的三个参数;
7) int EC_GROUP_get_curve_name(const EC_GROUP *group)
说明: 获取椭圆曲线名称,返回其NID;
8) int EC_GROUP_get_degree(const EC_GROUP *group)
说明: 获取椭圆曲线密钥长度。对于素数域Fp来说,是大数p的长度;
对二进制域F2m 来说,等于m;
9) int EC_GROUP_get_order( const EC_GROUP *group,
BIGNUM *order,
BN_CTX *ctx)
说明: 获取椭圆曲线的阶;
10) int EC_GROUP_get_pentanomial_basis( const EC_GROUP *group,
unsigned int *k1,
unsigned int *k2,
unsigned int *k3)
int EC_GROUP_get_trinomial_basis(const EC_GROUP *group, unsigned int *k)
说明: 获取多项式参数;
11) int EC_POINT_get_affine_coordinates_GF2m( const EC_GROUP *group,
const EC_POINT *point,
BIGNUM *x,
BIGNUM *y,
BN_CTX *ctx)
说明: 获取二进制域椭圆曲线上某个点的x和y的几何坐标;
12) int EC_POINT_get_affine_coordinates_GFp(const EC_GROUP *group,
const EC_POINT *point,
BIGNUM *x,
BIGNUM *y,
BN_CTX *ctx)
说明: 获取素数域上椭圆曲线上某个点的x和y的几何坐标;
13) int EC_POINT_get_Jprojective_coordinates_GFp(const EC_GROUP *group,
const EC_POINT *point,
BIGNUM *x,
BIGNUM *y,
BIGNUM *z,
BN_CTX *ctx)
说明: 获取素数域椭圆曲线上某个点的x、y和z的投影坐标系坐标。
20.3.3 转化函数¶
1) EC_POINT *EC_POINT_bn2point( const EC_GROUP *group,
const BIGNUM *bn,
EC_POINT *point,
BN_CTX *ctx)
说明: 将大数转化为椭圆曲线上的点;
2) EC_POINT *EC_POINT_hex2point( const EC_GROUP *group,
const char *buf,
EC_POINT *point,
BN_CTX *ctx)
说明: 将buf中表示的十六进制数据转化为椭圆曲线上的点;
3) int BN_GF2m_poly2arr(const BIGNUM *a, unsigned int p[], int max)
说明: 将大数转化为多项式的各个项;
4) int BN_GF2m_arr2poly(const unsigned int p[], BIGNUM *a)
说明: 将多项式的各个项转化为大数;
5) int EC_POINT_make_affine( const EC_GROUP *group,
EC_POINT *point,
BN_CTX *ctx)
说明: 将椭圆曲线group上点的point转化为几何坐标系;
6) int EC_POINT_oct2point( const EC_GROUP *group,
EC_POINT *point,
const unsigned char *buf,
size_t len,
BN_CTX *ctx)
说明: 将buf中点数据转化为椭圆曲线上的点,len为数据长度;
7) BIGNUM *EC_POINT_point2bn( const EC_GROUP *group,
const EC_POINT *point,
point_conversion_form_t form,
BIGNUM *ret,
BN_CTX *ctx)
说明: 将椭圆曲线上的点转化为大数,其中from为压缩方式,可以是
POINT_CONVERSION_COMPRESSED、POINT_CONVERSION_UNCOMPRESSED或
POINT_CONVERSION_HYBRID,可参考x9.62;
8) char *EC_POINT_point2hex( const EC_GROUP *group,
const EC_POINT *point,
point_conversion_form_t form,
BN_CTX *ctx)
说明: 将椭圆曲线上的点转化为十六进制,并返回该结果;
9) size_t EC_POINT_point2oct( const EC_GROUP *group,
const EC_POINT *point,
point_conversion_form_t form,
unsigned char *buf,
size_t len,
BN_CTX *ctx)
说明: 将椭圆曲线上的点转化为Octet-String,可分两次调用,
用法见EC_POINT_point2bn 的实现。
20.3.4 其他函数¶
1) size_t EC_get_builtin_curves( EC_builtin_curve *r,size_t nitems)
说明:获取内置的椭圆曲线。
当输入参数 r == NULL 或者 nitems == 0时,返回内置椭圆曲线的个数,
否则将各个椭圆曲线信息存放在 r 中
2) const EC_METHOD *EC_GF2m_simple_method(void)
说明: 返回二进制域上的方法集 EC_METHOD
3) const EC_METHOD *EC_GFp_mont_method(void)
const EC_METHOD *EC_GFp_nist_method(void)
const EC_METHOD *EC_GFp_simple_method(void)
返回素数域上的方法集 EC_METHOD
4) int EC_GROUP_check(const EC_GROUP *group, BN_CTX *ctx)
说明: 检查椭圆曲线,成功返回 1。
5) int EC_GROUP_check_discriminant(const EC_GROUP *group, BN_CTX *ctx)
说明: 检查椭圆曲线表达式。
对于素数域的椭圆曲线来说,该函数会调用
ec_GFp_simple_group_check_discriminant 函数,
主要检查 4*a^3 + 27*b^2 != 0 (mod p)。
而对于二进制域的椭圆曲线,会调用
ec_GF2m_simple_group_check_discriminant,
检查 y^2 + x*y = x^3 + a*x^2 + b 是否是一个椭圆曲线
并且 b !=0。
6) int EC_GROUP_cmp(const EC_GROUP *a, const EC_GROUP *b, BN_CTX *ctx)
说明: 通过比较各个参数来确定两个椭圆曲线是否相等;
7) int EC_GROUP_copy(EC_GROUP *dest, const EC_GROUP *src)
EC_GROUP *EC_GROUP_dup(const EC_GROUP *a)
说明: 椭圆曲线拷贝函数;
9) EC_GROUP *EC_GROUP_new_by_curve_name(int nid)
说明: 根据NID获取内置的椭圆曲线;
10) int EC_KEY_check_key(const EC_KEY *eckey)
说明: 检查椭圆曲线密钥;
11) int EC_KEY_generate_key(EC_KEY *eckey)
说明: 生成椭圆曲线公私钥;
12) int EC_KEY_print(BIO *bp, const EC_KEY *x, int off)
说明: 将椭圆曲线密钥信息输出到bio中,off为缩进量;
13) int EC_POINT_add( const EC_GROUP *group,
EC_POINT *r,
const EC_POINT *a,
const EC_POINT *b,
BN_CTX *ctx)
说明: 椭圆曲线上点的加法;
14) int EC_POINT_invert(const EC_GROUP *group,
EC_POINT *a,
BN_CTX *ctx)
说明: 求椭圆曲线上某点a的逆元,a既是输入参数,也是输出参数;
15) int EC_POINT_is_at_infinity(const EC_GROUP *group,
const EC_POINT *point)
说明: 判断椭圆曲线上的点point是否是无穷远点;
16) int EC_POINT_is_on_curve( const EC_GROUP *group,
const EC_POINT *point,
BN_CTX *ctx)
说明: 判断一个点point是否在椭圆曲线上;
17) int ECDSA_size
说明: 获取 ECC 密钥大小字节数。
18) ECDSA_sign
说明: 签名,返回 1 表示成功。
19) ECDSA_verify
说明: 验签,返回 1 表示合法。
20) EC_KEY_get0_public_key
说明: 获取公钥。
21) EC_KEY_get0_private_key
说明: 获取私钥。
22) ECDH_compute_key
说明: 生成共享密钥
23) EC_KEY *d2i_ECPrivateKey(EC_KEY **a, const unsigned char **in, long len)
说明: DER 解码将椭圆曲线密钥;
24) int i2d_ECPrivateKey(EC_KEY *a, unsigned char **out)
说明: 将椭圆曲线密钥 DER 编码;
更多底层函数的使用示例可参考 ec/ectest.c,特别是用户自行定义椭圆曲线参数。
#include <openssl/ec.h>
#include <openssl/evp.h> //include NID_sm2
/*
https://www.openssl.org/docs/man1.1.0/crypto/EC_GROUP_new.html
typedef struct {
int nid;
const char *comment;
} EC_builtin_curve
*/
int main()
{
EC_builtin_curve *curves = NULL;
size_t crv_len = 0, n = 0;
int nid,ret;
EC_GROUP *group = NULL;
/*
size_t EC_get_builtin_curves(EC_builtin_curve *r, size_t nitems)
说明: 获取内置的椭圆曲线。
当输入参数 r 为 NULL 或者 nitems 为 0 时,返回内置椭圆曲线的个数,
否则将各个椭圆曲线信息存放在 r 中。
*/
crv_len = EC_get_builtin_curves(NULL, 0);
curves = OPENSSL_malloc(sizeof(EC_builtin_curve) * crv_len);
/* 获取椭圆曲线列表 */
EC_get_builtin_curves(curves, crv_len);
for (n=0;n<crv_len;n++) {
nid = curves[n].nid;
group=NULL;
printf("[%zd] nid = %d comment=%s \n",
n , curves[n].nid,curves[n].comment);
group = EC_GROUP_new_by_curve_name(nid);
ret=EC_GROUP_check(group,NULL); /*检查椭圆曲线,成功返回 1*/
}
group = EC_GROUP_new_by_curve_name(NID_sm2);
ret=EC_GROUP_check(group,NULL); /*检查椭圆曲线,成功返回 1*/
if ( ret == 1){
printf("\nEC_GROUP_checks seccess : NID_sm2 \n\n");
}
OPENSSL_free(curves);
return 0;
}
#include <string.h>
#include <stdio.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/objects.h>
#include <openssl/err.h>
int main()
{
EC_KEY *key1,*key2;
EC_POINT *pubkey1,*pubkey2;
EC_GROUP *group1,*group2;
int ret,nid,size,i,sig_len;
unsigned char *signature,digest[20];
BIO *berr;
EC_builtin_curve *curves;
int crv_len;
//char shareKey1[128*2],shareKey2[128*2];
uint8_t shareKey1[256],shareKey2[256];
int len1,len2;
/* 构造 EC_KEY 数据结构 */
key1=EC_KEY_new();
if(key1==NULL) {
printf("EC_KEY_new err!\n");
return -1;
}
key2=EC_KEY_new();
if(key2==NULL) {
printf("EC_KEY_new err!\n");
return -1;
}
/* 获取实现的椭圆曲线个数 */
crv_len = EC_get_builtin_curves(NULL, 0);
curves = (EC_builtin_curve *)malloc(sizeof(EC_builtin_curve) * crv_len);
/* 获取椭圆曲线列表 */
EC_get_builtin_curves(curves, crv_len);
/*
nid=curves[0].nid;会有错误,原因是密钥太短
*/
/* 选取一种椭圆曲线 */
//nid=curves[25].nid;
nid=curves[81].nid; // SM2
/* 根据选择的椭圆曲线生成密钥参数 group */
group1=EC_GROUP_new_by_curve_name(nid);
if(group1==NULL) {
printf("EC_GROUP_new_by_curve_name err!\n");
return -1;
}
group2=EC_GROUP_new_by_curve_name(nid);
if(group1==NULL) {
printf("EC_GROUP_new_by_curve_name err!\n");
return -1;
}
/* 设置密钥参数 */
ret=EC_KEY_set_group(key1,group1);
if(ret!=1) {
printf("EC_KEY_set_group err.\n");
return -1;
}
ret=EC_KEY_set_group(key2,group2);
if(ret!=1) {
printf("EC_KEY_set_group err.\n");
return -1;
}
/* 生成密钥 */
ret=EC_KEY_generate_key(key1);
if(ret!=1) {
printf("EC_KEY_generate_key err.\n");
return -1;
}
ret=EC_KEY_generate_key(key2);
if(ret!=1) {
printf("EC_KEY_generate_key err.\n");
return -1;
}
/* 检查密钥 */
ret=EC_KEY_check_key(key1);
if(ret!=1) {
printf("check key err.\n");
return -1;
}
/* 获取密钥大小 */
size=ECDSA_size(key1);
printf("size %d \n",size);
for(i=0;i<20;i++){
memset(&digest[i],i+1,1);
}
signature=malloc(size);
/*加载各自错误信息*/
ERR_load_crypto_strings();
berr=BIO_new(BIO_s_file());
BIO_set_fp(berr,stdout,BIO_NOCLOSE);
/*签名 验签*/
/* 签名数据,本例未做摘要,可将 digest 中的数据看作是 sha1 摘要结果 */
ret=ECDSA_sign(0,digest,20,signature,&sig_len,key1);
if(ret!=1) {
ERR_print_errors(berr); printf("sign err!\n"); return -1;
}
/* 验证签名 */
ret=ECDSA_verify(0,digest,20,signature,sig_len,key1);
if(ret!=1) {
ERR_print_errors(berr); printf("ECDSA_verify err!\n"); return -1;
}
/** DH 会话密钥**/
/* 获取对方公钥,不能直接引用 */
pubkey2 = EC_KEY_get0_public_key(key2);
/* 生成一方的共享密钥 */
len1=ECDH_compute_key(shareKey1, 256, pubkey2, key1, NULL);
/* printf */
for ( i = 0; i<len1; i ++){
printf("%02x:",shareKey1[i]);
}
printf("\n");
pubkey1 = EC_KEY_get0_public_key(key1);
/* 生成另一方共享密钥 */
len2=ECDH_compute_key(shareKey2, 256, pubkey1, key2, NULL);
if(len1!=len2) {
printf("err\n");
}
else {
ret=memcmp(shareKey1,shareKey2,len1);
if(ret==0)
printf("生成共享密钥成功\n");
else
printf("生成共享密钥失败\n");
}
printf("test ok!\n");
BIO_free(berr);
EC_KEY_free(key1);
EC_KEY_free(key2);
free(signature);
free(curves);
return 0;
}
21 EVP¶
21.1 EVP 简介¶
Openssl EVP(high-level cryptographic functions[1])提供了丰富的密码学中的各种函数。Openssl 中实现了各种对称算法、摘要算法以及签名/验签算法。EVP 函数将这些具体的算 法进行了封装。
EVP 主要封装了如下功能函数:
- 1)实现了 base64 编解码 BIO;
- 2)实现了加解密 BIO;
- 3)实现了摘要 BIO;
- 4)实现了 reliable BIO;
- 5)封装了摘要算法;
- 6)封装了对称加解密算法;
- 7)封装了非对称密钥的加密(公钥)、解密(私钥)、签名与验证以及辅助函数;
- 7)基于口令的加密(PBE);
- 8)对称密钥处理;
- 9)数字信封:数字信封用对方的公钥加密对称密钥,数据则用此对称密钥加密。 发送给对方时,同时发送对称密钥密文和数据密文。接收方首先用自己的私钥解密密钥 密文,得到对称密钥,然后用它解密数据。
- 10)其他辅助函数。
21.2 数据结构¶
EVP 数据结构定义在 crypto/evp.h 中
EVP_PKEY¶
该结构用来存放非对称密钥信息,可以是 RSA、DSA、DH 或 ECC 密钥。其中,ptr 用来存放密钥结构地址,attributes 堆栈用来存放密钥属性
/*
openssl-1.1.1/crypto/include/internal/evp_int.h:395
*/
/*
* Type needs to be a bit field Sub-type needs to be for variations on the
* method, as in, can it do arbitrary encryption....
*/
struct evp_pkey_st {
int type;
int save_type;
CRYPTO_REF_COUNT references;
const EVP_PKEY_ASN1_METHOD *ameth;
ENGINE *engine;
ENGINE *pmeth_engine; /* If not NULL public key ENGINE to use */
union {
void *ptr;
# ifndef OPENSSL_NO_RSA
struct rsa_st *rsa; /* RSA */
# endif
# ifndef OPENSSL_NO_DSA
struct dsa_st *dsa; /* DSA */
# endif
# ifndef OPENSSL_NO_DH
struct dh_st *dh; /* DH */
# endif
# ifndef OPENSSL_NO_EC
struct ec_key_st *ec; /* ECC */
ECX_KEY *ecx; /* X25519, X448, Ed25519, Ed448 */
# endif
} pkey;
int save_parameters;
STACK_OF(X509_ATTRIBUTE) *attributes; /* [ 0 ] */
CRYPTO_RWLOCK *lock;
} /* EVP_PKEY */ ;
/*
openssl-1.1.1/include/openssl/ossl_typ.h:93
*/
typedef struct evp_pkey_st EVP_PKEY;
EVP_MD¶
该结构用来存放摘要算法信息、非对称算法类型以及各种计算函数。主要各项意义如 下:
- type: 摘要类型,一般是摘要算法 NID;
- pkey_type: 公钥类型,一般是签名算法 NID;
- md_size: 摘要值大小,为字节数;
- flags: 用于设置标记;
- init: 摘要算法初始化函数;
- update: 多次摘要函数;
- final: 摘要完结函数;
- copy: 摘要上下文结构复制函数;
- cleanup: 清除摘要上下文函数;
- sign: 签名函数,其中 key 为非对称密钥结构地址;
- verify: 摘要函数,其中 key 为非对称密钥结构地址。
openssl 对于各种摘要算法实现了上述结构,各个源码位于 cypto/evp 目录下,文件名 以 m_开头。Openssl 通过这些结构来封装了各个摘要相关的运算
/*
openssl-1.1.1/crypto/include/internal/evp_int.h:115
*/
struct evp_md_st {
int type;
int pkey_type;
int md_size;
unsigned long flags;
int (*init) (EVP_MD_CTX *ctx);
int (*update) (EVP_MD_CTX *ctx, const void *data, size_t count);
int (*final) (EVP_MD_CTX *ctx, unsigned char *md);
int (*copy) (EVP_MD_CTX *to, const EVP_MD_CTX *from);
int (*cleanup) (EVP_MD_CTX *ctx);
int block_size;
int ctx_size; /* how big does the ctx->md_data need to be */
/* control function */
int (*md_ctrl) (EVP_MD_CTX *ctx, int cmd, int p1, void *p2);
} /* EVP_MD */ ;
/*
openssl-1.1.1/include/openssl/ossl_typ.h:91
*/
typedef struct evp_md_st EVP_MD;
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 通过这些结构来封装了对称算法相关的运算。
/*
openssl-1.1.1/crypto/include/internal/evp_int.h:131
*/
struct evp_cipher_st {
int nid;
int block_size;
/* Default value for variable length ciphers */
int key_len;
int iv_len;
/* Various flags */
unsigned long flags;
/* init key */
int (*init) (EVP_CIPHER_CTX *ctx, const unsigned char *key,
const unsigned char *iv, int enc);
/* encrypt/decrypt data */
int (*do_cipher) (EVP_CIPHER_CTX *ctx, unsigned char *out,
const unsigned char *in, size_t inl);
/* cleanup ctx */
int (*cleanup) (EVP_CIPHER_CTX *);
/* how big ctx->cipher_data needs to be */
int ctx_size;
/* Populate a ASN1_TYPE with parameters */
int (*set_asn1_parameters) (EVP_CIPHER_CTX *, ASN1_TYPE *);
/* Get parameters from a ASN1_TYPE */
int (*get_asn1_parameters) (EVP_CIPHER_CTX *, ASN1_TYPE *);
/* Miscellaneous operations */
int (*ctrl) (EVP_CIPHER_CTX *, int type, int arg, void *ptr);
/* Application data */
void *app_data;
} /* EVP_CIPHER */ ;
/*
openssl-1.1.1/include/openssl/ossl_typ.h:89
*/
typedef struct evp_cipher_st EVP_CIPHER;
EVP_CIPHER_CTX¶
- 对称算法上下文结构
- 此结构主要用来维护加解密状态,存放中间以及最后结果。因 为加密或解密时,当数据很多时,可能会用到 Update 函数,并且每次加密或解密的 输入数据长度任意的,并不一定是对称算法 block_size 的整数倍,所以需要用该结构 来存放中间未加密的数据。主要项意义如下:
- cipher: 指明对称算法;
- engine: 硬件引擎;
- encrypt: 是加密还是解密;非 0 为加密,0 为解密;
- buf 和 buf_len: 指明还有多少数据未进行运算;
- oiv: 原始初始化向量;
- iv: 当前的初始化向量;
- final: 存放最终结果,一般与 Final 函数对应。
/*
openssl-1.1.1/crypto/evp/evp_locl.h:24
*/
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/ctr mode */
/* FIXME: Should this even exist? It appears unused */
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 */ ;
/*
openssl-1.1.1/include/openssl/ossl_typ.h:90
*/
typedef struct evp_cipher_ctx_st EVP_CIPHER_CTX;
21.3 源码结构¶
evp 源码位于 crypto/evp 目录,可以分为如下几类:
- 全局函数
主要包括 c_allc.c、c_alld.c、c_all.c 以及 names.c。他们加载 openssl 支持的所 有的对称算法和摘要算法,放入到哈希表中。实现了 OpenSSL_add_all_digests、 OpenSSL_add_all_ciphers 以及 OpenSSL_add_all_algorithms(调用了前两个函数)函 数。在进行计算时,用户也可以单独加载摘要函数(EVP_add_digest)和对称计算 函数(EVP_add_cipher)。
- BIO 扩充
包括 bio_b64.c、bio_enc.c、bio_md.c 和 bio_ok.c,各自实现了BIO_METHOD法,分别用于 base64 编解码、对称加解密以及摘要。
- 摘要算法EVP封装
由 digest.c 实现,实现过程中调用了对应摘要算法的回调函数。各个摘要算法 提供了自己的 EVP_MD 静态结构,对应源码为 m_xxx.c。
- 对称算法 EVP 封装
由 evp_enc.c 实现,实现过程调用了具体对称算法函数,实现了 Update 操作。 各种对称算法都提供了一个 EVP_CIPHER 静态结构,对应源码为 e_xxx.c。需要注 意的是,e_xxx.c 中不提供完整的加解密运算,它只提供基本的对于一个 block_size 数据的计算,完整的计算由 evp_enc.c 来实现。当用户想添加一个自己的对称算法 时,可以参考 e_xxx.c 的实现方式。一般用户至少需要实现如下功能:
- 构造一个新的静态的EVP_CIPHER结构;
- 实现EVP_CIPHER结构中的init函数,该函数用于设置iv,设置加解密标
- 记、以及根据外送密钥生成自己的内部密钥;
- 实现do_cipher函数,该函数仅对block_size字节的数据进行对称运算;
- 实现cleanup函数,该函数主要用于清除内存中的密钥信息。
- 非对称算法EVP封装
主要是以 p_开头的文件。其中,p_enc.c 封装了公钥加密;p_dec.c 封装了私钥解密;p_lib.c 实现一些辅助函数;p_sign.c 封装了签名函数;p_verify.c 封装了验签函数;p_seal.c 封装了数字信封;p_open.c 封装了解数字信封。
- 基于口令的加密
包括 p5_crpt2.c、p5_crpt.c 和 evp_pbe.c。
21.4 摘要函数¶
查看帮助 : main EVP_md5
典型的摘要函数主要有:
1) EVP_md5
返回 md5 的 EVP_MD。
2) EVP_sha1
返回 sha1 的 EVP_MD。
3) EVP_sha256
返回 sha256 的 EVP_MD。
4) EVP_DigestInit
摘要初使化函数,需要有 EVP_MD 作为输入参数。
5) EVP_DigestUpdate 和 EVP_DigestInit_ex
摘要 Update 函数,用于进行多次摘要。
6) EVP_DigestFinal 和 EVP_DigestFinal_ex
摘要 Final 函数,用户得到最终结果。
7) EVP_Digest
对一个数据进行摘要,它依次调用了上述三个函数。
21.5 对称加解密函数¶
典型的加解密函数主要有:
查看帮助 : main EVP_CIPHER_CTX_init
1) EVP_CIPHER_CTX_init
初始化对称计算上下文。
2) EVP_CIPHER_CTX_cleanup
清除对称算法上下文数据,它调用用户提供的销毁函数销清除存中的内部密钥 以及其他数据。
3) EVP_des_ede3_ecb
返回一个 EVP_CIPHER;
4) EVP_EncryptInit 和 EVP_EncryptInit_ex
加密初始化函数,本函数调用具体算法的 init 回调函数,将外送密钥 key 转换为内部密钥形式,将初始化向量 iv 拷贝到 ctx 结构中。
5) EVP_EncryptUpdate
加密函数,用于多次计算,它调用了具体算法的 do_cipher 回调函数。
6) EVP_EncryptFinal 和 EVP_EncryptFinal_ex
获取加密结果,函数可能涉及填充,它调用了具体算法的 do_cipher 回调函数。
7) EVP_DecryptInit 和 EVP_DecryptInit_ex
解密初始化函数。
8) EVP_DecryptUpdate
解密函数,用于多次计算,它调用了具体算法的 do_cipher 回调函数。
9) EVP_DecryptFinal 和 EVP_DecryptFinal_ex
获取解密结果,函数可能涉及去填充,它调用了具体算法的 do_cipher 回调函数。
10) EVP_BytesToKey
计算密钥函数,它根据算法类型、摘要算法、salt 以及输入数据计算出一个对称密钥和初始化向量iv.
11) PKCS5_PBE_keyivgen 和 PKCS5_v2_PBE_keyivgen
实现了 PKCS5 基于口令生成密钥和初始化向量的算法。
12) PKCS5_PBE_add
加载所有 openssl 实现的基于口令生成密钥的算法。
13) EVP_PBE_alg_add
添加一个 PBE 算法。
#include <openssl/evp.h>
#include <openssl/rsa.h>
int main()
{
int ret, ekl[2], npubk, inl, outl, total=0, total2=0;
unsigned long e=RSA_3;
char *ek[2],iv[8],in[100],out[500],de[500];
//EVP_CIPHER_CTX ctx,ctx2;
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX *ctx2 = EVP_CIPHER_CTX_new();
EVP_CIPHER *type;
EVP_PKEY *pubkey[2];
RSA *rkey;
BIGNUM *bne;
/* 生成 RSA 密钥*/
bne=BN_new();
ret=BN_set_word(bne,e);
rkey=RSA_new();
ret=RSA_generate_key_ex(rkey,1024,bne,NULL);
pubkey[0]=EVP_PKEY_new();
EVP_PKEY_assign_RSA(pubkey[0],rkey);
type=EVP_des_cbc();
npubk=1;
//EVP_CIPHER_CTX_init(&ctx);
EVP_CIPHER_CTX_init(ctx);
ek[0]=malloc(500);
ek[1]=malloc(500);
//ret=EVP_SealInit(&ctx,type,ek,ekl,iv,pubkey,1);
ret=EVP_SealInit(ctx,type,ek,ekl,iv,pubkey,1);
/* 只有一个公钥*/
if(ret!=1) goto err;
strcpy(in,"openssl 编程");
inl=strlen(in);
//ret=EVP_SealUpdate(&ctx,out,&outl,in,inl);
ret=EVP_SealUpdate(ctx,out,&outl,in,inl);
if(ret!=1) goto err;
total+=outl;
//ret=EVP_SealFinal(&ctx,out+outl,&outl);
ret=EVP_SealFinal(ctx,out+outl,&outl);
if(ret!=1) goto err;
total+=outl;
memset(de,0,500);
//EVP_CIPHER_CTX_init(&ctx2);
EVP_CIPHER_CTX_init(ctx2);
//ret=EVP_OpenInit(&ctx2,EVP_des_cbc(),ek[0],ekl[0],iv,pubkey[0]);
ret=EVP_OpenInit(ctx2,EVP_des_cbc(),ek[0],ekl[0],iv,pubkey[0]);
if(ret!=1) goto err;
//ret=EVP_OpenUpdate(&ctx2,de,&outl,out,total);
ret=EVP_OpenUpdate(ctx2,de,&outl,out,total);
total2+=outl;
//ret=EVP_OpenFinal(&ctx2,de+outl,&outl);
ret=EVP_OpenFinal(ctx2,de+outl,&outl);
total2+=outl;
de[total2]=0;
printf("%s\n",de);
err:
EVP_CIPHER_CTX_free(ctx);
EVP_CIPHER_CTX_free(ctx2);
free(ek[0]);
free(ek[1]);
EVP_PKEY_free(pubkey[0]);
BN_free(bne);
getchar();
return 0;
}
FAQ¶
One of the primary differences between master (OpenSSL 1.1.0) and the 1.0.2 version is that many types have been made opaque, i.e. applications are no longer allowed to look inside the internals of the structures. The biggest impact on applications is that:
You cannot instantiate these structures directly on the stack. So instead of:
EVP_CIPHER_CTX ctx;
you must instead do:
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
/*
do something
*/
EVP_CIPHER_CTX_free(ctx);
SM2椭圆曲线公钥密码算法推荐曲线参数
推荐使用素数域256位椭圆曲线。
椭圆曲线方程:y
2 = x
3 + ax + b。
曲线参数:
p=FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF
a=FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC
b=28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93
n=FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123
Gx=32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7
Gy=BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0
《GBT 32918.1-2016 SM2椭圆曲线公钥密码算法 第1部分:总则》,第四章节提到椭圆曲线上点的压缩与解压缩。
这样SM2的公钥将可以占用更少的字节数。
《GMT 0009-2012 SM2密码算法使用规范》对SM2PublicKey做出规定,
使用非压缩方式,Bit String类型,内容是04||X||Y。
$ gmssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:sm2p256v1 -out private.pem
$ gmssl pkey -pubout -in private.pem -out public.pem
待整理¶
- BIGNUM
- EVP
- PEM
# sm2 test
cd /home/vagrant/encode_src/src/openssl-1.1.1/test/
bash sm2_build.sh
# gmssl
export LD_LIBRARY_PATH='/home/vagrant/usr/lib'
export PKG_CONFIG_PATH='/home/vagrant/usr/lib/pkgconfig'
cd /mnt/workspace/GmSSL/test
./sm2test
openssl site:http://www.newsmth.net