网络传输加密及openssl使用样例(客户端服务器)

背景

网络传输中为保证数据安全,通常需要加密

常用加密方式

  • 对称加密:
    特点:使用同一个密钥进行加密和解密,速度快,但密钥管理复杂,适合在密钥交换后的大量数据传输。
    常用算法:DES、3DES、AES等。
  • 非对称加密:
    特点:使用一对密钥(公钥和私钥),公钥用于加密,私钥用于解密,或者私钥用于签名,公钥用于验证。这种方式解决了密钥交换的问题,但加密解密速度较慢。
    常用算法:RSA、DSS、ECC等。
  • 单向加密(散列加密、签名):
    特点:只能加密,不能解密,常用于生成数据的摘要或验证数据的完整性。元数据改变签名大概率发生变化
    常用算法:MD5、SHA-1、SHA-256、SHA-512等。

SSL

SSL(Secure Sockets
Layer)是最常用的网络传输加密协议之一,但需要注意的是,随着技术的发展,SSL的继任者TLS(Transport Layer
Security)已经成为更广泛使用的标准。然而,由于历史原因,“SSL”这一术语在日常使用中仍然非常普遍。

SSL/TLS协议通过结合对称加密、非对称加密和数字签名等多种加密技术,为网络通信提供了强大的安全保障。RSA、DSA和ECC等算法在SSL/TLS中扮演着重要的角色,分别用于密钥交换、数字签名等关键过程。

SSL/TLS协议主要包括以下几个关键组成部分:

  • 加密技术:SSL/TLS利用对称加密和非对称加密技术来保护数据的机密性和完整性。
  • 对称加密(如AES)用于加密和解密实际传输的数据,因为它速度快且效率高。
  • 非对称加密(如RSA、DSS、ECC)则用于密钥交换和数字签名,以确保通信双方的身份验证和数据的不可抵赖性。
  • 密钥交换:在SSL/TLS握手过程中,客户端和服务器会协商一个共同的加密密钥(会话密钥),用于后续的数据加密和解密。这个过程中,非对称加密算法(如RSA、ECC)被用来安全地交换密钥信息。
  • 数字签名:数字签名用于验证通信双方的身份和数据的完整性。在SSL/TLS中,服务器会向客户端发送一个包含其公钥和身份信息的证书,该证书由受信任的证书颁发机构(CA)签发,并使用非对称加密算法(如RSA、ECC)进行签名。客户端通过验证证书的签名来确认服务器的身份。

关于RSA、DSS(通常指DSA,即Digital Signature Algorithm)和ECC(Elliptic Curve Cryptography)与SSL/TLS的关系:

  • RSA:是一种非对称加密算法,广泛用于SSL/TLS中的密钥交换和数字签名。RSA算法的安全性基于大数分解的困难性,它使用一对密钥(公钥和私钥)来进行加密和解密操作。
  • DSA:是一种专门用于数字签名的算法,与RSA类似,也是基于非对称加密原理。DSA在SSL/TLS中主要用于验证证书的数字签名,而不是用于密钥交换。
  • ECC:是一种基于椭圆曲线密码学的加密算法,与RSA相比,ECC提供了更高的安全性和更短的密钥长度。ECC在SSL/TLS中的应用越来越广泛,因为它能够在保持相同安全性的同时减少计算量和存储需求。

OpenSSL

OpenSSL 是一个强大的开源加密软件库,它提供了多种加密算法、随机数生成器、安全套接字层(SSLv3/TLS)协议实现等功能。OpenSSL
不仅可以用于开发安全的网络应用程序,还可以作为一个命令行工具来处理加密相关的任务。

主要功能

  • 加密算法: 包括对称加密算法(如 AES)、非对称加密算法(如 RSA 和 ECC)、哈希算法(如 SHA-256)等。
  • 随机数生成: 提供了安全的随机数生成器。
  • 安全套接字层 (SSL/TLS): 实现了 SSLv3 和 TLS 协议,用于加密网络通信。
  • X.509 证书管理: 支持 X.509 标准的数字证书的创建、验证和管理。
  • PKCS 标准: 支持 PKCS#7、PKCS#12 等标准。

