SSL\TLS协议与数字证书
SSL\TLS协议与证书链
reference:
https://blog.csdn.net/qq_51789211/article/details/127778352,
https://www.runoob.com/np/tls-protocol.html
概述
SSL(Socket Secure Layer)是TLS(Transport Layer Security)的前身。TLS1.0在SSL3.0的基础上提出,当前已不推荐使用,推荐使用较新版本的TLS。
TLS(Transport Layer Security,传输层安全)是一种用于在网络中加密数据传输的协议,旨在保护数据的机密性、完整性和身份验证。
TLS基于TCP,建立两个进程之间的安全传输连接。
工作原理
TLS通过在应用层和传输层之间插入加密层,保护数据的安全。核心功能是建立加密通道和验证身份。TLS握手过程如下。

图中流程为客户端验证服务器证书的单向验证流程,实际情况下,TLS支持双向验证,此时不仅客户端验证服务器证书链,服务器也要验证客户端的证书链。
数字证书
数字证书是基于非对称加密(公钥 + 私钥)的 “网络身份凭证”,核心作用是证明 “公钥的真实归属”,解决 “公钥伪造” 和 “身份冒充” 问题,是 HTTPS、电子签名、安全登录等场景的信任基石。简单说,数字证书就像 “网络世界的身份证”,由权威机构背书,确保双方通信时 “对方是可信的”。
一、 为什么需要数字证书?
非对称加密中,公钥1是公开分发的,但存在一个致命漏洞:公钥可能被伪造(即 “中间人攻击”)。
举个例子:你想给银行网站发敏感信息(如密码),银行公开了公钥 A,你用 A 加密信息后发送。但如果黑客拦截了你的请求,伪造了一个 “假公钥 A’”,并冒充银行把 A’发给你,你用 A’加密的信息,黑客就能用自己的私钥解密(获取敏感信息),再用真实的公钥 A 加密后发给银行 —— 你和银行都无法发现异常。
而数字证书的作用就是 “防伪” :银行的公钥会由 CA 签署成证书,你收到证书后,会先验证 CA 的签名(用 CA 的公钥),确认证书是真实的,进而确认证书里的公钥是银行的,避免被中间人欺骗。
二、核心定义和本质
数字证书是一份结构化的电子文件,包含以下关键信息(由权威机构加密签署,不可篡改):
- 证书持有者的身份信息(如网站域名、企业名称、个人姓名);
- 证书持有者的公钥(用于加密通信或验证签名);
- 签发机构(CA,Certificate Authority,即 “证书认证中心”)的名称;
- 证书的有效期(超出期限则失效,需重新申请);
- 签发机构用自身私钥生成的数字签名(用于验证证书本身的真实性)。
其本质是:由权威第三方(CA)为 “公钥 + 身份” 做担保,告诉接收方 “这份公钥确实属于该身份,可放心使用”。
三、数字证书的核心工作流程(以 HTTPS 为例)
-
证书申请(CSR 提交) :网站运营者生成自己的公钥 + 私钥,然后向 CA 提交 “证书申请文件(CSR)”,包含网站域名、公钥等信息,并证明自己是域名的合法所有者(如验证域名解析、企业资质等)。
-
CA 审核与签发:CA 验证申请者的身份和域名所有权后,用 CA 自身的私钥对申请者的公钥、身份信息等进行数字签名,生成正式的数字证书,发给申请者。
-
证书部署与使用:网站将数字证书部署在服务器上,当用户通过 HTTPS 访问时,服务器会把证书发送给用户的浏览器。
-
证书验证(关键步骤) :
- 浏览器先获取 CA 的公钥(主流 CA 的公钥已预装在浏览器 / 操作系统中,如 VeriSign、Let’s Encrypt 等);
- 用 CA 的公钥验证证书上的 “CA 数字签名”—— 若验证通过,说明证书未被篡改,且公钥确实是该网站的;
- 同时检查证书的有效期、域名是否匹配(如证书是www.abc.com,不能用于www.def.com),全部通过后,才能建立安全加密通信。
四、核心技术:CA 与信任链
数字证书的信任基础是 “CA 的权威性”,而 CA 体系是分层的(避免单一 CA 风险),形成 “信任链”,信任链是保障 CA 体系安全、解决 “如何信任 CA” 的关键机制 —— 两者共同构成了非对称加密的信任基础,确保数字证书不被伪造、身份可追溯:
- 根 CA:顶级 CA,其公钥预装在浏览器 / 操作系统中(如 Windows、Chrome 的根证书库),无需再验证(默认可信);
- 中间 CA:由根 CA 授权签发的二级 CA,实际用于给用户签发证书(根 CA 不直接面向用户,避免私钥泄露风险);
- 信任链验证:当验证中间 CA 签发的证书时,会先验证中间 CA 的证书(由根 CA 签名),再验证用户证书(由中间 CA 签名),层层追溯到根 CA,形成完整信任链。
1. CA 的核心定位与作用
CA 是具备权威资质的第三方机构(需符合国际 / 国家安全标准),核心职责是 “验证身份 + 签发证书 + 管理证书生命周期”,相当于 “网络世界的公证处”:
- 身份验证:接收用户(个人 / 企业 / 服务器)的证书申请后,验证申请者的真实身份(如域名所有权、企业资质、个人信息),确保 “公钥与身份对应”;
- 签发证书:验证通过后,用 CA 自身的私钥对申请者的公钥、身份信息、有效期等数据进行数字签名,生成合法的数字证书;
- 证书管理:负责证书的更新、吊销(如私钥泄露时)、查询等,发布吊销列表(CRL)或提供在线验证接口(OCSP),确保失效证书不被滥用;
- 信任背书:CA 的权威性来自其合规资质(如国际 WebTrust 认证、国内《电子认证服务许可证》),其公钥预装在浏览器、操作系统、手机系统中,默认为 “可信”。
2. 为什么需要信任链?(CA 分层的核心原因)
如果只有一个 “顶级 CA” 直接给所有用户签发证书,会存在两个致命问题:
- 私钥泄露风险:顶级 CA 的私钥一旦泄露,所有由它签发的证书都会失效,整个信任体系崩塌;
- 管理效率低:全球亿级用户 / 服务器直接向顶级 CA 申请证书,审核、签发压力无法承载。
因此,CA 体系采用分层架构,通过 “信任链” 层层背书,既分散风险,又提升管理效率 —— 信任链的本质是 “从可信的顶级 CA,逐级验证到最终用户证书的链条”。
3. 信任链的结构与层级(以 HTTPS 为例)
信任链是自上而下的层级关系,核心分为 3 层(部分场景可能有更多中间层):
3.1. 根 CA(Root CA):信任的起点
-
地位:CA 体系的 “顶级权威”,是信任链的源头,无需其他机构验证(默认可信);
-
关键特点:
- 私钥高度保密(通常存储在离线硬件安全模块 HSM (Hardware Security Module)中,永不联网);
- 公钥预装在主流浏览器、操作系统、手机系统的 “根证书库” 中;
- 不直接面向终端用户签发证书,仅用于授权 “中间 CA”。
3.2. 中间 CA(Intermediate CA):信任的传递者
-
地位:由根 CA 签发证书(即 “中间 CA 证书”)的二级 CA,是实际面向用户的 “签发者”;
-
关键特点:
- 根 CA 通过数字签名,将自己的信任 “传递” 给中间 CA;
- 可根据地域、行业、用途细分(如 “金融行业中间 CA”“国内某地区中间 CA”);
- 即使某一个中间 CA 的私钥泄露,仅影响其签发的证书,不会波及整个根 CA 体系,风险可控。
3.3. 终端实体证书(End-Entity Certificate):用户 / 服务器的证书
- 地位:信任链的最底层,即普通用户、企业、网站服务器使用的证书(如你访问淘宝时,淘宝服务器出示的证书);
- 关键特点:由中间 CA 签发,其合法性依赖于 “中间 CA 证书的有效性”,最终追溯到根 CA。
4. 信任链的验证流程
当你用浏览器访问 HTTPS 网站时,浏览器会自动完成信任链验证,步骤如下(以 “根 CA→中间 CA→淘宝证书” 为例):
- 获取终端证书:网站服务器向浏览器发送自己的证书(终端实体证书);
- 检查终端证书的签发者:浏览器读取证书中的 “签发者信息”,发现该证书由 “某中间 CA” 签发;
- 获取中间 CA 证书:浏览器从网站服务器获取该中间 CA 的证书(或从本地缓存、CA 服务器下载);
- 验证中间 CA 证书:用 “根 CA 的公钥” 验证中间 CA 证书上的 “根 CA 数字签名”—— 若签名验证通过,说明中间 CA 是根 CA 授权的,可信;
- 验证终端证书:用 “中间 CA 的公钥” 验证终端证书上的 “中间 CA 数字签名”—— 若通过,说明终端证书未被篡改,且公钥属于该网站;
- 确认信任链完整:从终端证书→中间 CA→根 CA,层层验证无断裂,且证书有效期、域名匹配,浏览器才判定 “证书可信”,建立安全连接。
私钥验证机制详解
1. 基本原理
私钥验证的核心是基于非对称加密的特性:
- 公钥加密的数据只能用对应的私钥解密
- 私钥签名的数据只能用对应的公钥验证
这种特性确保了只有私钥的持有者才能生成有效的签名。
2. TLS握手过程中的私钥验证
在TLS握手过程中,私钥验证的具体步骤如下:
以客户端向服务器证明身份为例:
-
客户端发送证书:
- 客户端发送自己的证书链(包含终端证书和中间证书)
- 证书中包含客户端的公钥
-
服务器发送挑战数据:
- 服务器生成一些随机数据作为"挑战"
- 这些数据是唯一的,每次握手都不同
-
客户端使用私钥签名挑战:
// 这部分在OpenSSL内部自动完成 SSL_connect(ssl); // 握手过程中会自动进行私钥签名- 客户端使用自己的私钥(
client.key)对挑战数据进行签名 - 签名过程是:用私钥对挑战数据的哈希值进行加密
- 客户端使用自己的私钥(
-
服务器验证签名:
- 服务器收到签名后,使用客户端证书中的公钥进行验证
- 验证过程是:用公钥解密签名,得
- 到哈希值
- 同时计算原始挑战数据的哈希值
- 比较两个哈希值,如果相同则验证成功
3. 为什么这能证明身份?
这个机制能证明身份的原因是:
-
只有私钥持有者才能生成有效签名:
- 即使攻击者知道公钥和挑战数据,也无法生成有效签名
- 因为签名需要私钥,而私钥只有合法的客户端拥有
-
每次挑战都不同:
- 使用随机挑战数据防止重放攻击
- 即使攻击者截获了之前的签名,也无法在新会话中使用
-
证书绑定身份:
- 证书中的公钥与私钥是数学上的一对
- 证书本身由CA签名,证明了公钥的所有者身份
4. 实际代码中的体现
在代码中,这个过程是自动进行的:
// 客户端代码 // 加载客户端证书和私钥 SSL_CTX_use_certificate_chain_file(ctx, CLIENT_CERT_CHAIN.c_str()); SSL_CTX_use_PrivateKey_file(ctx, CLIENT_PRIVATE_KEY.c_str(), SSL_FILETYPE_PEM);// 握手过程中自动进行私钥验证
if (SSL_connect(ssl) <= 0) {
// 如果验证失败,这里会返回错误
}// 服务器代码
// 要求客户端提供证书
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
// 握手过程中自动验证客户端证书和私钥
if (SSL_accept(ssl) <= 0) {
// 如果验证失败,这里会返回错误
}
5. 简化的示例
为了更好地理解,这里有一个简化的示例说明这个过程:
// 假设的简化过程(实际TLS握手更复杂)// 1. 服务器发送挑战
std::string challenge = "random_data_12345";// 2. 客户端使用私钥签名(实际OpenSSL内部完成)
std::string signature = rsa_sign_with_private_key(challenge, client_private_key);// 3. 客户端发送证书和签名
send_to_server(client_certificate, signature);
// 4. 服务器验证签名
bool verify_result = rsa_verify_signature(challenge, signature, public_key_from_certificate);
if (verify_result) {
// 验证成功,客户端确实拥有对应私钥
}
代码实例
git仓库
https://gitcode.com/KindleDawn/TLS_verify_demo
证书生成步骤
根CA证书 (ca.crt)
↓ (签名)
中间CA证书 (intermediate.crt)
↓ (签名)
终端证书 (server.crt 或 client.crt)
根CA证书:生成根CA私钥-》通过私钥生成自签名根CA证书
中间CA证书:生成中间CA私钥-》通过私钥生成中间CA CSR-》用根CA证书和私钥签名生成中间CA证书
服务器终端证书:生成服务器终端私钥-》通过私钥生成服务器CSR-》用中间CA证书和私钥签名生成服务器终端证书
客户端终端证书:生成客户端终端私钥-》通过私钥生成客户端CSR-》用中间CA证书和私钥签名生成客户端终端证书
-
CSR 是 Certificate Signing Request(证书签名请求)的缩写,本质是「向 CA(证书颁发机构)申请数字证书时提交的 “标准化申请表”」—— 由证书申请者(比如你生成服务器 / 客户端证书时)用自己的私钥生成,包含申请者的公钥 + 身份信息(如国家、公司、域名等),且会用申请者的私钥对这些信息签名,确保内容不可篡改。
- 身份信息(Subject) :你在
server.conf里配置的C/ST/L/O/CN等,CA 会核验这些信息(自签名 CA 可跳过核验,商用 CA 会严格审核); - 公钥(Subject Public Key Info) :申请者自己生成的私钥对应的公钥(比如
server.key对应的公钥)—— 这是 CSR 的核心,CA 签发的证书会直接嵌入这个公钥; - 签名(Signature) :申请者用自己的私钥(
server.key)对 “身份信息 + 公钥” 做的签名 ——CA 验证这个签名,就能确认 “CSR 中的公钥确实属于申请者”(因为只有私钥持有者能生成这个签名)。
- 身份信息(Subject) :你在
详细的证书生成配置文件和脚本见git仓库。
基于TCP的服务器和客户端TLS验证代码
基本流程
- 初始化openssl库
- 创建并配置openssl上下文(其中会导入证书链)
- 创建tcp socket
- 创建ssl对象绑定tcp socket
- 使用ssl对象进行tcp通信,其会自动使用之前配置的openssl上下文进行证书验证和加密通信
server.cpp
#include <iostream> #include <string> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <openssl/ssl.h> #include <openssl/err.h>using namespace std;
const int PORT = 8443;
const string CERT_PATH = "crt/mtls_certs/";
const string SERVER_CERT_CHAIN = CERT_PATH + "server_chain.crt";
const string SERVER_PRIVATE_KEY = CERT_PATH + "server.key";
const string CA_CERT = CERT_PATH + "ca.crt"; // 用于验证客户端证书// 初始化OpenSSL库
void init_openssl() {
OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
}// 创建并配置SSL上下文
SSL_CTX* create_context() {
const SSL_METHOD* method = TLS_server_method();
SSL_CTX* ctx = SSL_CTX_new(method);
if (!ctx) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}// 加载服务器证书链和私钥 if (SSL_CTX_use_certificate_chain_file(ctx, SERVER_CERT_CHAIN.c_str()) <= 0) { ERR_print_errors_fp(stderr); exit(EXIT_FAILURE); } if (SSL_CTX_use_PrivateKey_file(ctx, SERVER_PRIVATE_KEY.c_str(), SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); exit(EXIT_FAILURE); } // 加载根CA证书(用于验证客户端证书) if (SSL_CTX_load_verify_locations(ctx, CA_CERT.c_str(), nullptr) <= 0) { ERR_print_errors_fp(stderr); exit(EXIT_FAILURE); } // 要求客户端提供证书 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); return ctx;}
// 创建TCP服务器套接字
int create_socket() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(PORT); addr.sin_addr.s_addr = INADDR_ANY; if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); } if (listen(sockfd, 10) < 0) { perror("listen failed"); exit(EXIT_FAILURE); } cout << "Server listening on port " << PORT << endl; return sockfd;}
int main() {
init_openssl();
SSL_CTX* ctx = create_context();
int sockfd = create_socket();while (true) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len); if (client_sockfd < 0) { perror("accept failed"); continue; } cout << "Client connected: " << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << endl; // 创建SSL对象并绑定客户端套接字 SSL* ssl = SSL_new(ctx); SSL_set_fd(ssl, client_sockfd); // 执行TLS握手(会验证客户端证书) if (SSL_accept(ssl) <= 0) { ERR_print_errors_fp(stderr); cout << "TLS handshake failed (client certificate invalid?)" << endl; } else { cout << "TLS handshake succeeded (mutual authentication passed)" << endl; // 发送数据给客户端 const string response = "Hello from mTLS server!"; SSL_write(ssl, response.c_str(), response.length()); cout << "Sent to client: " << response << endl; // 接收客户端数据 char buffer[1024] = {0}; int bytes_read = SSL_read(ssl, buffer, sizeof(buffer)); if (bytes_read > 0) { cout << "Received from client: " << string(buffer, bytes_read) << endl; } } // 关闭连接 SSL_shutdown(ssl); SSL_free(ssl); close(client_sockfd); cout << "Client disconnected" << endl; } close(sockfd); SSL_CTX_free(ctx); return 0;
}
client.cpp
#include <iostream> #include <string> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <openssl/ssl.h> #include <openssl/err.h>using namespace std;
const string SERVER_IP = "127.0.0.1";
const int PORT = 8443;
const string CERT_PATH = "crt/mtls_certs/";
const string CLIENT_CERT_CHAIN = CERT_PATH + "client_chain.crt";
const string CLIENT_PRIVATE_KEY = CERT_PATH + "client.key";
const string CA_CERT = CERT_PATH + "ca.crt"; // 用于验证服务器证书// 初始化OpenSSL库
void init_openssl() {
OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
}// 创建并配置SSL上下文
SSL_CTX* create_context() {
const SSL_METHOD* method = TLS_client_method();
SSL_CTX* ctx = SSL_CTX_new(method);
if (!ctx) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}// 加载客户端证书链和私钥 if (SSL_CTX_use_certificate_chain_file(ctx, CLIENT_CERT_CHAIN.c_str()) <= 0) { ERR_print_errors_fp(stderr); exit(EXIT_FAILURE); } if (SSL_CTX_use_PrivateKey_file(ctx, CLIENT_PRIVATE_KEY.c_str(), SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); exit(EXIT_FAILURE); } // 加载根CA证书(用于验证服务器证书) if (SSL_CTX_load_verify_locations(ctx, CA_CERT.c_str(), nullptr) <= 0) { ERR_print_errors_fp(stderr); exit(EXIT_FAILURE); } return ctx;}
// 连接到服务器
int connect_to_server() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); if (inet_pton(AF_INET, SERVER_IP.c_str(), &server_addr.sin_addr) <= 0) { perror("invalid server address"); exit(EXIT_FAILURE); } if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("connection failed"); exit(EXIT_FAILURE); } cout << "Connected to server: " << SERVER_IP << ":" << PORT << endl; return sockfd;}
int main() {
init_openssl();
SSL_CTX* ctx = create_context();
int sockfd = connect_to_server();// 创建SSL对象并绑定套接字 SSL* ssl = SSL_new(ctx); SSL_set_fd(ssl, sockfd); // 执行TLS握手(会验证服务器证书) if (SSL_connect(ssl) <= 0) { ERR_print_errors_fp(stderr); cout << "TLS handshake failed (server certificate invalid?)" << endl; } else { cout << "TLS handshake succeeded (mutual authentication passed)" << endl; // 发送数据给服务器 const string request = "Hello from mTLS client!"; SSL_write(ssl, request.c_str(), request.length()); cout << "Sent to server: " << request << endl; // 接收服务器数据 char buffer[1024] = {0}; int bytes_read = SSL_read(ssl, buffer, sizeof(buffer)); if (bytes_read > 0) { cout << "Received from server: " << string(buffer, bytes_read) << endl; } } // 关闭连接 SSL_shutdown(ssl); SSL_free(ssl); close(sockfd); SSL_CTX_free(ctx); return 0;
}
验证证书链过程中存在的坑
openssl命令验证证书
openssl verify -CAfile ca.crt client_chain.crt
-
ca.crt就是根ca证书
-
client_chain.crt是中间ca证书+终端证书,但是要想上面这条命令验证通过,client_chain.crt的顺序必须是中间ca证书在前,终端证书在后。即cat intermddiate.crt client.crt > client_chain.crt
我们也可以选择下面这条证书验证命令,这样就可以确保在证书正确的情况下,验证通过
openssl verify -CAfile ca.crt -untrusted intermediate.crt client.crt
openssl函数验证证书
然而,cat intermediate.crt client.crt > client_chain.crt生成的证书链可以通过openssl verify -CAfile ca.crt client_chain.crt的验证,却不能通过openssl库函数的验证,该函数如下:
SSL_CTX_use_certificate_chain_file(ctx, CLIENT_CERT_CHAIN.c_str())
该函数接受的证书链,要求是终端证书在前,中间ca证书在后,即
cat client.crt intermediate.crt > client_chain.crt
TLS在MQTT中的应用
以下例子基于mosquitto22.0.18,openssl3.0.13-0ubuntu3.6
mosquitto 命令行(验证 TLS 双向认证)
前提条件:
- 证书和私钥,包括“ca证书,中间ca证书,服务器和客户端终端证书,服务器和客户端私钥”
-
mtls.conf,启动mosquitto服务器的配置项,文件名自取,但是路径固定在/etc/mosquitto/conf.d/ -
acl.conf,ACL(access control list),配置哪些用户能访问哪些主题(topic),文件名和路径都不固定,但是在mtls.conf中要指定该文件路径
证书
-
ca.crt, intermediate.crt, client.crt, server.crt, client.key, server.key的创建如上一节证书生成步骤所示,但是这里需要创建一个ca_chain.crt,原因是mosquitto命令中并不支持“根ca+中间ca+终端证书”三个参数,因此要将“根ca+中间ca”合为一个根证书链。对该根证书链中两个证书的顺序有严格要求,生成命令如下:cat intermediate.crt ca.crt > ca_chain.crt -
可以使用
openssl verify -CAfile ca_chain.crt /etc/mosquitto/certs/client.crt验证证书链 -
我们这里把证书和私钥都放到
/etc/mosquitto/certs/
配置文件
mtls.conf
我们把mtls.conf放在/etc/mosquitto/conf.d/
# 核心:开启listener配置独立(2.0+必须加,否则allow_anonymous不生效) per_listener_settings true设置监听地址和端口
listener 8883 0.0.0.0
=ACL配置===
允许匿名:不允许。不允许匿名,则必须配置acl
allow_anonymous false
使用证书CN作为MQTT用户名
use_identity_as_username true
指定ACL配置文件路径
acl_file /etc/mosquitto/acl.conf
=TLS配置===
证书路径
cafile /etc/mosquitto/certs/ca_chain.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key强制证书(关闭则单向认证,否则双向认证)
require_certificate true
日志全开,便于确认配置生效
log_dest stdout
log_type all
层面 作用 配置载体 TLS 双向认证 验证 “客户端是不是合法身份”(身份验证) cafile/certfile等ACL 授权 验证 “合法身份能操作哪些主题”(权限管控) ACL 文件 -
查看证书CN(也就是用户名)命令
# 命令 openssl x509 -in client.crt -noout -subject # 正常输出 subject=C = CN, ST = Beijing, L = Beijing, O = My Company, OU = Client Department, CN = client.user # 根据use_identity_as_username true,使用client.crt的客户端用户名为client.user,来自于CN
acl.conf
放在/etc/mosquitto/
# 客户端用户名(根据上面配置,就是客户端证书的CN) user client.user # 允许订阅+发布所有主题,#是通配符 topic readwrite #兜底规则,拒绝所有其他用户
user *
拒绝访问所有主题(#是通配符,匹配所有)
topic deny #
-
#在行开头,表示注释符号,在行内,则为通配符。
命令
# 启动mosquitto服务,指定配置文件,并且打印日志,mosquitto.conf包含了所有conf.d路径下的配置文件 sudo mosquitto -c /etc/mosquitto/mosquitto.conf -v启动订阅客户端,订阅指定主题,并且指定使用的客户端证书和私钥
mosquitto_sub -h 127.0.0.1 -p 8883 -t "test/topic" --cafile /etc/mosquitto/certs/ca_chain.crt --cert /etc/mosquitto/certs/client.crt --key /etc/mosquitto/certs/client.key -v
启动发布客户端,向指定主题发布信息,并且指定使用的客户端证书和私钥
mosquitto_pub -h 127.0.0.1 -p 8883 -t "test/topic" -m "试试就试试" --cafile /etc/mosquitto/certs/ca_chain.crt --cert /etc/mosquitto/certs/client.crt --key /etc/mosquitto/certs/client.key
查看服务是否启动
sudo netstat -tulpn | grep mosquitto输出
tcp 0 0 0.0.0.0:8883 0.0.0.0:* LISTEN 611122/mosquitto
程序中使用mqsquitto+tls
注:编码只是实现订阅和发布客户端,mqtt服务配置及启动如上一节所示。其中客户端和服务端的证书需要匹配。【证书生成脚本会生成匹配的服务端和客户端的证书及私钥,服务端配置使用生成的服务端证书和密钥,客户端中使用生成的客户端证书及密钥】
https://gitcode.com/KindleDawn/MqttTls
公钥和私钥
公钥和私钥是非对称加密算法的核心,也是现代网络安全(如加密通信、数字签名、区块链等)的基础,核心特点是 “一对密钥、分工明确、无法互推”。
一、核心定义与本质
- 密钥对:公钥和私钥是通过加密算法(如 RSA、ECC)生成的一对数学相关的字符串,生成时绑定,无法单独存在。
- 私钥:又称 “秘密密钥”,是用户绝对保密的核心密钥,需存储在安全设备(如本地硬盘、加密芯片、硬件钱包)中,绝不对外泄露。
- 公钥:又称 “公开密钥”,可自由对外分发(如发布在网站、区块链地址、密钥服务器),任何人都可获取,无保密要求。
二、核心工作原理:“加密 - 解密” 与 “签名 - 验证”
非对称加密的核心价值是解决 “对称加密需共享密钥” 的安全隐患,主要有两大核心用途:
1. 加密通信(公钥加密,私钥解密)
用于传递敏感信息(如密码、文件),确保只有接收方才能解密:
- 发送方:获取接收方的公钥,用公钥对明文(原始信息)加密,生成无法直接读取的密文;
- 接收方:用自己唯一的私钥对密文解密,还原为明文;
- 关键:即使密文和公钥被第三方截取,没有私钥也无法解密(破解难度相当于分解极大的质数,目前算力无法实现)。
2. 数字签名(私钥签名,公钥验证)
用于确认信息完整性和发送方身份(防篡改、防伪造),常见于合同签署、软件校验、区块链交易:
- 发送方:用自己的私钥对信息(或信息的哈希值)进行 “签名”,生成签名数据,与明文一起发送;
- 接收方:获取发送方的公钥,用公钥验证签名 —— 若验证通过,说明信息未被篡改,且确实是该私钥持有者发送;
- 关键:私钥唯一对应发送方,他人无法伪造签名;信息一旦篡改,签名验证会直接失败。
三、公钥与私钥的核心区别
维度 公钥 私钥 保密性 公开可见,无需保密 绝对保密,严禁泄露 用途 加密信息、验证签名 解密信息、生成签名 生成与推导 由私钥通过算法生成 算法直接生成,无法由公钥反推 持有对象 多人持有(接收方 / 验证方) 唯一持有(发送方 / 签名方) 安全要求 低(公开无风险) 极高(泄露则信息 / 身份不安全) ↩ mosquitto【MQTT消息代理软件】
mosquitto简述
概述
mosquitto是一款开源的MQTT消息代理(服务器)软件,实现了MQTT协议版本3.1和3.1.1,提供轻量级的,支持可发布/可订阅的的消息推送模式。
API:mosquitto.h
安装
sudo apt install mosquitto # 安装服务端 sudo apt install mosquitto-clients # 安装客户端 sudo apt-get install libmosquitto-dev # C风格开发依赖包 sudo apt-get install libmosquittopp-dev # C++风格封装的libmosquitto开发包服务端指令
-
查看服务状态
sudo service mosquitto status -
启动服务器
sudo service mosquitto start -
关闭服务器
sudo service mosquitto stop -
启动服务器并实时显示所有日志
mosquitto -v -
根据指定的配置文件启动服务器
mosquitto -c /etc/mosquitto/mosquitto.conf -d-
-c: 指定配置文件 -
-d: 后台运行
-
-
指定端口启动服务器,默认端口是1883,最多指定10次
mosquitto -p 1884
配置文件
/etc/mosquitto/mosquitto.conf# 消息持久存储 persistence true persistence_location /var/lib/mosquitto/日志文件
log_dest file /var/log/mosquitto/mosquitto.log
其他配置
include_dir /etc/mosquitto/conf.d
禁止匿名访问
allow_anonymous false
认证配置,即登录账号信息的文件
password_file /etc/mosquitto/pwfile
权限配置
acl_file /etc/mosquitto/aclfile
监听的端口
listener 1883
客户端指令
订阅主题
mosquitto_sub -t topic发布主题
mosquitto_pub -t topic -m '消息'Mosquitto库编程(C风格)
发布信息客户端
mqtt_pub.c#include <stdio.h> #include <stdlib.h> #include <mosquitto.h> #include <string.h>define HOST "127.0.0.1"
define PORT 1883
define KEEP_ALIVE_TIME 60
define MSG_MAX_SIZE 512
bool session = true;
int main(void)
{
int err = 0;
printf("mqtt publish init ...\n");
struct mosquitto* mosq = NULL;
char buff[MSG_MAX_SIZE];// 初始化 err = mosquitto_lib_init(); if(err<0) { printf("mosquitto lib int fail..."); return -1; } // 创建客户端 mosq = mosquitto_new(NULL, session, NULL); if(mosq==NULL) { printf("create client failed...\n"); err = -1; mosquitto_lib_cleanup(); return -1; } // 客户端连接broker err = mosquitto_connect(mosq, HOST, PORT, KEEP_ALIVE_TIME); if(err<0) { printf("connect fail"); mosquitto_destroy(mosq); return -1; } // 启动事件循环(启动独立线程处理mosquitto事件) err = mosquitto_loop_start(mosq); if(err!=MOSQ_ERR_SUCCESS) { printf("mosquitto loop error\n"); mosquitto_disconnect(mosq); return -1; } // 发布信息到test主题 strncpy(buff, "hello world!", 13); mosquitto_publish(mosq, NULL, "test", strlen(buff)+1, buff, 0, 0); mosquitto_disconnect(mosq); mosquitto_loop_stop(mosq, true); mosquitto_destroy(mosq); mosquitto_lib_cleanup(); return 0;}
订阅信息客户端
mqtt_sub.c#include <stdio.h> #include <stdlib.h> #include <mosquitto.h> #include <string.h>define HOST "127.0.0.1"
define PORT 1883
define KEEP_ALIVE 60
bool session = true;
// 订阅主题成功时回调
void mqtt_subscribe_callback(struct mosquitto *mosq,
void *userdata, int mid, int qos_count, const int *granted_qos)
{
int i;
printf("subscribed (mid: %d): %d", mid, granted_qos[0]);
for(i=1; i < qos_count; i++){
printf(", %d", granted_qos[i]);
}
printf("\n");
}//消息回调函数,收到订阅的消息后调用
void mqtt_message_callback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message)
{
if (message->payloadlen){
printf("%s: %s \n", message->topic, (char *)message->payload);
}else{
printf("%s (null)\n",message->topic);
}
}//mqtt连接回调
void mqtt_connect_callback(struct mosquitto *mosq, void *userdata, int result)
{
int ret;
if (!result){
ret = mosquitto_subscribe(mosq, NULL, "test", 2);
if(ret < 0){
printf("Subscription failed\n");
}else{
printf("Subscription succeeded\n");
}
}else{
printf("connect failed\n");
}
}//日志回调函数
void mqtt_log_callback(struct mosquitto *mosq, void *userdata, int level, const char *str)
{
printf("log__ %s\n", str);
}int main(void)
{int err = 0; printf("mqtt client init...\n"); struct mosquitto *mosq = NULL; //libmosquitto 库初始化 err = mosquitto_lib_init(); if (err < 0){ printf("mosquitto lib int fail..."); return err; } //创建mosquitto客户端 mosq = mosquitto_new(NULL,session,NULL); if (mosq == NULL){ printf("create client failed...\n"); err = -1; mosquitto_lib_cleanup(); return err; } //设置回调函数 mosquitto_log_callback_set(mosq, mqtt_log_callback); mosquitto_connect_callback_set(mosq, mqtt_connect_callback); mosquitto_message_callback_set(mosq, mqtt_message_callback); mosquitto_subscribe_callback_set(mosq, mqtt_subscribe_callback); //客户端连接服务器 err = mosquitto_connect(mosq, HOST, PORT, KEEP_ALIVE); if (err < 0){ printf("connect fail"); mosquitto_destroy(mosq); return err; } //启动事件循环,永久阻塞 err = mosquitto_loop_forever(mosq, -1, 1); if (err < 0){ printf("mosquitto loop fail"); mosquitto_disconnect(mosq); return err; } mosquitto_disconnect(mosq); mosquitto_loop_stop(mosq, false); mosquitto_destroy(mosq); mosquitto_lib_cleanup(); return 0;}
编译
CMakeLists.txtcmake_minimum_required(VERSION 3.16)project(mosquitto_demo)
find_package(PkgConfig REQUIRED)
pkg_check_modules(MOSQ libmosquitto)
set(PUB_SOURCES mqtt_pub.c)
set(SUB_SOURCES mqtt_sub.c)add_executable(subclient ${SUB_SOURCES})
add_executable(pubclient ${PUB_SOURCES})target_include_directories(subclient PRIVATE ${MOSQ_INCLUDE_DIRS})
target_link_libraries(subclient PRIVATE ${MOSQ_LIBRARIES})target_include_directories(pubclient PRIVATE ${MOSQ_INCLUDE_DIRS})
target_link_libraries(pubclient PRIVATE ${MOSQ_LIBRARIES})
Mosquittopp库编程(C++风格)
订阅信息客户端
subclient.cc#include<mosquittopp.h> #include<iostream> #include<string>const char* topic = "topic1";
const char* host = "127.0.0.1";
const int port = 1883;
const int alivetime = 60;class MqttSubClient:public mosqpp::mosquittopp
{
public:
MqttSubClient(const char* id):mosquittopp(id){}
void on_connect(int rc) override;
void on_disconnect(int rc) override;
void on_subscribe(int mid, int qos_count, const int* granted_qos) override;
void on_message(const struct mosquitto_message* message) override;
};// 连接回调函数
void MqttSubClient::on_connect(int rc)
{
if(rc == MOSQ_ERR_SUCCESS)
{
std::cout<<"connect success!"<<std::endl;
// 订阅主题
subscribe(nullptr, topic, 1);
}
else
{
std::cerr<<"connect failed!"<<std::endl;
}
}// 断开连接回调函数
void MqttSubClient::on_disconnect(int rc)
{
std::cout<<"disconnect success!"<<std::endl;
}// 订阅成功回调函数
void MqttSubClient::on_subscribe(int mid, int qos_count, const int* granted_qos)
{
std::cout<<"订阅 mid: "<<mid<<" success!"<<std::endl;
}// 消息处理回调函数
void MqttSubClient::on_message(const struct mosquitto_message* message)
{
bool match = false;
mosqpp::topic_matches_sub(topic, message->topic, &match);
if(match)
{
std::string recv(static_cast<char*>(message->payload), message->payloadlen);
std::cout<<"来自"<<message->topic<<"的消息:"<<recv<<" (mid: "<<message->mid<<")"<<std::endl;
}
}int main()
{
// 初始化
mosqpp::lib_init();
MqttSubClient subclient("subclient1");
int rc;
rc = subclient.connect(host, port, alivetime);
if(rc == MOSQ_ERR_ERRNO)
{
std::cout<<"连接错误: "<<mosqpp::strerror(rc)<<std::endl;
}
else if(MOSQ_ERR_SUCCESS == rc)
{
// 启动事件循环,并阻塞
rc = subclient.loop_forever();
}subclient.disconnect(); mosqpp::lib_cleanup(); return 0;}
发布信息客户端
pubclient.cc#include<mosquittopp.h> #include<string> #include<iostream> #include<thread>const char* topic = "topic1";
const char* host = "127.0.0.1";
const int port = 1883;
const int alivetime = 60;class MqttPubClient:public mosqpp::mosquittopp
{
public:
MqttPubClient(const char* id):mosquittopp(id){}
/// @brief 连接broker时回调
/// @param rc 返回码
void on_connect(int rc) override;
/// @brief 断开连接时回调
/// @param rc 返回码
void on_disconnect(int rc) override;
/// @brief 消息发布成功时回调
/// @param mid
void on_publish(int mid) override;
/// @brief 发布消息
/// @param message 要发布的消息
/// @param qos 定义消息传输可靠性 0 1 2
void publish_message(std::string message, int qos);
};void MqttPubClient::on_connect(int rc)
{
if(rc == MOSQ_ERR_SUCCESS)
{
std::cout<<"connect success!\n";
}
else
{
std::cout<<"connect error!";
}
}void MqttPubClient::on_disconnect(int rc)
{
std::cout<<"disconeect!\n";
}void MqttPubClient::on_publish(int mid)
{
std::cout<<"消息发布成功 (mid: "<<mid<<" ), topic: "<<topic<<std::endl;
}void MqttPubClient::publish_message(std::string message, int qos)
{
int ret = publish(nullptr, topic, message.size(), message.c_str(), qos, false);
if(ret != MOSQ_ERR_SUCCESS)
{
std::cerr<<"publish error!"<<mosqpp::strerror(ret)<<"\n";
}
}int main()
{
// 初始化
mosqpp::lib_init();
MqttPubClient publisher("cpp_publisher");int rc = publisher.connect(host, port, alivetime); if(rc != MOSQ_ERR_SUCCESS) { std::cerr<<"connect error!"<<mosqpp::strerror(rc)<<std::endl; return -1; } // 启动异步事件循环 publisher.loop_start(); // 消息发布 std::string msg; std::this_thread::sleep_for(std::chrono::milliseconds(5)); // 两个sleep是为了确保主线程的输出在后台线程的回调函数输出之后 std::cout<<"请输入要发布的消息:"; while(std::cin>>msg) { publisher.publish_message(msg, 1); std::this_thread::sleep_for(std::chrono::milliseconds(5)); std::cout<<"请输入要发布的消息:"; } // 清理资源 publisher.loop_stop(true); publisher.disconnect(); mosqpp::lib_cleanup(); return 0;}
编译
CMakeLists.txtcmake_minimum_required(VERSION 3.16)project(mqtt_cpp_style)
find_package(PkgConfig REQUIRED)
pkg_check_modules(MOSQ libmosquittopp)
set(SUBSRC subclient.cc)
set(PUBSRC pubclient.cc)add_executable(subclient ${SUBSRC})
add_executable(pubclient ${PUBSRC})target_include_directories(subclient PRIVATE ${MOSQ_INCLUDE_DIRS})
target_link_libraries(subclient PRIVATE ${MOSQ_LIBRARIES})target_include_directories(pubclient PRIVATE ${MOSQ_INCLUDE_DIRS})
target_link_libraries(pubclient PRIVATE ${MOSQ_LIBRARIES})
↩
-

浙公网安备 33010602011771号