Openssl加密库

基础知识


对称算法

对称算法使用一个密钥。给定一个明文和一个密钥,加密产生密文,其长度和明文大致相同。解密时,使用读密钥与加密密钥相同。 对称算法主要有四种加密模式:

电子密码本模式 Electronic Code Book(ECB)

这种模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密。其缺点是:电子编码薄模式用一个密钥加密消息的所有块,如果原消息中重复明文块则加密消息中的相应密文块也会重复,因此,电子编码薄模式适于加密小消息。

加密块链模式 Cipher Block Chaining(CBC)

CBC模式的加密首先也是将明文分成固定长度的块,然后将前面一个加密块输出的密文与下一个要加密的明文块进行异或操作,将计算结果再用密钥进行加密得到密文。第一明文块加密的时候,因为前面没有加密的密文,所以需要一个初始化向量。跟ECB方式不一样,通过连接关系,使得密文跟明文不再是一一对应的关系,破解起来更困难,而且克服了只要简单调换密文块可能达到目的的攻击。

加密反馈模式 Cipher Feedback Mode(CFB)

面向字符的应用程序的加密要使用流加密法,可以使用加密反馈模式。在此模式下,数据用更小的单元加密,如可以是8位,这个长度小于定义的块长(通常是64位)。其加密步骤是:

  • 使用64位的初始化向量。初始化向量放在移位寄存器中,在第一步加密,产生相应的64位初始化密文;
  • 始化向量最左边的8位与明文前8位进行异或运算,产生密文第一部分(假设为c),然后将c传输到接收方;
  • 向量的位(即初始化向量所在的移位寄存器内容)左移8位,使移位寄存器最右边的8位为不可预测的数据,在其中填入c的内容;
  • 第1-3步,直到加密所有的明文单元。

解密过程相反

输出反馈模式 Output Feedback Mode(OFB)

输出反馈模式与 CFB 相似,惟一差别是, CFB 中密文填入加密过程下一阶段,而在 OFB中,初始化向量加密过程的输入填入加密过程下一阶段。

摘要算法

        摘要算法是一种能产生特殊输出格式的算法,这种算法的特点是:无论用户输入什么长度的原始数据,经过计算后输出的密文都是固定长度的,这种算法的原理是根据一定的运算规则对原数据进行某种形式的提取,这种提取就是摘要,被摘要的数据内容与原数据有密切联系,只要原数据稍有改变,输出的“摘要”便完全不同,因此,基于这种原理的算法便能对数据完整性提供较为健全的保障。但是,由于输出的密文是提取原数据经过处理的定长值,所以它已经不能还原为原数据,即消息摘要算法是不可逆的,理论上无法通过反向运算取得原数据内容,因此它通常只能被用来做数据完整性验证。如今常用的“消息摘要”算法经历了多年验证发展而保留下来的算法已经不多,这其中包括MD2、 MD4、 MD5、SHA、 SHA-1/256/383/512等。常用的摘要算法主要有MD5和SHA1。 D5的输出结果为16字节, sha1的输出结果为20字节。

公钥算法

        在公钥密码系统中,加密和解密使用的是不同的密钥,这两个密钥之间存在着相互依存关系:即用其中任一个密钥加密的信息只能用另一个密钥进行解密。这使得通信双方无需事先交换密钥就可进行保密通信。其中加密密钥和算法是对外公开的,人人都可以通过这个密钥加密文件然后发给收信者,这个加密密钥又称为公钥;而收信者收到加密文件后,它可以使用他的解密密钥解密,这个密钥是由他自己私人掌管的,并不需要分发,因此又成称为私钥,这就解决了密钥分发的问题。主要的公钥算法有: RSA、 DSA、 DH和ECC。