库结构

  • Crypto Library: 提供基本的加密功能。
  • SSL Library: 基于 Crypto Library 提供 SSL/TLS 协议的支持。
    命令行工具:
    openssl: 提供了丰富的命令行选项,用于执行各种加密操作

版本查看

openssl version -a

交互流程

证书生成

生成 RSA 私钥

openssl genrsa -out server.key 2048

OpenSSL
生成的私钥的数据结构并不是简单的随机数集合,而是遵循特定加密算法和协议规定的复杂数据结构。私钥的格式和内容取决于所使用的加密算法(如RSA、DSA、ECDSA等)和密钥的格式(如PEM、DER等)。

私钥的主要组成部分

对于不同的加密算法,私钥的组成会有所不同,但通常包括以下几个基本部分:

  • 算法标识:指明使用的是哪种加密算法(如RSA、DSA、ECDSA等)。
  • 参数:包括加密算法所需的具体参数。例如,对于RSA算法,这些参数可能包括两个大的质数(p和q)、模数(n = p * q)、公钥和私钥指数(e和d)等。
  • 随机性:私钥的生成通常涉及一定程度的随机性,以确保私钥的不可预测性和安全性。然而,这并不意味着私钥完全由随机数组成,而是随机数用于生成私钥的某些关键部分(如私钥指数d)。

私钥的格式

私钥可以以不同的格式存储,最常见的两种是PEM(Privacy Enhanced Mail)和DER(Distinguished Encoding Rules):

  • PEM格式:以-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----为边界,内部是Base64编码的私钥数据。PEM格式是文本格式,易于阅读和存储,但可能会包含额外的换行符和空格。
  • DER格式:DER是二进制格式,直接存储私钥的二进制数据,没有额外的编码或标记。DER格式更加紧凑,适用于需要最小化大小的场景,如嵌入式系统。

创建自签名证书:

openssl req -new -key server.key -out server.csr
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
在创建证书的过程中,你可能需要输入一些信息,
如国家代码、组织名称等。你可以填写适当的值或直接按回车键使用默认值

在这里插入图片描述

使用openssl req -new -x509 -key server.key -out server.crt -days 365
-nodes命令生成的证书(server.crt)是一个自签名的X.509证书,其数据结构包含了多个关键的信息字段。以下是该证书数据结构中通常包含的内容:

  • 证书版本(Version):
    表明证书遵循的X.509版本,常见的版本有V1、V2和V3。
  • 序列号(Serial Number):
    由证书颁发机构(CA)分配的唯一数字,用于唯一标识该证书。
  • 签名算法(Signature Algorithm):
    指出用于签名证书的算法,如SHA-256 with RSA等。
  • 颁发者(Issuer):
    指出颁发该证书的实体,由于是自签名证书,颁发者和主体(Subject)通常相同。
  • 有效期(Validity Period):
    包括证书的生效日期(Not Before)和失效日期(Not After),本例中有效期为365天。
  • 主体(Subject):
    包含证书持有者的信息,如组织名(O)、组织单位名(OU)、通用名(CN,即- Common Name,通常指域名或服务器名)等。这些信息在openssl req命令执行时会通过交互式输入或配置文件提供。
  • 公钥信息(Public Key Info):
    包含证书的公钥和公钥使用的算法(如RSA)。公钥用于与私钥配对,进行加密/解密和签名/验证操作。
  • 证书签名(Signature):
    使用颁发者的私钥对证书内容的哈希值进行签名,确保证书内容的完整性和真实性。由于是自签名证书,这里的签名是使用server.key中的私钥生成的。
  • 其他可选字段:
    如主题备用名称(Subject Alternative Name,SANs),可以包含除了CN之外的多个域名或IP地址,增强证书的灵活性。
  • 扩展(Extensions):
    X.509 V3证书支持扩展字段,用于包含额外的信息,如证书吊销列表(CRL)的分发点、密钥用途(Key Usage)、扩展密钥用途(Extended Key Usage)等。

