zoukankan      html  css  js  c++  java
  • 2017-2018-1 20155306 实验五 通信协议设计

    2017-2018-1 20155306 20155315 实验五 通信协议设计

    实验内容:

    通讯协议设计-1

    在Ubuntu中完成 http://www.cnblogs.com/rocedu/p/5087623.html 中的作业:
    1. 两人一组
    基于Socket实现TCP通信,一人实现服务器,一人实现客户端
    2. 研究OpenSSL算法,测试对称算法中的AES,非对称算法中的RSA,Hash算法中的MD5
    3. 选用合适的算法,基于混合密码系统实现对TCP通信进行机密性、完整性保护。
    4. 学有余力者,对系统进行安全性分析和改进。
    
     提交运行结果截图  
    

    通讯协议设计-2

    在Ubuntu中实现对实验二中的“wc服务器”通过混合密码系统进行防护
    提交测试截图  
    

    实验步骤:

    任务一:

    1. 下载OpenSSL 1.1.0alpha

    2.解压源代码:

    tar xzvf    openssl-1.1.0-pre1.tar.gz 
    
    

    3.然后进入源代码目录:

    cd openssl-1.1.0-pre1
    

    4.编译安装:

    ./configure
    
    make
    
    sudo make install
    

    5. 测试代码test_openssl.c:

    #include <stdio.h>
    #include <openssl/evp.h>
    
    int main(){
        
        OpenSSL_add_all_algorithms();
        
        return 0;
    }
    

    6. 用下面命令编译:【编译后出现“致命错误”,在问题中有相应解决】

    gcc -o to test_openssl.c -I /usr/local/ssl/inlcude /usr/local/ssl/lib -ldl -lpthread
    

    7. 执行:【如下图,成功返回0,安装成功】

    ./to;echo $?
    

    8. 基于Socket实现TCP通信

    • 基于C的Socket编程相关函数和数据类型

    1.socket()函数

    该函数用于根指定的地址族、数据类型和协议来分配一个套接字的描述 字及其所用的资源。 Socket 函数原型为:

    int socket( int domain , int type , int protocol ) 
    

    a、 参数 domain 指定地址描述,一般为 AP_INET;

    b、 参数 type 指定 socket 类型:SOCK_STREAM 和 SOCK_DGRAM;

    1. bind()函数

    该函数用于将一个本地地址与一个套接字绑定在一起。

    int bind( int sockfd , struct sockadd* my_addr , int addrlen)  
    
    

    sockfd:socket 描述符,使用 socket 函数返回值,将该 socket 与本机上的一个端口相关联。

    3.connect()函数

    与远程服务器建立一个 TCP 连接。

    int connect(int sockfd, struct sockaddr* serv_addr, int addrlen);
    

    a. sockfd:目的服务器的 socket 描述符

    b. connect 函数返回值:为-1 表示遇到错误,并且 errno 中包含相应的错误码,进行服务器端程序设计时不需调用 connect 函数。

    4.listen()函数

    在服务器端程序中,当 socket 与某一端口绑定后,需要监听该端口, 及时处理到达该端口上的服务请求。

    int listen(int sockfd, int backlog); 
    

    backlog:指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待接收 backlog 限制了队列中等待服务的请求数目,系统缺省值为 20。

    5.accept()函数

    当某个客户端试图与服务器监听的端口连接时, 该连接请求将排队等待 服务器用 accept 接收它并为其建立一个连接。

    int a ccept(int sockfd, s truct s ockaddr* addr, i nt* addrlen);
    

    accept 函数返回值:为-1 表示遇到错误,并且 errno 中包含相应的错误码。

    6.sent()和recv()函数

    int send(int sockfd, const void* msg, int len, int flags) 
    
    int recv(int sockfd, void* buf, int len, unsigned int flags);
    

    7.close()函数:

    在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。

    close(int sockfd);
    
    • 实现的客户端和服务器核心代码如下
    服务器:
    #include <stdio.h> 
    #include <stdlib.h> 
    #include <string.h> 
    #include <errno.h> 
    #include <sys/socket.h> 
    #include <arpa/inet.h> 
    #include <netinet/in.h> 
    #include <sys/types.h> 
    #include <unistd.h> 
    #include <sys/time.h> 
      
    #define BUFLEN 1024 
    #define PORT 6666
    #define LISTNUM 20
      
    int main() 
    { 
        int sockfd, newfd; 
        struct sockaddr_in s_addr, c_addr; 
        char buf[BUFLEN]; 
        socklen_t len; 
        unsigned int port, listnum; 
        fd_set rfds; 
        struct timeval tv; 
        int retval,maxfd; 
          
        /*建立socket*/ 
        if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ 
            perror("socket"); 
            exit(errno); 
        }else 
            printf("socket create success!
    "); 
        memset(&s_addr,0,sizeof(s_addr)); 
        s_addr.sin_family = AF_INET; 
        s_addr.sin_port = htons(PORT); 
        s_addr.sin_addr.s_addr = htons(INADDR_ANY); 
        
        /*把地址和端口帮定到套接字上*/ 
        if((bind(sockfd, (struct sockaddr*) &s_addr,sizeof(struct sockaddr))) == -1){ 
            perror("bind"); 
            exit(errno); 
        }else 
            printf("bind success!
    "); 
        /*侦听本地端口*/ 
        if(listen(sockfd,listnum) == -1){ 
            perror("listen"); 
            exit(errno); 
        }else 
            printf("the server is listening!
    "); 
        while(1){ 
            printf("*****************聊天开始***************
    "); 
            len = sizeof(struct sockaddr); 
            if((newfd = accept(sockfd,(struct sockaddr*) &c_addr, &len)) == -1){ 
                perror("accept"); 
                exit(errno); 
            }else 
                printf("正在与您聊天的客户端是:%s: %d
    ",inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port)); 
            while(1){ 
                FD_ZERO(&rfds); 
                FD_SET(0, &rfds); 
                maxfd = 0; 
                FD_SET(newfd, &rfds); 
                /*找出文件描述符集合中最大的文件描述符*/ 
                if(maxfd < newfd) 
                    maxfd = newfd; 
                /*设置超时时间*/ 
                tv.tv_sec = 6; 
                tv.tv_usec = 0; 
                /*等待聊天*/ 
                retval = select(maxfd+1, &rfds, NULL, NULL, &tv); 
                if(retval == -1){ 
                    printf("select出错,与该客户端连接的程序将退出
    "); 
                    break; 
                }else if(retval == 0){ 
                    printf("waiting...
    "); 
                    continue; 
                }else{ 
                    /*用户输入信息了*/ 
                    if(FD_ISSET(0, &rfds)){ 
                
                        /******发送消息*******/ 
                        memset(buf,0,sizeof(buf)); 
                        /*fgets函数:从流中读取BUFLEN-1个字符*/ 
                        fgets(buf,BUFLEN,stdin); 
                        /*打印发送的消息*/ 
                        //fputs(buf,stdout); 
                        if(!strncasecmp(buf,"quit",4)){ 
                            printf("server 请求终止聊天!
    "); 
                            break; 
                        } 
                            len = send(newfd,buf,strlen(buf),0); 
                        if(len > 0) 
                            printf("	消息发送成功:%s
    ",buf); 
                        else{ 
                            printf("消息发送失败!
    "); 
                            break; 
                        } 
                    } 
                    /*客户端发来了消息*/ 
                    if(FD_ISSET(newfd, &rfds)){ 
                        /******接收消息*******/ 
                        memset(buf,0,sizeof(buf)); 
                        /*fgets函数:从流中读取BUFLEN-1个字符*/ 
                        len = recv(newfd,buf,BUFLEN,0); 
                        if(len > 0) 
                            printf("客户端发来的信息是:%s
    ",buf); 
                        else{ 
                            if(len < 0 ) 
                                printf("接受消息失败!
    "); 
                            else 
                                printf("客户端退出了,聊天终止!
    "); 
                            break; 
                        } 
                    } 
                } 
            } 
            /*关闭聊天的套接字*/ 
            close(newfd); 
            /*是否退出服务器*/ 
            printf("服务器是否退出程序:y->是;n->否? "); 
            bzero(buf, BUFLEN); 
            fgets(buf,BUFLEN, stdin); 
            if(!strncasecmp(buf,"y",1)){ 
                printf("server 退出!
    "); 
                break; 
            } 
        } 
        /*关闭服务器的套接字*/ 
        close(sockfd); 
        return 0; 
    }
    
    客户端:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/time.h>
    
    #define BUFLEN 1024
    #define PORT 6666
    
    int main(int argc, char **argv)
    {
        int sockfd;
        struct sockaddr_in s_addr;
        socklen_t len;
        unsigned int port;
        char buf[BUFLEN];
        fd_set rfds;
        struct timeval tv;
        int retval, maxfd; 
        
        /*建立socket*/
        if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
            perror("socket");
            exit(errno);
        }else
            printf("socket create success!
    ");
    
        
        /*设置服务器ip*/
        memset(&s_addr,0,sizeof(s_addr));
        s_addr.sin_family = AF_INET;
         s_addr.sin_port = htons(PORT);
        if (inet_aton(argv[1], (struct in_addr *)&s_addr.sin_addr.s_addr) == 0) {
            perror(argv[1]);
            exit(errno);
        }
        /*开始连接服务器*/ 
        if(connect(sockfd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr)) == -1){
            perror("connect");
            exit(errno);
        }else
            {printf("conncet success!
    ");
    	printf("*****************聊天开始***************
    ");}
        
        while(1){
            FD_ZERO(&rfds);
            FD_SET(0, &rfds);
            maxfd = 0;
            FD_SET(sockfd, &rfds);
            if(maxfd < sockfd)
                maxfd = sockfd;
            tv.tv_sec = 6;
            tv.tv_usec = 0;
            retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
            if(retval == -1){
                printf("select出错,客户端程序退出
    ");
                break;
            }else if(retval == 0){
                printf("waiting...
    ");
                continue;
            }else{
                /*服务器发来了消息*/
                if(FD_ISSET(sockfd,&rfds)){
                    /******接收消息*******/
                    bzero(buf,BUFLEN);
                    len = recv(sockfd,buf,BUFLEN,0);
                    if(len > 0)
                        printf("服务器发来的消息是:%s
    ",buf);
                    else{
                        if(len < 0 )
                            printf("接受消息失败!
    ");
                        else
                            printf("服务器退出了,聊天终止!
    ");
                    break; 
                    }
                }
                /*用户输入信息了,开始处理信息并发送*/
                if(FD_ISSET(0, &rfds)){ 
                    /******发送消息*******/ 
                    bzero(buf,BUFLEN);
                    fgets(buf,BUFLEN,stdin);
                   
                    if(!strncasecmp(buf,"quit",4)){
                        printf("client 请求终止聊天!
    ");
                        break;
                    }
                        len = send(sockfd,buf,strlen(buf),0);
                    if(len > 0)
                        printf("	消息发送成功:%s
    ",buf); 
                    else{
                        printf("消息发送失败!
    ");
                        break; 
                    } 
                }
            }
        
        }
        /*关闭连接*/
        close(sockfd);
    
        return 0;
    }
    

    9. 研究OpenSSL算法,测试对称算法中的AES,非对称算法中的RSA,Hash算法中的MD5

    openssl支持在命令行进行各种基本加密算法的操作。这些操作过程无需编程,其命令参数与程序函数调用加密的参数有着很好的直接对应关系。openssl提供了N多的对称加密算法指令,enc就是把这些N多的对称的加密算法指令统一集成到enc指令中。当用户使用时,只需使用enc,指定加密算法,就是完成单独的加密算法指令完成的操作。而且,enc中可以指定的对称加密算法指令可能并没有以单独指令的形式存在。推荐使用enc的方式。所以使用man命令查看帮助文档,如下图:

    通过自己上网查询和翻译man文档,明白了各参数的含义:

    [in/out]
    
    这两个参数指定输入文件和输出文件,加密是输入文件是明文,输出文件是密文;解密时输入文件是密文,输出文件是明文。
    
    [pass]
    
    指定密码的输入方式,共有五种方式:命令行输入(stdin)、文件输入(file)、环境变量输入(var)、文件描述符输入(fd)、标准输入(stdin)。默认是标准输入,及从键盘输入。
    
    [e/d]
    
    e:加密, d:解密  默认是加密
    
    [-a/-base64]
    
    由于文件加密后是二进制形式,不方便查看,使用该参数可以使加密后的内容经过base64编码,使其可读;同样,解密时需要先进行base64解编码,然后进行解密操作。
    
    [-k/-kfile]
    
    兼容以前版本,指定密码输入方式,现已被pass参数取代
    
    [md]
    
    指定密钥生成的摘要算法,用户输入的口令不能直接作为文件加密的密钥,而是经过摘要算法做转换,此参数指定摘要算法,默认md5
    
    [-S]
    
    为了增强安全性,在把用户密码转换成加密密钥的时候需要使用盐值,默认盐值随机生成。使用该参数,则盐值由用户指定。也可指用-nosalt指定不使用盐值,但降低了安全性,不推荐使用。
    
    [K/IV]
    
    默认文件的加密密钥的Key和IV值是有用户输入的密码经过转化生成的,但也可以由用户自己指定Key/IV值,此时pass参数不起作用
    
    [pP]
    
    加上p参数会打印文件密钥Key和IV值,加上P参数也会打印文件密钥Key和IV值,但不进行真正的加解密操作
    
    [bufsize]
    
    读写文件的I/O缓存,一般不需要指定
    
    [-nopad]
    
    不使用补齐,这就需要输入的数据长度是使用加密算法的分组大小的倍数
    
    [engine]
    
    指定三方加密设备,没有环境,暂不实验
    
    • RSA加解密

    生成一个密钥:【这里-out指定生成文件的。需要注意的是这个文件包含了公钥和密钥两部分,也就是说这个文件即可用来加密也可以用来解密。后面的1024是生成密钥的长度。】

    openssl genrsa -out test.key 1024
    

    openssl可以将这个文件中的公钥提取出来:【-in指定输入文件,-out指定提取生成公钥的文件名】

    openssl rsa -in test.key -pubout -out test_pub.key
    

    利用此前生成的公钥加密文件:【-in指定要加密的文件,-inkey指定密钥,-pubin表明是用纯公钥文件加密,-out为加密后的文件】

    openssl rsautl -encrypt -in hello -inkey test_pub.key -pubin -out hello.en
    

    解密文件:【-in指定被加密的文件,-inkey指定私钥文件,-out为解密后的文件。】

    openssl rsautl -decrypt -in hello.en -inkey test.key -out hello.de
    

    • AES加解密

    加密:

    openssl enc -aes-128-cbc -e -a -in in.txt -out dj.txt -K c286696d887c9aa0611bbb3e2025a45a 
    

    解密:

    openssl enc -aes-128-cbc -d -a -in dj.txt -out dedj.txt -K c286696d887c9aa0611bbb3e2025a45a 
    
    

    密钥也可以这样输入:

    openssl enc -aes-128-cbc -in plain.txt -out out.txt -pass pass:123456
    

    上述命令各参数含义如下:

    -aes-128-ecb 表示使用128位AES算法,EBC工作模式;
    
    -K 0001......0F 表示加密使用的密钥为十六进制000102030405060708090A0B0C0D0E0F;
    
    -in和-out指明输入和输出文件名;
    
    -p表示屏幕打印加密所使用的密钥key、初始向量iv、盐值salt等信息。
    
    

    MD5加解密:

    openssl dgst -md5 20155339.txt
    

    任务二:

    混合密码系统如图所示:

    通过查阅资料,基于openSSl实现客户端服务器,大概就需要以下步骤:

    (1) OpenSSL初始化

    在使用OpenSSL之前,必须进行相应的协议初始化工作,这可以通过下面的函数实现:

    void SSLeay_add_ssl_algorithms();
    该函数实际是int SSL_library_init(void),在ssl.h中时行了宏定义;
    

    (2) 选择会话协议

    在利用OpenSSL开始SSL会话之前,需要为客户端和服务器制定本次会话采用的协议,目前能够使用的协议包括TLSv1.0、SSLv2、SSLv3、SSLv2/v3。

    需要注意的是,客户端和服务器必须使用相互兼容的协议,否则SSL会话将无法正常进行。

    SSL_METHOD *SSLv23_client_method();
    

    (3 ) 创建会话环境

    在OpenSSL中创建的SSL会话环境称为CTX,使用不同的协议会话,其环境也不一样的。

    申请SSL会话环境的OpenSSL函数是:

    SSL_CTX *SSL_CTX_new(SSL_METHOD * method);
    

    当SSL会话环境申请成功后,还要根据实际的需要设置CTX的属性,通常的设置是指定SSL握手阶段证书的验证方式和加载自己的证书。

    制定证书验证方式的函数是:

    int SSL_CTX_set_verify(SSL_CTX *ctx,int mode,int(*verify_callback),int(X509_STORE_CTX *));
    

    为SSL会话环境加载CA证书的函数是:

    SSL_CTX_load_verify_location(SSL_CTX *ctx,const char *Cafile,const char *Capath);
    

    为SSL会话加载用户证书的函数是:

    SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file,int type);
    

    为SSL会话加载用户私钥的函数是:

    SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx,const char* file,int type);
    

    在将证书和私钥加载到SSL会话环境之后,就可以调用下面的函数来验证私钥和证书是否相符:

    int SSL_CTX_check_private_key(SSL_CTX *ctx);
    

    (4) 建立SSL套接字

    SSL套接字是建立在普通的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);
    
    //绑定只写套接字
    

    (5) 完成SSL握手

    在成功创建SSL套接字后,客户端应使用函数SSL_connect( )替代传统的函数connect( )来完成握手过程:

    int SSL_connect(SSL *ssl);
    

    而对服务器来讲,则应使用函数SSL_ accept ( )替代传统的函数accept ( )来完成握手过程:

    int SSL_accept(SSL *ssl);
    

    握手过程完成之后,通常需要询问通信双方的证书信息,以便进行相应的验证,这可以借助于下面的函数来实现:

    X509 *SSL_get_peer_certificate(SSL *ssl);
    

    该函数可以从SSL套接字中提取对方的证书信息,这些信息已经被SSL验证过了。

    X509_NAME *X509_get_subject_name(X509 *a);
    
    

    该函数得到证书所用者的名字。

    (6) 进行数据传输

    当SSL握手完成之后,就可以进行安全的数据传输了,在数据传输阶段,需要使用SSL_read( )和SSL_write( )来替代传统的read( )和write( )函数,来完成对套接字的读写操作:

    int SSL_read(SSL *ssl,void *buf,int num);
    
    int SSL_write(SSL *ssl,const void *buf,int num);
    

    (7 ) 结束SSL通信

    当客户端和服务器之间的数据通信完成之后,调用下面的函数来释放已经申请的SSL资源:

    
    int SSL_shutdown(SSL *ssl);
    //关闭SSL套接字
    
    void SSl_free(SSL *ssl);
    //释放SSL套接字
    
    void SSL_CTX_free(SSL_CTX *ctx); 
    //释放SSL会话环境
    

    客户端程序的框架为:

    meth = SSLv23_client_method();  
    ctx = SSL_CTX_new (meth);  
    ssl = SSL_new(ctx);  
    fd = socket();  
    connect();  
    SSL_set_fd(ssl,fd);  
    SSL_connect(ssl);  
    SSL_write(ssl,"Hello world",strlen("Hello World!"));  
    

    服务端程序的框架为:

    meth = SSLv23_server_method();  
    ctx = SSL_CTX_new (meth);  
    ssl = SSL_new(ctx);  
    fd = socket();  
    bind();  
    listen();  
    accept();  
    SSL_set_fd(ssl,fd);  
    SSL_connect(ssl);  
    SSL_read (ssl, buf, sizeof(buf)); 
    

    实验中遇到的问题和解决方案

    • 问题一:任务一,进行gcc编译时,出现如下错误:

    • 解决:查阅资料以后,发现尝试编译的程序使用OpenSSL,但是需要和OpenSSL链接的文件(库和头文件)在你Linux平台上缺少。
      使用下面的命令后,重新编译。

     sudo apt-get install libssl-dev
    

    参考资料

    利用openssl实现SSL安全通讯协议(一)
    安全通信系统--OpenSSL服务器和客户端
    使用openssl命令进行加密解密及散列运算的命令行

  • 相关阅读:
    06HTML和CSS知识点总结(六)
    05HTML和CSS知识点总结(五)
    webpack警告解除(WARNING in configuration The 'mode' option has not been set)
    如何Altium Designer AD输出元件清单及按照不同数值分类
    M57962
    艾科 驱动电路分析
    矢量旋度的散度恒为零
    迟滞比较器
    与非门SR锁存器
    寄存器与锁存器的区别
  • 原文地址:https://www.cnblogs.com/0831j/p/8052896.html
Copyright © 2011-2022 走看看