RSA 算法

        当前最著名、应用最广泛的公钥系统RSA是在1978年,由美国麻省理工学院(MIT)的Rivest、 Shamir和Adleman在题为《获得数字签名和公开钥密码系统的方法》的论文中提出的。它是一个基于数论的非对称(公开钥)密码体制,是一种分组密码体制。其名称来自于三个发明者的姓名首字母。 它的安全性是基于大整数素因子分解的困难性,而大整数因子分解问题是数学上的著名难题,至今没有有效的方法予以解决,因此可以确保RSA算法的安全性。 RSA系统是公钥系统的最具有典型意义的方法,大多数使用公钥密码进行加密和数字签名的产品和标准使用的都是RSA算法。 RSA算法是第一个既能用于数据加密也能用于数字签名的算法,因此它为公用网络上信息的加密和鉴别提供了一种基本的方法。它通常是先生成一对RSA 密钥,其中之一是保密密钥,由用户保存;另一个为公开密钥,可对外公开,甚至可在网络服务器中注册,人们用公钥加密文件发送给个人,个人就可以用私钥解密接受。为提高保密强度, RSA密钥至少为500位长,一般推荐使用1024位。RSA算法是R.Rivest、 A.Shamir和L.Adleman于1977年在美国麻省理工学院开发,于1978年首次公布。 RSA公钥密码算法是目前网络上进行保密通信和数字签名的最有效的安全算法之一。 RSA算法的安全性基于数论中大素数分解的困难性,所以, RSA需采用足够大的整数。因子分解越困难,密码就越难以破译,加密强度就越高。其算法如下:

  1. 选择两质数p、 q
  2. 计算n = p * q
  3. 计算n的欧拉函数Φ(n) = (p - 1)(q - 1)
  4. 选择整数e,使e与Φ(n)互质,且1 < e < Φ(n)
  5. 计算d,使d * e = 1 mod Φ(n)

其中,公钥KU={e, n},私钥KR={d, n}。
加密/解密过程:
利用RSA加密,首先需将明文数字化,取长度小于log2n位的数字作为明文块。
对于明文块M和密文块C,加/解密的形式如下:
加密: C = Me mod n
解密: M = Cd mod n = (Me)d mod n = Med mod n
RSA的安全性基于大数分解质因子的困难性。因为若n被分解为n = p * q,则Φ(n)、 e、 d可依次求得。目前,因式分解速度最快的方法的时间复杂性为exp(sqrt(ln(n)lnln(n)))。 统计数据表明,在重要应用中,使用512位的密钥已不安全,需要采用1024位的密钥。

DSA 算法

        DSA(Digital Signature Algorithm,数字签名算法,用作数字签名标准的一部分),它是另一种公开密钥算法,它不能用作加密,只用作数字签名。 DSA使用公开密钥,为接受者验证数据的完整性和数据发送者的身份。它也可用于由第三方去确定签名和所签数据的真实性。 DSA算法的安全性基于解离散对数的困难性,这类签字标准具有较大的兼容性和适用性,成为网络安全体系的基本构件之一。DSA签名算法中用到了以下参数:
p是L位长的素数,其中L从512到1024且是64的倍数。
q是160位长且与p-1互素的因子 ,其中h是小于p-1并且满足 大于1的任意数。
x是小于q的数。

另外,算法使用一个单向散列函数H(m)。标准指定了安全散列算法(SHA)。三个参数p, q和g是公开的,且可以被网络中所有的用户公有。私人密钥是x,公开密钥是y。
对消息m签名时:

  1. 发送者产生一个小于q的随机数k。
  2. 发送者产生:r和s就是发送者的签名,发送者将它们发送给接受者。
  3. 接受者通过计算来验证签名:如果v=r,则签名有效。

Diffie-Hellman 密钥交换

        DH算法是W.Diffie和M.Hellman提出的。此算法是最早的公钥算法。它实质是一个通信双方进行密钥协定的协议:两个实体中的任何一个使用自己的私钥和另一实体的公钥,得到一个对称密钥,这一对称密钥其它实体都计算不出来。 DH算法的安全性基于有限域上计算离散对数的困难性。离散对数的研究现状表明:所使用的DH密钥至少需要1024位,才能保证有足够的中、长期安全。

椭圆曲线密码体制(ECC)

        1985年, N. Koblitz和V. Miller分别独立提出了椭圆曲线密码体制(ECC),其依据就是定义在椭圆曲线点群上的离散对数问题的难解性。为了用椭圆曲线构造密码系统,首先需要找到一个单向陷门函数,椭圆曲线上的数量乘就是这样的单向陷门函数。椭圆曲线的数量乘是这样定义的:设E为域K上的椭圆曲线, G为E上的一点,这个点被一个正整数k相乘的乘法定义为 k个G相加,因而有