签发证书

上述例子由于是自签名证书,其颁发者信息、签名和公钥都是基于server.key中的私钥生成的,因此不需要外部证书颁发机构(CA)的参与。自签名证书通常用于测试和开发环境,而不推荐在生产环境中使用,因为自签名证书无法由客户端自动验证其真实性,可能会导致安全警告。

线上环境需要购买证书 比如使用XX云

服务器端代码

版本 OpenSSL 1.0.2

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define PORT 8443
#define BUFFER_SIZE 1024

void handle_ssl_connection(SSL *ssl) {
    char buffer[BUFFER_SIZE];
    char resp[BUFFER_SIZE+10];
    int bytes_received;

    while ((bytes_received = SSL_read(ssl, buffer, BUFFER_SIZE)) > 0) {
        buffer[bytes_received] = '\0';
        printf("Received: %s\n", buffer);
        sprintf(resp,"srv recv %s",buffer);
        // 响应客户端
        SSL_write(ssl,resp, strlen(resp) + 1);
    }

    if (bytes_received <= 0) {
        fprintf(stderr, "Error on read: %d\n", bytes_received);
    }
}

int main() {
    int listen_fd, conn_fd;
    struct sockaddr_in serv_addr;
    SSL_CTX *ctx;
    SSL *ssl;
    int opt = 1;

    // 创建 socket
    if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 设置 SO_REUSEADDR 选项
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("Setsockopt failed");
        exit(EXIT_FAILURE);
    }

    // 绑定地址
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(PORT);

    if (bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("Bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(listen_fd, 5) < 0) {
        perror("Listen failed");
        exit(EXIT_FAILURE);
    }

    // 初始化 SSL 上下文
    SSL_library_init();
    ctx = SSL_CTX_new(TLSv1_2_method());
    if (!ctx) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }

    // 加载证书和私钥
    if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }

    // 验证私钥
    if (!SSL_CTX_check_private_key(ctx)) {
        fprintf(stderr, "Private key does not match the certificate public key\n");
        exit(EXIT_FAILURE);
    }

    // 接受客户端连接
    while (1) {
        struct sockaddr_in client_addr;
	socklen_t client_len = sizeof(client_addr);
        conn_fd = accept(listen_fd,(struct sockaddr *)&client_addr, &client_len);
        if (conn_fd < 0) {
            perror("Accept failed");
            continue;
        }
        printf("Accepted client  %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
        ssl = SSL_new(ctx);
        SSL_set_fd(ssl, conn_fd);

        // 建立 SSL 连接
        if (SSL_accept(ssl) == -1) {
            ERR_print_errors_fp(stderr);
            SSL_free(ssl);
            close(conn_fd);
            continue;
        }

        // 处理 SSL 连接
        handle_ssl_connection(ssl);

        // 清理资源
        SSL_shutdown(ssl);
        SSL_free(ssl);
        close(conn_fd);
    }

    SSL_CTX_free(ctx);
    close(listen_fd);

    return 0;
}

客户端代码

#include <stdio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define RCVBUFSIZE 1024

/*
gcc -g -o client client.c -lssl -lcrypto
*/
 
