openssl之ssl编程

openssl:

        openssl是一个实现ssl协议的开源产品,采用c语言开发,具备跨系统的性能。调用openssl函数就可以实现ssl加密的安全数据传输通道,从而保护客户端和服务端之间的数据安全。

        加密方式:

                不需要秘钥:

                        Hash散列算法,加密不需要秘钥,解码只能撞库。

                需要秘钥:

                        对称加密算法,如,DES、AES、3DES。加密和解密是同一个秘钥。

                        非对称加密算法,如,RSA、DSA。加密和解密分别使用各自的密钥。

        理解RSA算法

                公钥:对外公开的秘钥,所有人都可以下载使用。

                私钥:只能宿主自己拥有,不对外开放。

        加密通信流程:

                若Bob和Alice之间通信,流程如下;

                1)Bob通过Alice公钥加密发送信息给Alice

                2)Alice用私钥解密得到信息

                3)Alice通过Bob公钥加密回信给Bob

                4)Bob用私钥解密得到回信

        数字签名

                若Bob给Alice写信,但是内容全是明文。Alice收到信后怎么确认是Bob发的,而不是其他人冒充的。

                1)Bob把信的内容hash下,得到一个信息摘要,再用私钥对信息摘要加密生成个数字签名,添加到信的后面发送给Alice。

                2)Alice收到数字签名后,用Bob公钥解密,然后在对信的内容进行hash,最后比对两者是否一致。若一致,则是Bob发的。

        数字证书:

                若有人冒充了Bob,把一个假的公钥发送给Alice,在Alice不知情的状况下,会觉得是和Bob通信。解决这个问题,就需要数字证书,及权威机构(CA)认证的公钥,是真实有效的,确认了身份的。

openssl编程步骤:

openssl初始化

        使用openssl之前,必须初始化,加载对应ssl协议、加密算法和错误信息,使用函数:

        int SSL_library_int(void);

        OpenSSL_add_all_algorithms();

        SSL_load_error_strings();  

创建SSL会话环境

        在openssl中创建的ssl会话环境称为CTX,使用不同ssl协议,其会话环境也不同。

        //客户端和服务端都需要调用

   SSL_CTX *SSL_CTX_new(int method);   //申请SSL会话环境,客户端method可用SSLv23_client_method();服务端method可用SSLv23_server_method()

        

        //若有验证对方证书的需求,则需调用

        SSL_CTX_set_verify();   //指定证书验证方式

        SSL_CTX_load_verify_location(); //为SSL会话环境加载本应该所信任的CA证书列表

        //若有加载证书的需求,则需调用

        int SSL_CTX_use_certificate_file(); //为SSL会话加载本应用的证书

        int SSL_CTX_use_certificate_chain_file(); //为SSL会话加载本应用的证书所属的证书链

        int SSL_CTX_use_Privatekey_file(); //为SSL会话加载本应用的私钥

        int SSL_CTX_check_private_key(); //验证所加载的私钥和证书是否相匹配

创建SSL套接字

        创建SSL套接字之前,先需要创建socket套接字,建立tcp连接。SSL套接字操作函数有

        SSL *SSL_new(SSL_CTX *ctx); //创建一个ssl套接字

        int SSL_set_fd(SSL *ssl, int fd); //以读写模式绑定流套接字

        int SSL_set_rfd(SSL *ssl, int fd); //以只读模式绑定流套接字

        int SSL_set_wfd(SSL *ssl, int fd); //以只写模式绑定流套接字

完成SSL握手

     在这一步,我们需要在TCP连接基础上,完成SSL连接。客户端发起SSL连接,使用SSL_connect()发起SSL握手;服务端接收SSL连接,使用SSL_accept()完成SSL握手。

        int SSL_connect(SSL *ssl);

        int SSL_accept(SSL *ssl);

        握手完成后,客户端会想服务端发送证书信息,以便服务端进行鉴别。其中会用到两个函数:

        X509 *SSL_get_peer_certificate(SSL *ssl); //从套接字中获取对方的证书信息。

        X509_NAME *X509_get_subject_name(X509 *a); //得到证书所有者

数据传输

        int SSL_read(SSL *ssl, void *buf, int num);   //从ssl套接字读取数据

        int SSL_write(SSL *ssl, const void *buf, int num); //向ssl套接字写数据

结束会话

        当客户端和服务端结束通信后,通过以下函数来释放前面过程中申请的SSL资源。

        int SSL_shutdown(SSL *ssl); //关闭SSL套接字

        void SSL_free(SSL *ssl);    //释放SSL套接字

        void SSL_CTX_free(SSL_CTX *ctx); //释放SSL会话环境

ssl-server.c:
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  

#define MAXBUF 1024 