kG = G + G + … + G (共有k个G)

        若存在椭圆曲线上的另一点N ≠ G,满足方程kG = N。容易看出,给定k和G,计算N相对容易。而给定N和G,计算k = logG N相对困难。这就是椭圆曲线离散对数问题。离散对数求解是非常困难的。椭圆曲线离散对数问题比有限域上的离散对数问题更难求解。对于有理点数有大素数因子的椭圆离散对数问题,目前还没有有效的攻击方法。

OpenSSL 源代码


OpenSSL源代码主要由eay库、 ssl库、工具源码、范例源码以及测试源码组成。eay库是基础的库函数,提供了很多功能。源代码放在crypto目录下。包括如下内容:

  1. asn.1 DER编码解码(crypto/asn1目录),它包含了基本asn1对象的编解码以及数字证书请求、数字证书、 CRL撤销列表以及PKCS8等最基本的编解码函数。这些函数主要通过宏来实现。
  2. 抽象IO(BIO,crypto/bio目录),本目录下的函数对各种输入输出进行抽象,包括文件、内存、标准输入输出、 socket和SSL协议等。
  3. 大数运算(crypto/bn目录),本目录下的文件实现了各种大数运算。这些大数运算主要用于非对称算法中密钥生成以及各种加解密操作。另外还为用户提供了大量辅助函数,比如内存与大数之间的相互转换。
  4. 字符缓存操作(crypto/buffer目录)。
  5. 配置文件读取(crypto/conf目录), OpenSSL主要的配置文件为OpenSSL.cnf。本目录下的函数实现了对这种格式配置文件的读取操作。
  6. DSO(动态共享对象,crypto/dso目录),本目录下的文件主要抽象了各种平台的动态库加载函数,为用户提供统一接口。
  7. 硬件引擎(crypto/engine目录),硬件引擎接口。用户如果要写自己的硬件引擎,必须实现它所规定的接口。
  8. 错误处理(crypto/err目录),当程序出现错误时, OpenSSL能以堆栈的形式显示各个错误。本目录下只有基本的错误处理接口,具体的的错误信息由各个模块提供。各个模块专门用于错误处理的文件一般为*_err..c文件。
  9. 对称算法、非对称算法及摘要算法封装(crypto/evp目录)。
  10. HMAC(crypto/hmac目录),实现了基于对称算法的MAC。
  11. hash表(crypto/lhash目录),实现了散列表数据结构。 OpenSSL中很多数据结构都是以散列表来存放的。比如配置信息、 ssl session和asn.1对象信息等。
  12. 数字证书在线认证(crypto/ocsp目录),实现了ocsp协议的编解码以及证书有效性计算等功能。
  13. PEM文件格式处理(crypto/pem),用于生成和读取各种PEM格式文件,包括各种密钥、数字证书请求、数字证书、 PKCS7消息和PKCS8消息等。
  14. pkcs7消息语法(crypto/pkcs7目录),主要实现了构造和解析PKCS7消息;
  15. pkcs12个人证书格式(crypto/pckcs12目录),主要实现了pkcs12证书的构造和解析。
  16. 队列(crypto/pqueue目录),实现了队列数据结构,主要用于DTLS。
  17. 随机数(crypto/rand目录),实现了伪随机数生成,支持用户自定义随机数生成。
  18. 堆栈(crypto/stack目录),实现了堆栈数据结构。
  19. 线程支持(crypto/threads), OpenSSL支持多线程,但是用户必须实现相关接口。
  20. 文本数据库(crypto/txt_db目录)。
  21. x509数字证书(crypto/x509目录和crypto/x509v3),包括数字证书申请、数字证书和CRL的构造、解析和签名验证等功能了;
  22. 对称算法(crypto/aes、 crypto/bf、 crypto/cast、 ccrypto/omp和crypto/des等目录)。
  23. 非对称算法(crypto/dh、 crypto/dsa、 crypto/ec和crypto/ecdh)。
  24. 摘要算法(crypto/md2、 crypto/md4、 crypto/md5和crypto/sha)以及密钥交换/认证算法(crypto/dh 和crypto/krb5)。


        ssl库所有源代码在ssl目录下,包括了sslv2、 sslv3、 tlsv1和DTLS的源代码。各个版本基本上都有客户端源码(*_clnt.c)、服务源码(*_srvr.c)、通用源码(*_both.c)、底层包源码(*_pkt.c)、方法源码(*_meth.c)以及协议相关的各种密钥计算源码(*_enc.c)等。

       

        工具源码主要在crypto/apps目录下,默认编译时只编译成OpenSSL可执行文件。该命令包含了各种命令工具。此目录下的各个源码可以单独进行编译。

        

        范例源码在demo目录下,另外engines目录给出了OpenSSL支持的几种硬件的engines源码,也可以作为engine编写参考。测试源码主要在test目录下。

