目录
- 简介
- 哈希表
- 内存分配
- 抽象IO
- Base64编码解码
- 常见的加密算法
- RSA
- 总结
简介
openssl一直以来在实际应用中都十分的广泛,内部集成了许多成熟的接口,可以直接调用,是一个功能十分丰富的工具箱。常见的用途在SSL协议实现 (包括SSLv2、SSLv3和TLSv1)、大量软算法(对称/非对称/摘要)、大数运算、非对称算法密钥生成、ASN.1编解码库、证书请求(PKCS10)编解码、数字证书编解码、CRL编解码、OCSP协议、数字证书验证、PKCS7标准实现和PKCS12个人数字证书格式实现等功能。
同时,openssl使用C编写的,具体比较好的跨平台性能,而且一般的系统都会预装一个openssl的版本,但是我们需要使用openssl的接口的话需要安装libssl-dev这个库。
另外如果想要自己安装特定的openssl的版本的话就需要自己去官网下载安装。https://www.OpenSSL.org/source/
安装手顺也很简单:
$ tar xzvf OpenSSL-1.1.0l.tar.gz
$ cd OpenSSL-1.1.0l
$ ./config –prefix=/usr/local/OpenSSL
$ make
$ sudo make install
哈希表
openssl内部整合了一套hash表实现的存储类型,对于数据的存储使用hash的方式,做到快速的查找。下面通过一个简单的例子演示一下用法。
#include <stdio.h>
#include <string.h>
#include <openssl/lhash.h>
#define NAME_LENGHT 32
typedef struct Student {
char name[NAME_LENGHT];
int high;
char hobby[NAME_LENGHT];
}Student;
// 比较函数
void demo_cmp(void* a1, void* b1)
{
char* s1 = ((Student*)a1)->name;
char* s2 = ((Student*)b1)->name;
return strcmp(s1, s2);
}
// 打印函数,具体的就是来表示数据的处理
void printValue(void* a)
{
Student* b = (Student*)a;
printf("student name:%s\n", b->name);
printf("student high:%d\n", b->high);
printf("student hobby:%s\n", b->hobby);
}
int main()
{
_LHASH* lh = lh_new(NULL, demo_cmp); // 现需要new一个lh的指针来指向新new出来的hash
if (lh == NULL) {
return -1;
}
Student stu1 = {"w", 178, "xx"};
Student stu2 = {"x", 179, "xx"};
Student stu3 = {"l", 180, "xx"};
lh_insert(lh, &stu1); // 将数据插入到hash中
lh_insert(lh, &stu2);
lh_insert(lh, &stu3);
lh_doall(lh, printValue); // 此函数表示对hash表中存储的每一个元素都执行第二个参数存进来的函数
const char* data = "l";
void* ret = lh_retrieve(lh, &data); // 查找数据,返回hash查询的值,如果没有找到就返回NULL,data传的种类很多,具体就是利用这个关键字查找
if (ret != NULL) {
printValue(ret);
}
lh_free(lh);
return 0;
}
上面的那部分代码是一个小的例子,new一个hash,然后插入元素,查询元素,然后释放hash表。接口其实还有很多其他的,具体可以到官网上或者源码中查看更加详细的接口及定义。
内存分配
提到内存分配,我认为最需要注意的就是内存泄漏。openssl提供了内存分配/释放的接口,如果用户完全调用openssl的接口进行内存的分配和释放,那么就可以更加方便的来查询内存泄漏的问题。openssl内部使用的是hash表来存放没有释放的内存,如果发生内存泄漏openssl也提供了函数来知道哪边发生了内存泄漏。
代码示例如下:
#include <string.h>
#include <openssl/crypto.h>
int main() {
char *p = OPENSSL_malloc(4); // 分配四个字节的空间
p = OPENSSL_remalloc(p, 40); // 重新分配40个字节的空间
p = OPENSSL_realloc(p, 32); // 重新分配32个字节的空间
int i = 0;
for (i = 0;i < 32;i ++) {
memset(&p[i], i, 1);
}
p = OPENSSL_realloc_clean(p, 32, 77); // 重新分配新的内存空间,并将老的数据进行拷贝,并且将老得空间清空并释放
p = OPENSSL_remalloc(p, 40);
OPENSSL_malloc_locked(3); // 与锁有关
OPENSSL_free(p); // 释放内存
return 0;
}
上述例子就是使用了基本的内存分配和释放的函数。
具体每个借口的含义可以根据注释得出。
抽象IO
抽象IO也是openssl的一大应用。其隐藏了IO内部的实现细节,提供出一系列简单易用的接口,主要针对日志、文件、socket、标准输入输出等。BIO中的数据可以从一个BIO传到另一个BIO或者是应用程序。
代码示例:
#include <stdio.h>
#include <openssl/bio.h>
int main() {
BIO *b = BIO_new(BIO_s_mem()); // new一个BIO类型的对象
int len = BIO_write(b, "OpenSSL", 4); // 将openssl写到b中
len = BIO_printf(b, "%s", "zcp"); // 将zcp写到b中
printf("len: %d\n", len);
len = BIO_ctrl_pending(b); // 读取当前b中写入的字符长度
printf("len: %d\n", len);
char *out = OPENSSL_malloc(len); // 分配len的内存空间
len = BIO_read(b, out, len); // 将数据读到out中
printf("len: %d\n", len);
OPENSSL_free(out);
BIO_free(b);
return 0;
}
以上例子就是简单的将数据读写到BIO对象中的例子,实际上这个抽象接口提供出来的使用方向有很多,我们下面再介绍一个比较常用的socket的使用。
代码示例:
// 服务端
#include <stdio.h>
#include <OpenSSL/bio.h>
#include <string.h>
int main()
{
BIO *b=NULL,*c=NULL;
int sock,ret,len;
char *addr=NULL;
char out[80];
sock=BIO_get_accept_socket("2323",0); // 绑定端口号
b=BIO_new_socket(sock, BIO_NOCLOSE); // 创建一个socket,并且不关闭
ret=BIO_accept(sock,&addr); // 等待客户端连接端口
BIO_set_fd(b,ret,BIO_NOCLOSE); // 设置连接不关闭
while(1)
{
memset(out,0,80);
len=BIO_read(b,out,80); // 读数据
if(out[0]=='q')
break;
printf("%s",out);
}
BIO_free(b); // 释放b的内存
return 0;
}
// 客户端
#include <OpenSSL/bio.h>
int main()
{
BIO *cbio, *out;
int len;
char tmpbuf[1024];
cbio = BIO_new_connect("localhost:http"); // 生成建立连接到本地web服务的BIO
out = BIO_new_fp(stdout, BIO_NOCLOSE); // 生成一个输出到屏幕的BIO
if(BIO_do_connect(cbio) <= 0) // 连接服务
{
fprintf(stderr, "Error connecting to server\n");
}
BIO_puts(cbio, "GET / HTTP/1.0\n\n"); // 通过BIO发送数据
for(;;)
{
len = BIO_read(cbio, tmpbuf, 1024); // 将web服务响应的数据写入缓存,
if(len <= 0) break;
BIO_write(out, tmpbuf, len); // 通过BIO打印收到的数据
}
BIO_free(cbio);
BIO_free(out);
return 0;
}
Base64编码解码
BASE64编码是一种很常见的编码形式。其原理是将数据三个字节一组组成一个24bit的二进制数,然后对这个二进制数分成4组,得到0-63的数字,对应编码表找出每个数字代表的字母。
下面是接口使用的示例:
#include <string.h>
#include <OpenSSL/evp.h>
int main()
{
EVP_ENCODE_CTX ectx,dctx;
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); // 进行base64编码,本函数可以调用多次
total+=outl;
EVP_EncodeFinal(&ectx,out+total,&outl); // 进行base64编码,输出编码结果
total+=outl;
printf("%s\n",out);
EVP_DecodeInit(&dctx); // 解码前也需要初始化环境变量
outl=500;
total2=0;
ret=EVP_DecodeUpdate(&dctx,d,&outl,out,total); // 进行base64解码,本函数可以调用多次
if(ret<0)
{
printf("EVP_DecodeUpdate err!\n");
return -1;
}
total2+=outl;
ret=EVP_DecodeFinal(&dctx,d,&outl); // 进行base64解码,输出编码结果
total2+=outl;
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。
常见的加密算法
RSA
RSA是一种常见的对称算法。包含一个公钥和一个私钥。一般都是公钥是公开可以访问的,私钥只有用户才能有。然后公开之后,其他用户使用加密之后发送来的消息就可以使用私钥解密。
接口示例如下:
#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®;
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;
}
RSA_free®;
return 0;
}
总结
openssl的重点就是以上这些,这些例子需要自己手写一遍加深印象。hash表的话需要了解其实现原理,BIO,内存分配这些接口比较多,用到的时候可以自己查询。对于加密算法这些我认为能狗大致了解他的加密方式以及函数的使用即可。