int main() {
    int sock;
    struct sockaddr_in serv_addr;
    char user_input[1024];
    char buffer[RCVBUFSIZE];
    int  recv_len =0;
    SSL_CTX *ctx;
    SSL *ssl;
    int res = 0;
    int i = 0;
    SSL_library_init();
    ctx = SSL_CTX_new(TLSv1_2_method());

	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }
	memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8443);
    inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
	

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
        printf("socket connect failed\n");
    }

    printf("connect client, socket:%d\n", sock);

    ssl = SSL_new(ctx);
    if (ssl == NULL) {
        printf("socket:%d, SSL_new failed\n", sock);
        goto sslerror;
    }

    SSL_set_fd(ssl, sock);

    if (SSL_connect(ssl) < 0) {
        printf("socket:%d, SSL_accept failed\n", sock);
        goto sslerror;
    }
    printf("ssl connected ok\n");
    
	for(i+0;i<500;i++){ // test
		printf("Enter data to send: ");
		fgets(user_input,sizeof(user_input)-1, stdin);
		int result = SSL_write(ssl, user_input, strlen(user_input));
		if (result <= 0) {
			int error = SSL_get_error(ssl, result);
			switch (error) {
				case SSL_ERROR_ZERO_RETURN:
					printf("SSL_write error: Connection closed\n");
					break;
				case SSL_ERROR_WANT_READ:
				case SSL_ERROR_WANT_WRITE:
					printf("SSL_write error: Try again later\n");
					break;
				case SSL_ERROR_SYSCALL:
					perror("SSL_write error");
					break;
				default:
					printf("SSL_write error: %d\n", error);
					break;
			}
		}

		recv_len = SSL_read(ssl, buffer, sizeof(buffer));
		printf("Received[%d] %s\n",recv_len,buffer);
	}

sslerror:
    SSL_shutdown(ssl);
    close(sock); 
    if (ssl) SSL_free(ssl);
    return 0;
}

常见错误

版本问题

新旧版本接口API会有不同

证书问题

139851570730928:error:140A90A1:lib(20):func(169):reason(161):ssl_lib.c:1966
  • lib(20) 表示错误发生在 SSL 库中。
  • func(169) 表示错误发生在 SSL 库中的某个特定函数。
  • reason(161) 表示错误的具体原因,通常表示私钥与证书不匹配或私钥格式不正确。
  • ssl_lib.c:1966 表示错误发生的源代码位置
    解决方法
  • 确认私钥格式:
    确保私钥文件 server.key 的格式是 PEM 格式。
    使用 openssl rsa -in server.key -check 来检查私钥文件是否正确。
  • 确认证书和私钥匹配:
    确保证书和私钥是由同一组密钥对生成的。
    使用 openssl x509 -pubkey -in server.crt -noout -text 获取证书中的公钥。
    使用 openssl rsa -in server.key -pubout -outform PEM 获取私钥对应的公钥,并与证书中的公钥进行比较。
  • 重新生成证书和私钥:
    如果私钥文件格式不正确或与证书不匹配,可以尝试重新生成证书和私钥文件。
    使用 OpenSSL 命令行工具重新生成文件。
  • 检查文件路径:
    确认 server.crt 和 server.key 文件的路径是正确的。
    如果文件不在当前目录下,确保提供完整的路径。
  • 设置正确的文件权限:
    确保您的程序有足够的权限读取证书和私钥文件。
    可以尝试更改文件权限以允许程序访问:chmod 600 server.crt server.key

证书格式

SSL_FILETYPE_PEM 是 OpenSSL 中用于处理 PEM (Privacy Enhanced Mail) 格式的常量。PEM
是一种广泛使用的 ASCII 编码格式,用于存储证书、密钥和其他加密数据。除了 PEM 格式之外,OpenSSL 还支持其他几种格式,例如
DER (Distinguished Encoding Rules) 格式。

PEM 格式的特点是使用 Base64 编码,并以特定的文本标记开始和结束。例如,一个 PEM 格式的私钥文件通常看起来像这样:

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwUq...
...
-----END RSA PRIVATE KEY-----

PEM 格式的证书文件通常看起来像这样:

-----BEGIN CERTIFICATE-----
MIIEowIBAAKCAQEAwUq...
...
-----END CERTIFICATE-----

DER 格式

DER 格式是一种二进制编码格式,它不包含任何文本标记。这种格式比 PEM 更紧凑,但不是文本可读的。一个 DER
格式的文件通常是一个纯二进制文件,没有额外的文本标记。

在 OpenSSL 中使用不同格式

  • 使用 PEM 格式:
    使用 SSL_FILETYPE_PEM 常量来加载 PEM 格式的证书和密钥文件。
  • 使用 DER 格式:
    使用 SSL_FILETYPE_ASN1 常量来加载 DER 格式的证书和密钥文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值