哈希表


OpenSSL函数使用哈希表来加快查询操作,并能存放任意形式的数据,比如配置文件的读取、内存分配中被分配内存的信息等。其源码在crypto/lhash目录下。OpenSSL中的哈希表数据结构在lhash_lcl.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分配空间的大小。基本的结构如下示图:

函数说明

LHASH *lh_new(LHASH_HASH_FN_TYPE h, LHASH_COMP_FN_TYPE c)
功能:生成哈希表
说明:输入参数h为哈希函数, c为比较函数。这两个函数都是回调函数。 因为哈希表用于存放任意的数据结构,哈希表存放、查询、删除等操作都需要比较数据和进行哈希运算,而哈希表不知道用户数据如何进行比较,也不知道用户数据结构中需要对哪些关键项进行散列运算。所以,用户必须提供这两个回调函数。

void *lh_delete(LHASH *lh, const void *data)
功能:删除散列表中的一个数据
说明: data为数据结构指针。

void lh_doall(LHASH *lh, LHASH_DOALL_FN_TYPE func)
功能:处理哈希表中的所有数据
说明: func为外部提供的回调函数,本函数遍历所有存储在哈希表中的数据,每个数据被func处理。

void lh_doall_arg(LHASH *lh, LHASH_DOALL_ARG_FN_TYPE func, void*arg)
功能:处理哈希表中所有数据
说明:此参数类似于lh_doall 函数, func为外部提供的回调函数, arg为传递给func函数的参数。本函数遍历所有存储在哈希表中的数据,每个数据被func处理。

void lh_free(LHASH *lh)
功能:释放哈希表。

void *lh_insert(LHASH *lh, void *data)
功能:往哈希表中添加数据。
说明: data为需要添加数据结构的指针地址。

void *lh_retrieve(LHASH *lh, const void *data)
功能:查询数据。
说明:从哈希表中查询数据, 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);

void lh_node_stats_bio(const LHASH *lh, BIO *out)
功能:将哈希表中每个链表下的数据状态输出到BIO中。

void lh_node_stats(const LHASH *lh, FILE *fp)
功能:将哈希表中每个链表下数据到个数输出到FILE中。
说明:此函数调用了lh_node_stats_bio函数

void lh_node_usage_stats_bio(const LHASH *lh, BIO *out)
功能:将哈希表的使用状态输出到BIO中。

void lh_node_usage_stats(const LHASH *lh, FILE *fp)
功能:将哈希表的使用状态输出到FILE中
说明:此函数调用了lh_node_usage_stats_bio函数

unsigned long lh_num_items(const LHASH *lh)
功能:获取哈希表中元素的个数。

void lh_stats_bio(const LHASH *lh, BIO *out)
功能:输出哈希表统计信息到BIO中

void lh_stats(const LHASH *lh, FILE *fp)
源文件: lh_stats.c
功能:打印哈希表的统计信息,此函数调用了lh_stats_bio。

unsigned long lh_strhash(const char *c)
功能:计算文本字符串到哈希值。

编程示例

#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 void *a, const void *b) {
    char *namea=((Student *)a)->name;
    char *nameb=((Student *)b)->name;
    return strcmp(namea,nameb);
}

static void PrintValue(Student *a) {
    printf("name :%s\n",a->name);
    printf("age :%d\n",a->age);
    printf("otherInfo : %s\n",a->otherInfo);
}

static void PrintValue_arg(Student *a,void *b){
    int flag=0;
    flag=*(int *)b;
    printf("用户输入参数为:%d\n",flag);
    printf("name :%s\n",a->name);
    printf("age :%d\n",a->age);
    printf("otherInfo : %s\n",a->otherInfo);
}