int main(int argc, char * *argv) 
{
    int sockfd, new_fd;
    socklen_t len;
    struct sockaddr_in my_addr, their_addr;
    unsigned int myport, lisnum;
    char buf[MAXBUF + 1];
    SSL_CTX * ctx;
    //指定监听端口
    if (argv[1]) 
    {
        myport = atoi(argv[1]);
    }
    else 
    {
        myport = 8888;
    }
    //最大客户端连接数
    if (argv[2]) 
    {
        lisnum = atoi(argv[2]);
    }
    else 
    {
        lisnum = 2;
    }
    /* SSL 库初始化*/
    SSL_library_init();
    /* 载入所有SSL 算法*/
    OpenSSL_add_all_algorithms();
    /* 载入所有SSL 错误消息*/
    SSL_load_error_strings();
    /* 以SSL V2 和V3 标准兼容方式产生一个SSL_CTX ,即SSL Content Text */
    ctx = SSL_CTX_new(SSLv23_server_method());
    /* 
    也可以用SSLv2_server_method() 或SSLv3_server_method() 单独表示V2 或V3标准
    */
    if (ctx == NULL) 
    {
        ERR_print_errors_fp(stdout);
        exit(1);
    }
    /* 载入用户的数字证书, 此证书用来发送给客户端。证书里包含有公钥*/
    if (SSL_CTX_use_certificate_file(ctx, argv[4], SSL_FILETYPE_PEM) <= 0) 
    {
        ERR_print_errors_fp(stdout);
        exit(1);
    }
    /* 载入用户私钥*/
    if (SSL_CTX_use_PrivateKey_file(ctx, argv[5], SSL_FILETYPE_PEM) <= 0) 
    {
        ERR_print_errors_fp(stdout);
        exit(1);
    }
    /* 检查用户私钥是否正确*/
    if (!SSL_CTX_check_private_key(ctx)) 
    {
        ERR_print_errors_fp(stdout);
        exit(1);
    }

    /* 开启一个socket 监听*/
    if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) 
    {
        perror("socket");
        exit(1);
    } 
    else 
    {
        printf("socket created\n");
    }
    bzero( &my_addr, sizeof(my_addr));
    my_addr.sin_family = PF_INET;
    my_addr.sin_port = htons(myport);
    
    //设置监听的IP
    if (argv[3]) 
    {
        my_addr.sin_addr.s_addr = inet_addr(argv[3]);
    }
    else 
    {
        //如果用户没有指定监听端口,则默认监听0.0.0.0(任意IP)
        my_addr.sin_addr.s_addr = INADDR_ANY;
    }
    if (bind(sockfd, (struct sockaddr * ) &my_addr, sizeof(struct sockaddr)) == -1) 
    {
        perror("bind");
        exit(1);
    } 
    else
    {
        printf("binded\n");
    } 
    if (listen(sockfd, lisnum) == -1) 
    {
        perror("listen");
        exit(1);
    } 
    else 
    {
        printf("begin listen\n");
    }
    while (1) 
    {
        SSL * ssl;
        len = sizeof(struct sockaddr);
        /* 等待客户端连上来*/
        if ((new_fd = accept(sockfd, (struct sockaddr * ) & their_addr, &len)) == -1) 
        {
            perror("accept");
            exit(errno);
        } 
        else 
        {
            printf("server: got connection from %s, port %d, socket %d\n", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);
        }
        /* 基于ctx 产生一个新的SSL */
        ssl = SSL_new(ctx);
        /* 将连接用户的socket 加入到SSL */
        SSL_set_fd(ssl, new_fd);
        /* 建立SSL 连接*/
        if (SSL_accept(ssl) == -1) 
        {
            perror("accept");
            close(new_fd);
            break;
        }
        /* 开始处理每个新连接上的数据收发*/
        bzero(buf, MAXBUF + 1);
        strcpy(buf, "server->client");
        /* 发消息给客户端*/
        len = SSL_write(ssl, buf, strlen(buf));
        if (len <= 0) 
        {
            printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buf, errno, strerror(errno));
            goto finish;
        } 
        else 
        {
            printf("消息'%s'发送成功,共发送了%d 个字节!\n", buf, len);
        }
        bzero(buf, MAXBUF + 1);
        /* 接收客户端的消息*/
        len = SSL_read(ssl, buf, MAXBUF);
        if (len > 0) 
        {
            printf("接收消息成功:'%s',共%d 个字节的数据\n", buf, len);
        }
        else 
        {
            printf("消息接收失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno));
        }
        /* 处理每个新连接上的数据收发结束*/
        finish:
        /* 关闭SSL 连接*/
        SSL_shutdown(ssl);
        /* 释放SSL */
        SSL_free(ssl);
        /* 关闭socket */
        close(new_fd);
    }
    /* 关闭监听的socket */
    close(sockfd);
    /* 释放CTX */
    SSL_CTX_free(ctx);
    return 0;
}


