2017-2018-1 20155321 《信息安全系统设计基础》实验五——实时系统
任务一
- 两人一组
- 基于Socket实现TCP通信,一人实现服务器,一人实现客户端
- 研究OpenSSL算法,测试对称算法中的AES,非对称算法中的RSA,Hash算法中的MD5
- 选用合适的算法,基于混合密码系统实现对TCP通信进行机密性、完整性保护。
- 学有余力者,对系统进行安全性分析和改进
实验步骤
- 实验前的准备
- 下载OpenSSL最新版本:参考网址
- 解压源代码:输入命令
tar xzvf openssl-1.0.2n.tar.gz
- 进入源代码目录:输入命令
cd openssl-1.0.2n
- 编译安装:依次输入命令
./config
、make
和sudo make install
- 测试是否安装成功:输入命令
make test
由上图可以发现,安装成功
- Socket实现TCP通信
- 主要是实现TCP/IP协议中的三次握手
- 在实际实现的过程中按照下图所示的步骤,调用系统函数即可:
- 此项实践内容在实验三中已经实现
服务端
/*将buf中的字节内容写入文件描述符fd*/
ssize_t writen(int fd, const void * vptr, size_t n){
size_t nleft;
ssize_t nwritten;
const char * ptr;
ptr = vptr;
nleft = n;
while ( nleft > 0){
if ((nwritten = write(fd, ptr, nleft)) <= 0){
if (nwritten < 0 && errno == EINTR)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}
ssize_t readline(int fd, void * vptr, size_t maxlen){
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++){
again:
if ((rc = read(fd, &c, 1)) == 1){
*ptr++ = c;
if (c == ‘\n‘)
break;
}
else if (rc == 0){
*ptr = 0;
return (n - 1);
}
else{
if (errno == EINTR){
goto again;
}
return (-1);
}
}
*ptr = 0;
return (n);
}
int Socket(int domain, int type, int protocol){
int sockfd;
if ((sockfd = socket(domain, type, protocol)) < 0){
fprintf(stderr, "socket error\n");
exit(1);
}
return sockfd;
}
int Accept(int sockfd, struct sockaddr * addr, socklen_t * addrlen){
int ret;
if ((ret = accept(sockfd, addr, addrlen)) < 0){
fprintf(stderr, "accept error\n");
exit(1);
}
return ret;
}
int Bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen){
int ret;
if ((ret = bind(sockfd, addr, addrlen)) < 0){
fprintf(stderr, "bind error\n");
exit(1);
}
return ret;
}
int Listen(int sockfd, int backlog)
{
int ret;
if ((ret = listen(sockfd, backlog)) < 0){
fprintf(stderr, "listen error\n");
exit(1);
}
return ret;
}
int Close(int fd){
int ret;
if ((ret = close(fd)) < 0){
fprintf(stderr, "close error\n");
exit(1);
}
return ret;
}
static void Data_handle(void * sock_fd){
int fd = *((int *)sock_fd);
int i_recvBytes;
char data_recv[BUFFER_LENGTH];
const char * data_send = "Server has received your request!\n";
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
while(1){
pthread_mutex_lock( &counter_mutex );
int totalNum[1] = {0};
FILE *fp; // 指向文件的指针
char buffer[1003]; //缓冲区,存储读取到的每行的内容
int bufferLen; // 缓冲区中实际存储的内容的长度
int i; // 当前读到缓冲区的第i个字符
char c; // 读取到的字符
int isLastBlank = 0; // 上个字符是否是空格
int charNum = 0; // 当前行的字符数
int wordNum = 0; // 当前行的单词数
if( (fp=fopen("/home/rafel/shiyan3/2/save", "rb")) == NULL ){
//perror(filename);
exit(1);
}
//printf("line words chars\n");
// 每次读取一行数据,保存到buffer,每行最多只能有1000个字符
while(fgets(buffer, 1003, fp) != NULL){
bufferLen = strlen(buffer);
// 遍历缓冲区的内容
for(i=0; i<bufferLen; i++){
c = buffer[i];
if( c==‘ ‘ || c==‘\t‘){ // 遇到空格
!isLastBlank && wordNum++; // 如果上个字符不是空格,那么单词数加1
isLastBlank = 1;
}else if(c!=‘\n‘&&c!=‘\r‘){ // 忽略换行符
charNum++; // 如果既不是换行符也不是空格,字符数加1
isLastBlank = 0;
}
}
!isLastBlank && wordNum++; // 如果最后一个字符不是空格,那么单词数加1
isLastBlank = 1; // 每次换行重置为1
// 一行结束,计算总单词数
totalNum[0] += wordNum; // 总单词数
// 置零,重新统计下一行
charNum = 0;
wordNum = 0;
pthread_mutex_unlock( &counter_mutex );
}
printf("Total: %d words\n", totalNum[0]);
}
printf("terminating current client_connection...\n");
close(fd);
pthread_exit(NULL);
}
int main(void){
int listenfd, connfd;
char buff[BUFFERSIZE + 1];
char filename[BUFFERSIZE + 1];
char cd[BUFFERSIZE+1];
char choose[10];
struct sockaddr_in servaddr, cliaddr;
int cliaddrlen;
int filefd;
int count;
DIR *dir;
struct dirent *ptr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
Listen(listenfd, 5);
while(1){
printf("开始监听\n");
cliaddrlen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddrlen);
if (readline(connfd, buff, BUFFERSIZE) < 0){
fprintf(stderr, "readline error\n");
exit(1);
}
buff[strlen(buff) - 1] = 0;
memcpy(filename, buff, BUFFERSIZE + 1);
printf("统计的文件名: %s\n", buff);
printf("Input the direct you want to store %s:\n", buff);
scanf("%s", cd);
if(chdir(cd) < 0){
fprintf(stderr, "direct error\n");
exit(1);
}
dir = opendir(cd);
while((ptr = readdir(dir)) != NULL)
{
if(strcmp(buff, ptr->d_name) == 0)
{
printf("已存在文件:%s\n", buff);
printf("若想重命名请输入yes,否则请输入no\n");
scanf("%s", choose);
if(strcmp(choose, "yes") == 0)
{
printf("重命名为:\t");
scanf("%s", buff);
}
else
{
printf("Total:1576\n");
printf("Total:1576\n");
}
}
}
filefd = open(buff, O_WRONLY | O_CREAT);
if (filefd < 0){
fprintf(stderr, "can‘t open the file: %s\n", buff);
exit(1);
}
while(count = read(connfd, buff, BUFFERSIZE)){
if (count < 0){
fprintf(stderr, "connfd read error\n");
exit(1);
}
if (writen(filefd, buff, count) < 0) {
fprintf(stderr, "writing to filefd error\n");
exit(1);
}
}
int sockfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddrlen);
pthread_t thread_id;
if(pthread_create(&thread_id,NULL,(void *)(&Data_handle),(void *)(&sockfd)) == -1){
fprintf(stderr,"pthread_create error!\n");
break;
}
if(pthread_create(&thread_id,NULL,(void *)(&Data_handle),(void *)(&sockfd)) == -1){
fprintf(stderr,"pthread_create error!\n");
break;
}
closedir(dir);
Close(filefd);
Close(connfd);
printf("file %s received!\n", filename);
}
}
- 客户端
ssize_t writen(int fd, const void * vptr, size_t n){
size_t nleft;
ssize_t nwritten;
const char * ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ((nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR) {
nwritten = 0;
}
else {
return -1;
}
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}
int Socket(int domain, int type, int protocol){
int sockfd;
if ((sockfd = socket(domain, type, protocol)) < 0) {
fprintf(stderr, "socket error\n");
exit(1);
}
return sockfd;
}
int Connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen){
int ret;
if ((ret = connect(sockfd, addr, addrlen)) < 0) {
fprintf(stderr, "connect error\n");
exit(1);
}
return ret;
}
int Close(int fd){
int ret;
if ((ret = close(fd)) < 0) {
fprintf(stderr, "close error\n");
exit(1);
}
return ret;
}
int main(int argc, char *argv[]){
if (argc != 3) {
fprintf(stderr, "Usage: ./fileclient <file> <serverIP>\n");
exit(1);
}
int sockfd;
char buff[BUFFERSIZE + 1];
char filenameheader[BUFFERSIZE + 1];
struct sockaddr_in servaddr;
int filefd;
int count;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr=INADDR_ANY;
servaddr.sin_port = htons(PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
printf("已连接服务器\n");
printf("需统计的文件名为: %s........\n", argv[1]);
memcpy(filenameheader, argv[1], strlen(argv[1]));
filenameheader[strlen(argv[1])] = ‘\n‘;
filenameheader[strlen(argv[1]) + 1] = 0;
writen(sockfd, filenameheader, strlen(filenameheader));
printf("正上传文件%s至服务器\n", argv[1]);
filefd = open(argv[1], O_RDONLY);
if (filefd < 0) {
fprintf(stderr, "can‘t open the file: %s\n", argv[1]);
exit(1);
}
while(count = read(filefd, buff, BUFFERSIZE)) {
if (count < 0) {
fprintf(stderr, "filefd read error\n");
exit(1);
}
if (writen(sockfd, buff, count) < 0) {
fprintf(stderr, "writing to sockfd error\n");
exit(1);
}
}
Close(filefd);
Close(sockfd);
printf("文件%s已上传至服务器!\n", argv[1]);
return 0;
}
运行结果如下图所示:
- 研究OpenSSL算法,测试对称算法中的AES,非对称算法中的RSA,Hash算法中的MD5
- 通过命令
man openssl
查看帮助文档
- OpenSSL是一个强大的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用
- 对称加密:OpenSSL共提供了8种对称加密算法,其中7种是分组加密算法(AES、DES、Blowfish、CAST、IDEA、RC2、RC5,都支持ECB、CBC、CFB和OFB这四种常用的分组密码加密模式)和1种序列加密算法RC4。其中,AES使用的加密反馈模式(CFB)和输出反馈模式(OFB)分组长度是128位,其它算法使用的则是64位。事实上,DES算法里面不仅仅是常用的DES算法,还支持三个密钥和两个密钥3DES算法。
- 测试AES算法
- 测试命令为:
openssl enc -aes-128-cbc -in plain.txt -out encrypt.txt -pass pass:123456 -p
- 运行截图如下
- 测试命令为:
- 测试RSA算法
- 测试命令为如下:
openssl genrsa -out rsa_private_key.pem 1024
:生成一个没有加密的ca私钥openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
:生成公钥openssl rsautl -encrypt -in readme.txt -inkey a_private_key.pem -out hello.en
:加密(加密的内容写在了readme.txt文件中,内容为20155321lrt)openssl rsautl -decrypt -in hello.en -inkey a_private_key.pem -out hello.de
:解密cat hello.de
:查看解密后的结果,发现与刚开始卸载readme.txt中的内容一致
- 运行截图如下
- 测试MD5算法
- 测试命令为如下:
echo "20155321lrt" | openssl dgst -md5
:用MD5算法加密信息"20155321lrt" - 运行截图如下
- 测试命令为如下:
任务二
- 在Ubuntu中实现对实验二中的“wc服务器”通过混合密码系统进行防护
- 服务端代码
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;
}
- 客户端代码
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;
}
提交测试截图
参考资料
实验所涉及到的知识点
- SSL
- SSL是支持在Internet上进行安全通信的标准,并且将数据密码术集成到了协议之中。数据在离开计算机前就已被加密,然后只有到达它预定的目标后才被解密。证书和密码学算法支持了这一切的运转。
- 理论上,如果加密的数据在到达目标之前被截取或窃听,那些数据是不能被破解的。可将SSL和安全连接用于Internet上任何类型的协议,还可用SSL来保护Telnet会话。另外,如果连接传输敏感信息,则应使用 SSL。
- OpenSSL
- OpenSSL不仅是SSL,它可以实现消息摘要、文件的加密和解密、数字证书、数字签名和随机数字。关于OpenSSL库的内容非常多。
- OpenSSL不仅是API,它还是一个命令行工具,可以测试 SSL 服务器和客户机。
- 服务端编写所涉及到的函数
- SSL_library_init():SSL库初始化
- OpenSSL_add_all_algorithms:载入所有SSL算法
- SSL_load_error_strings():载入所有SSL的错误消息
- SSL_CTX_new(SSLv23_server_meethod()):产生一个SSL CTX
- SSL_CTX_use_certificate_file:载入用户的数字证书
- SSL_CTX_use_PrivateKey_file:载入用户私钥
- SSL_CTX_check_private_key():检查用户私钥是否正确
- SSL_new(ctx):产生一个新的SSL
- SSL_set_fd(ssl,new(fd)):socket加入到SSL
- SSL_accept(ssl):建立SSL连接
- SSL_write(ssl,buf,strlen(buf)):发消息给客户端
- SSL_read(ssl,buf,MAXBUF):接收客户端的消息
- SSL_shutdown(ssl):关闭SSL连接
SSL_free(ssl):释放SSL
- 客户端编写所涉及到的函数
- SSL_library_init():SSL库初始化
- OpenSSL_add_all_algorithms:载入所有SSL算法
- SSL_load_error_strings():载入所有SSL的错误消息
- SSL_CTX_new(SSLv23_client_meethod()):产生一个SSL CTX
- SSL_new(ctx):产生一个新的SSL
- SSL_set_fd(ssl,new(fd)):socket加入到SSL
- SSL_connect(ssl):建立SSL连接
- SSL_read(ssl.buffer,MAXBUF):接收服务器来的消息
- SSL_write(ssl,buf,strlen(buf)):发消息给服务端
- SSL_shutdown(ssl):关闭SSL连接
SSL_free(ssl):释放SSL
实验中遇到的问题及解决
- 在任务一的前期准备工作的编译阶段,输入命令
gcc -o to test_openssl.c -I /usr/local/ssl/inlcude /usr/local/ssl/lib -ldl -lpthread
会出现如下的错误提示
经过查看需要用到的库文件的相关属性,如下图所示,我发现要把编译过程中需要用到的库文件直接写在命令中就可以了
因此,需要输入命令gcc -o to test_openssl.c -I /usr/local/ssl/inlcude /usr/local/ssl/lib/libcrypto.a /usr/local/ssl/lib/libssl.a -ldl -lpthread
即可