int main(){
    int flag=11;
    LHASH *h;
    Student s1={"zcp",28,"hu bei"},
    s2={"forxy",28,"no info"},
    s3={"skp",24,"student"},
    s4={"zhao_zcp",28,"zcp's name"},
    *s5;

    void *data;
    h=lh_new(NULL,Student_cmp);
    if(h==NULL) {
        printf("err.\n");
        return -1;
    }

    data=&s1;
    lh_insert(h,data);

    data=&s2;
    lh_insert(h,data);

    data=&s3;
    lh_insert(h,data);

    data=&s4;
    lh_insert(h,data);

    /* 打印*/
    lh_doall(h,PrintValue);
    lh_doall_arg(h,PrintValue_arg,(void *)(&flag));
    data=lh_retrieve(h,(const void*)"skp");
    if(data==NULL){
        printf("can not look up skp!\n");
        lh_free(h);
        return -1;
    }

    s5=data;
    printf("student name : %s\n",s5->name);
    printf("sutdent age : %d\n",s5->age);
    printf("student otherinfo : %s\n",s5->otherInfo);
    lh_free(h);
    getchar();
    return 0;
}

OpenSSL 抽象 IO


OpenSSL抽象IO(I/O abstraction,即BIO)是OpenSSL对于io类型的抽象封装,包括:内存、文件、日志、标准输入输出、 socket(TCP/UDP)、加/解密、摘要和ssl通道等。OpenSSL BIO通过回调函数为用户隐藏了底层实现细节,所有类型的bio的调用大体上是类似的。 Bio中的数据能从一个BIO传送到另外一个BIO或者是应用程序。

数据结构

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;
    struct bio_st *next_bio; /* used by filter BIOs */
    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:用于存放额外数据。

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等等。

编程示例

mem bio

#include <stdio.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_printf(b,"%s","zcp");
    len=BIO_ctrl_pending(b);
    out=(char *)OPENSSL_malloc(len);
    len=BIO_read(b,out,len);
    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缓冲区。

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;
    }

    BIO_free(b);
    free(out);

    return 0;
}

socket bio

服务端:

#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);
    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);
    return 0;
}

客户端telnet此端口成功后,输入字符,服务端会显示出来(linux下需要输入回车)。

客户端:

#include <OpenSSL/bio.h>

int main() {
    BIO *cbio, *out;
    int len;
    char tmpbuf[1024];
    cbio = BIO_new_connect("localhost:http");
    out = BIO_new_fp(stdout, BIO_NOCLOSE);
    if(BIO_do_connect(cbio) <= 0) {
        fprintf(stderr, "Error connecting to server\n");
    }

    BIO_puts(cbio, "GET / HTTP/1.0\n\n");
    for(;;) {
        len = BIO_read(cbio, tmpbuf, 1024);
        if(len <= 0) break;
        BIO_write(out, tmpbuf, len);
    }

    BIO_free(cbio);
    BIO_free(out);
    return 0;
}

说明:本示例用来获取本机的web服务信息。
cbio = BIO_new_connect("localhost:http");用来生成建立连接到本地web服务的BIO。
out = BIO_new_fp(stdout, BIO_NOCLOSE);生成一个输出到屏幕的BIO。
BIO_puts(cbio, "GET / HTTP/1.0\n\n");通过BIO发送数据。
len = BIO_read(cbio, tmpbuf, 1024);将web服务响应的数据写入缓存,此函数循环调用直到无数据。
BIO_write(out, tmpbuf, len);通过BIO打印收到的数据。

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;
    char tmp[1024];
    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);
    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缓冲区。

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];
    unsigned char key[8],iv[8];
    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);

    /* 解密 */
    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);
    return 0;
}

说明:本示例采用cipher BIO对字符串"OpenSSL"进行加密和解密,本示例编译需要用c++编译器;关键说明:
BIO_set_cipher(bc,c,key,iv,1);设置加密BI。
BIO_set_cipher(bdec,cd,key,iv,0);设置解密BIO。
其中key为对称密钥,iv为初始化向量。
加/解密结果通过BIO_read获取。

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());
    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");
    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加密套件并且不能找到各种算法。

主要掌握 RSA、BIO即可。

更多内容:https://download.csdn.net/download/u012173846/79364566

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值