ssl-client.c
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  

#define MAXBUF 1024 

void ShowCerts(SSL * ssl) 
{
    X509 * cert;
    char * line;
    cert = SSL_get_peer_certificate(ssl);
    if (cert != NULL) 
    {
        printf("数字证书信息:\n");
        line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
        printf("证书: %s\n", line);
        free(line);
        line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
        printf("颁发者: %s\n", line);
        free(line);
        X509_free(cert);
    } 
    else 
    {
        printf("无证书信息!\n");
    }
} 

int main(int argc, char * *argv) 
{
    int sockfd, len;
    struct sockaddr_in dest;
    char buffer[MAXBUF + 1];
    SSL_CTX * ctx;
    SSL * ssl;
    if (argc != 3) 
    {
        printf("参数格式错误!正确用法如下:\n\t\t%s IP 地址端口\n\t 比如:\t%s 127.0.0.1 80\n 此程序用来从某个IP 地址的服务器某个端口接收最多MAXBUF 个字节的消息",
 argv[0], argv[0]);
        exit(0);
    }
    /* SSL 库初始化*/
    SSL_library_init();
    /* 载入所有SSL 算法*/
    OpenSSL_add_all_algorithms();
    /* 载入所有SSL 错误消息*/
    SSL_load_error_strings();
    /* 以SSL V2 和V3 标准兼容方式产生一个SSL_CTX ,即SSL Content Text */
    ctx = SSL_CTX_new(SSLv23_client_method());
    if (ctx == NULL) 
    {
        ERR_print_errors_fp(stdout);
        exit(1);
    }
    /* 创建一个socket 用于tcp 通信*/
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    {
        perror("Socket");
        exit(errno);
    }
    printf("socket created\n");
    /* 初始化服务器端(对方)的地址和端口信息*/
    bzero( &dest, sizeof(dest));
    dest.sin_family = AF_INET;
    //设置连接的端口
    dest.sin_port = htons(atoi(argv[2]));
    //设置连接的IP地址
    if (inet_aton(argv[1], (struct in_addr * ) &dest.sin_addr.s_addr) == 0) 
    {
        perror(argv[1]);
        exit(errno);
    }
    printf("address created\n");
    /* 连接服务器*/
    if (connect(sockfd, (struct sockaddr * ) &dest, sizeof(dest)) != 0) 
    {
        perror("Connect ");
        exit(errno);
    }
    printf("server connected\n");
    /* 基于ctx 产生一个新的SSL */
    ssl = SSL_new(ctx);
    /* 将新连接的socket 加入到SSL */
    SSL_set_fd(ssl, sockfd);
    /* 建立SSL 连接*/
    if (SSL_connect(ssl) == -1) 
    {
        ERR_print_errors_fp(stderr);
    }
    else 
    {
        printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
        ShowCerts(ssl);
    }
    /* 接收对方发过来的消息,最多接收MAXBUF 个字节*/
    bzero(buffer, MAXBUF + 1);
    /* 接收服务器来的消息*/
    len = SSL_read(ssl, buffer, MAXBUF);
    if (len > 0) 
    {
        printf("接收消息成功:'%s',共%d 个字节的数据\n", buffer, len);
    }
    else 
    {
        printf("消息接收失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno));
        goto finish;
    }
    bzero(buffer, MAXBUF + 1);
    strcpy(buffer, "from client->server");
    /* 发消息给服务器*/
    len = SSL_write(ssl, buffer, strlen(buffer));
    if (len < 0) 
    {
        printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buffer, errno, strerror(errno));
    }
    else 
    {
        printf("消息'%s'发送成功,共发送了%d 个字节!\n", buffer, len);
    }
    finish:
    /* 关闭连接*/
    SSL_shutdown(ssl);
    SSL_free(ssl);
    close(sockfd);
    SSL_CTX_free(ctx);
    return 0;
}


usage:
1. 程序中用到的包含公钥的服务端证书cacert.pem和服务端私钥文件privkey.pem需要使用如下方式生成:
openssl genrsa -out privkey.pem 2048
openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095
2. 编译程序用下列命令:
gcc -Wall ssl-client.c -o client -lssl
gcc -Wall ssl-server.c -o server -lssl
3. 运行程序用如下命令:
./server 8888 3 127.0.0.1 cacert.pem privkey.pem
./client 127.0.0.1 8888

        需要编译时,需添加-lssl -lcrypto -ldl -lpthread,注意ssl和crypto需放在前面

你可能感兴趣的:(网络编程,c语言)