http://www.ibm.com/developerworks/cn/linux/l-openssl.html
http://www.ibm.com/developerworks/cn/linux/l-openssl3.html 共3个部分
OpenSSL API 的文档有些含糊不清。因为还没有多少关于 OpenSSL 使用的教程,所以对初学者来说,在应用程序中使用它可能会有一些困难。那么怎样才能使用 OpenSSL 实现一个基本的安全连接呢?本教程将帮助您解决这个问题。
学习如何实现 OpenSSL 的困难部分在于其文档的不完全。不完全的 API 文档通常会妨碍开发人员使用该 API,而这通常意味着它注定要失败。但 OpenSSL 仍然很活跃,而且正逐渐变得强大。这是为什么?
OpenSSL 是用于安全通信的最著名的开放库。在 google 中搜索“SSL library”得到的返回结果中,列表最上方就是 OpenSSL。它诞生于 1998 年,源自 Eric Young 和 Tim Hudson 开发的 SSLeay 库。其他 SSL 工具包包括遵循 GNU General Public License 发行的 GNU TLS,以及 Mozilla Network Security Services(NSS)
那么,是什么使得 OpenSSL 比 GNU TLS、Mozilla NSS 或其他所有的库都优越呢?许可是一方面因素(请参阅 参考资料)。此外,GNS TLS(迄今为止)只支持 TLS v1.0 和 SSL v3.0 协议,仅此而已。
Mozilla NSS 的发行既遵循 Mozilla Public License 又遵循 GNU GPL,它允许开发人员进行选择。不过,Mozilla NSS 比 OpenSSL 大,并且需要其他外部库来对库进行编译,而 OpenSSL 是完全自包含的。与 OpenSSL 相同,大部分 NSS API 也没有文档资料。Mozilla NSS 获得了 PKCS #11 支持,该支持可以用于诸如智能卡这样的加密标志。OpenSSL 就不具备这一支持。
先决条件
要充分理解并利用本文,您应该:
- 精通 C 编程。
- 熟悉 Internet 通信和支持 Internet 的应用程序的编写。
拥有密码学方面的知识固然好,但这并不是必需的。
什么是 SSL?
SSL 是一个缩写,代表的是 Secure Sockets Layer。它是支持在 Internet 上进行安全通信的标准,并且将数据密码术集成到了协议之中。数据在离开您的计算机之前就已经被加密,然后只有到达它预定的目标后才被解密。证书和密码学算法支持了这一切的运转,使用 OpenSSL,您将有机会切身体会它们。
理论上,如果加密的数据在到达目标之前被截取或窃听,那些数据是不可能被破解的。不过,由于计算机的变化一年比一年快,而且密码翻译方法有了新的发展,因此,SSL 中使用的加密协议被破解的可能性也在增大。
可以将 SSL 和安全连接用于 Internet 上任何类型的协议,不管是 HTTP、POP3,还是 FTP。还可以用 SSL 来保护 Telnet 会话。虽然可以用 SSL 保护任何连接,但是不必对每一类连接都使用 SSL。如果连接传输敏感信息,则应使用 SSL。
什么是 OpenSSL?
OpenSSL 不仅仅是 SSL。它可以实现消息摘要、文件的加密和解密、数字证书、数字签名和随机数字。关于 OpenSSL 库的内容非常多,远不是一篇文章可以容纳的。
OpenSSL 不只是 API,它还是一个命令行工具。命令行工具可以完成与 API 同样的工作,而且更进一步,可以测试 SSL 服务器和客户机。它还让开发人员对 OpenSSL 的能力有一个认识。
您需要什么
首先需要的是最新版本的 OpenSSL。查阅参考资料部分,以确定从哪里可以获得最新的可以自己编译的源代码,或者最新版本的二进制文件(如果您不希望花费时间来编译的话)。不过,为了安全起见,我建议您下载最新的源代码并自己编译它。二进制版本通常是由第三方而不是由 OpenSSL 的开发人员来编译和发行的。
一些 Linux 的发行版本附带了 OpenSSL 的二进制版本,对于学习如何使用 OpenSSL 库来说,这足够了;不过,如果您打算去做一些实际的事情,那么一定要得到最新的版本,并保持该版本一直是最新的。
对于以 RPM 形式安装的 Linux 发行版本(Red Hat、Mandrake 等),建议您通过从发行版本制造商那里获得 RPM 程序包来更新您的 OpenSSL 发行版本。出于安全方面的原因,建议您使用最新版本的发行版本。如果您的发行版本不能使用最新版本的 OpenSSL,那么建议您只覆盖库文件,不要覆盖可执行文件。OpenSSL 附带的 FAQ 文档中包含了有关这方面的细节。
还要注意的是,OpenSSL 并没有在所有的平台上都获得官方支持。虽然制造商已经尽力使其能够跨平台兼容,但仍然存在 OpenSSL 不能用于您的计算机 和/或 操作系统的可能。请参阅 OpenSSL 的 Web 站点,以获得关于哪些平台可以得到支持的信息。
如果想使用 OpenSSL 来生成证书请求和数字证书,那么必须创建一个配置文件。在 OpenSSL 程序包的 apps
文件夹中,有一个名为 openssl.cnf
的可用模板文件。我不会对该文件进行讨论,因为这不在本文要求范围之内。不过,该模板文件有一些非常好的注释,而且如果在 Internet 上搜索,您可以找到很多讨论修改该文件的教程。
头文件和初始化
本教程所使用的头文件只有三个:ssl.h、bio.h 和 err.h。它们都位于 openssl 子目录中,而且都是开发您的项目所必需的。要初始化 OpenSSL 库,只需要三个代码行即可。清单 1 中列出了所有内容。其他的头文件 和/或 初始化函数可能是其他一些功能所必需的。
清单 1. 必需的头文件
/* OpenSSL headers */ #include "openssl/bio.h" #include "openssl/ssl.h" #include "openssl/err.h" /* Initializing OpenSSL */ SSL_load_error_strings(); ERR_load_BIO_strings(); OpenSSL_add_all_algorithms();
|
建立非安全连接
不管连接是安全的还是不安全的,OpenSSL 都使用了一个名为 BIO 的抽象库来处理包括文件和套接字在内的各种类型的通信。您还可以将 OpenSSL 设置成为一个过滤器,比如用于 UU 或 Base64 编码的过滤器。
在这里对 BIO 库进行全面说明有点麻烦,所以我将根据需要一点一点地介绍它。首先,我将向您展示如何建立一个标准的套接字连接。相对于使用 BSD 套接字库,该操作需要的代码行更少一些。
在建立连接(无论安全与否)之前,要创建一个指向 BIO 对象的指针。这类似于在标准 C 中为文件流创建 FILE 指针。
清单 2. 指针
打开连接
创建新的连接需要调用 BIO_new_connect
。您可以在同一个调用中同时指定主机名和端口号。也可以将其拆分为两个单独的调用:一个是创建连接并设置主机名的 BIO_new_connect
调用,另一个是设置端口号的 BIO_set_conn_port
(或者 BIO_set_conn_int_port
)调用。
不管怎样,一旦 BIO 的主机名和端口号都已指定,该指针会尝试打开连接。没有什么可以影响它。如果创建 BIO 对象时遇到问题,指针将会是 NULL。为了确保连接成功,必须执行BIO_do_connect
调用。
清单 3. 创建并打开连接
bio = BIO_new_connect("hostname:port"); if(bio == NULL) { /* Handle the failure */ } if(BIO_do_connect(bio) <= 0) { /* Handle failed connection */ }
|
在这里,第一行代码使用指定的主机名和端口创建了一个新的 BIO 对象,并以所示风格对该对象进行 格式化。例如,如果您要连接到 www.ibm.com 的 80 端口,那么该字符串将是www.ibm.com:80
。调用 BIO_do_connect
检查连接是否成功。如果出错,则返回 0 或 -1。
+++++++++++++++++++++++++++++++++
http://www.linuxidc.com/Linux/2011-04/34523.htm
1.安装openssl库:
sudo sudo apt-get install libssl-dev
2.服务器端实现代码
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <unistd.h> #include <arpa/inet.h> #include <openssl/ssl.h> #include <openssl/err.h>
#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 = 7838;
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[3], SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stdout); exit(1); } /* 载入用户私钥 */ if (SSL_CTX_use_PrivateKey_file(ctx, argv[4], 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); 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; }
|
2. 客户端实现代码
dfa
#include <stdio.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <resolv.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <openssl/ssl.h> #include <openssl/err.h>
#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-server.c 代码 */ SSL_library_init(); OpenSSL_add_all_algorithms(); SSL_load_error_strings(); 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])); 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); 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; }
|
3. 编译方式:
gcc -o ssl_server ssl_server.c -Wall -g -lssl
gcc -o ssl_client ssl_client.c -Wall -g -lssl
4. 生产私钥和证书
openssl genrsa -out privkey.pem 1024
openssl req -new -x509 -key privkey.pem -out CAcert.pem -days 1095
5. 程序运行方式:
./ssl_server 7838 1 CAcert.pem privkey.pem
./ssl_client 127.0.0.1 7838
有个错误,就是在第5部分-程序运行方式中,服务端的运行原来写的是:
./ssl_server 7838 1 CAcert.pem privkey.pem
其实应该是:
./ssl_server 7838 1 privkey.pem privkey.pem
因为这两个一个是服务端的证书,一个是服务端的密钥,与CA无关,又因为pem文件包含了证书和密钥,所以两个地方都可以用服务端的pem文件。