文章目录
0 前言 2 DDS security标准 2.1 DDS Security Plugins 2.2 DDS:Auth:PKI-DH 3 fastdds代码分析 3.1认证过程 3.1.1 本地身份初始化(加载自己的证书) 3.1.2 A→B:发送证书+DH公钥 3.1.3 B→A:验证A证书 + 回复证书+签名 3.1.3.1 阶段一:校验 3.1.3.2 阶段二:构造 Reply 消息 3.1.4 A处理Reply:验证B证书 + 发送Final 3.1.4.1 阶段一 验证 Replier 身份 3.1.4.2 阶段二 验证 Reply 的完整性和签名 3.1.4.3 阶段三 构造 HandshakeFinal 并签名 3.1.4.4 阶段四 生成 SharedSecret 3.1.5 B处理Final:验证A签名 + 完成认证 3.1.5.1 阶段一:验证 Final 消息字段 3.1.5.2 阶段二:验证 Initiator 的签名 3.1.5.3 阶段三:生成 SharedSecret 3.2 DH协商密钥 3.2.1 密钥协商算法选择 3.2.2 生成临时 DH 密钥对 3.2.3 序列化公钥(用于网络传输) 3.2.4 从字节流重构对方公钥 3.2.5 DH 密钥派生 + SHA-256 4.参考文献
0 前言
遇到以下场景:A同学:最近业务中使用到了DDS协议,如何做通信安全呢B:同学:这个简单,TLS可以做通信安全A同学:我使用的是fast DDS ,业务定义是UDP端口,不是TCP端口B同学:我查阅了以下fast DDS,官网只介绍了TCP→TLS的配置方法,确实没有UDP→DTLS的配置方法A同学:是的呀!如果没有现场的配置,让我们再去研究协议栈,改造协议栈成本太高了。
DDS官网自己定义了一套安全机制。分别是Authentication Service Plugin、AccessControl Service Plugin、Cryptographic Service Plugin.、Logging Service Plugin、Data Tagging Service Plugin.。 本文将针对Authentication Plugin PKI-DH 双向认证与密钥协商实现进行介绍、讨论。
2 DDS security标准
2.1 DDS Security Plugins
DDS Security 规范(OMG DDS Security)采用插件(Plugin)架构设计,将安全功能解耦为多个独立模块。每个插件负责不同的安全能力,开发者可以根据实际需求进行配置或替换。 整体可以分为 身份认证、访问控制、数据加密、日志审计和数据标签 五大类。

五个插件并不是独立工作的,而是形成了一条完整的安全链路:

2.2 DDS:Auth:PKI-DH
整个 DDS:Auth:PKI-DH 的认证过程实际上包含两个阶段:
- 身份认证阶段(Authentication)
所有参与者都信任同一个 CA。 每个 DomainParticipant 持有自己的私钥和由该 CA 签发的 X.509 证书。 双方通过 **ECDSA(或其他数字签名算法)**验证对方证书并对认证消息进行签名,实现双向身份认证,建立身份信任链。 - 密钥协商阶段(Key Establishment)
在身份认证成功后,双方利用 ECDH(或其他密钥协商算法计算得到相同的 Shared Secret(共享密钥)。 该共享密钥随后用于派生会话密钥(Session Key),建立安全的点对点通信信道,并供后续的 AES-GCM 等加密算法保护 RTPS 消息的机密性和完整性。
3 fastdds代码分析
敲黑板:目前fast-dds没有实现 DDS:Auth:PSK,仅仅实现了DDS:Auth:PKI-DH,有就是说必须依赖CA证书的方案进行认证和协商密钥

3.1认证过程
3.1.1 本地身份初始化(加载自己的证书)
PKI-DH 认证流程的 Phase 0(本地初始化)——在握手开始前,加载并验证本端的 PKI 材料(CA、证书、私钥),构建 IdentityHandle,生成 IdentityToken。双方各自独立执行,无网络交互。
ValidationResult_t PKIDH::validate_local_identity(IdentityHandle** local_identity_handle,GUID_t& adjusted_participant_key,const uint32_t /*domain_id*/,const PropertyPolicy& part_props,const GUID_t& candidate_participant_key,SecurityException& exception)
3.1.2 A→B:发送证书+DH公钥
敲黑板:此时Request 不携带签名
Initiator(主动方)构造并发送 HandshakeRequest 消息。这是三次握手的第一条消息,携带 Initiator 的身份信息、DH 公钥和随机数。
ValidationResult_t PKIDH::begin_handshake_request(HandshakeHandle** handshake_handle,HandshakeMessageToken** handshake_message,const IdentityHandle& initiator_identity_handle,IdentityHandle& replier_identity_handle,const CDRMessage_t& cdr_participant_data,SecurityException& exception)
构造的 Request 消息内容。函数按顺序向 HandshakeMessageToken 中添加以下字段。
HandshakeRequest (DDS:Auth:PKI-DH:1.0+Req)│├── c.id ← Initiator 证书(DER 编码字节)├── c.perm ← Initiator 权限凭证(Permissions 文件内容)├── c.pdata ← ParticipantProxyData(含 GUID)├── c.dsign_algo ← 签名算法("RSA-SHA256" 或 "ECDSA-SHA256")├── c.kagree_algo ← 密钥协商算法("DH+MODP-2048-256" 或 "ECDH+prime256v1-CEUM")├── hash_c1 ← 上述 5 个 "c." 字段的 SHA-256 摘要├── dh1 ← Initiator 的临时 DH 公钥(序列化字节)└── challenge1 ← 256 位随机数(防重放)

3.1.3 B→A:验证A证书 + 回复证书+签名
敲黑板:在这个阶段首次出现了签名
Replier(应答方)收到 Request 后,验证 Initiator 身份,生成自身的 DH 密钥对,构造并发送 HandshakeReply 消息(含数字签名)。这是握手协议中信息量最大、验证逻辑最重的一步。
3.1.3.1 阶段一:校验
阶段一:验证 Request 消息逐项校验 Request 中的每个字段 ① 验证消息类型 class_id == “DDS:Auth:PKI-DH:1.0+Req” ② 加载并验证 Initiator 证书 ├── load_certificate(c.id) ├── 校验 SubjectName └── verify_certificate() — CA 链 + CRL 验证 ③ 提取权限凭证 c.perm ④ 验证 c.pdata 中的 GUID 与证书绑定 └── SubjectName 的 SHA-256 前 47 位 == GUID 前缀的特定位 ⑤ 验证签名算法 c.dsign_algo(RSA-SHA256 / ECDSA-SHA256) ⑥ 验证密钥协商算法 c.kagree_algo(DH-2048-256 / ECDH-prime256v1) ⑦ 重算 hash_c1 并与 Request 中的 hash_c1 比对 ⑧ 提取 dh1 和 challenge1(供后续 Reply 消息回传)
3.1.3.2 阶段二:构造 Reply 消息
HandshakeReply (DDS:Auth:PKI-DH:1.0+Reply)││── 身份字段(Replier 自身)├── c.id ← Replier 证书├── c.perm ← Replier 权限凭证├── c.pdata ← Replier ParticipantProxyData├── c.dsign_algo ← Replier 签名算法├── c.kagree_algo ← Replier 密钥协商算法├── hash_c2 ← 上述 5 个 "c." 字段的 SHA-256││── 密钥交换字段├── dh2 ← Replier 的临时 DH 公钥 ★││── 回传字段(来自 Request)├── hash_c1 ← Request 的身份摘要├── dh1 ← Initiator 的 DH 公钥├── challenge1 ← Initiator 的随机数││── Replier 新增├── challenge2 ← Replier 的随机数││── 签名(核心)└── signature ← Sign(Replier私钥,hash_c2 + challenge2 + dh2 +challenge1 + dh1 + hash_c1)
ValidationResult_t PKIDH::begin_handshake_reply(HandshakeHandle** handshake_handle,HandshakeMessageToken** handshake_message_out,HandshakeMessageToken&& handshake_message_in,IdentityHandle& initiator_identity_handle,const IdentityHandle& replier_identity_handle,const CDRMessage_t& cdr_participant_data,SecurityException& exception)



3.1.4 A处理Reply:验证B证书 + 发送Final
Initiator(主动方)收到 Reply 后,验证 Replier 的身份和签名,构造 HandshakeFinal 消息并签名,最后生成 SharedSecret。这是 Initiator 侧的最终处理步骤,也是握手三次消息中逻辑最复杂的一个函数
ValidationResult_t PKIDH::process_handshake_request(HandshakeMessageToken** handshake_message_out,HandshakeMessageToken&& handshake_message_in,PKIHandshakeHandle& handshake_handle,SecurityException& exception)
3.1.4.1 阶段一 验证 Replier 身份
① 验证消息类型 class_id == “DDS:Auth:PKI-DH:1.0+Reply” ② 加载 Replier 证书(c.id) ├── load_certificate() ├── 校验 SubjectName └── verify_certificate() — CA 链 + CRL 验证 ③ 提取权限凭证 c.perm ④ 验证 c.pdata 中的 GUID 与证书绑定 ⑤ 验证签名算法 c.dsign_algo ⑥ 验证密钥协商算法 c.kagree_algo(必须与本端一致)
3.1.4.2 阶段二 验证 Reply 的完整性和签名
⑦ 验证 hash_c2 — Replier 身份字段摘要 └── 重新序列化所有 “c.” 字段 → SHA-256 → 比对 hash_c2
⑧ 提取 dh2 → generate_dh_peer_key() 存为 peer key └── handshake_handle->peerkeys_ = generate_dh_peer_key(dh2, …)
⑨ 回传字段逐一比对(防篡改) ├── hash_c1: Reply中的 == Request原始发出的 ├── dh1: Reply中的 == Request原始发出的 └── challenge1: Reply中的 == Request原始发出的
⑩ 验证签名(核心安全检查) └── 用 Replier 证书公钥验证 Reply 的 signature
3.1.4.3 阶段三 构造 HandshakeFinal 并签名


HandshakeFinal (DDS:Auth:PKI-DH:1.0+Final) │ ├── hash_c1 ← 回传 ├── hash_c2 ← 回传 ├── dh1 ← 回传 ├── dh2 ← 回传 ├── challenge1 ← 回传 ├── challenge2 ← 回传 └── signature ← Sign(Initiator私钥, hash_c1 + challenge1 + dh1 + challenge2 + dh2 + hash_c2)
3.1.4.4 阶段四 生成 SharedSecret
handshake_handle->sharedsecret_ = generate_sharedsecret(handshake_handle->dhkeys_, // 本端完整密钥对(pub_A + priv_A)handshake_handle->peerkeys_, // 对端公钥(pub_B)exception);// 将 challenge1 和 challenge2 存入 SharedSecret(*handle)->data_.emplace_back("Challenge1", challenge1);(*handle)->data_.emplace_back("Challenge2", challenge2);
3.1.5 B处理Final:验证A签名 + 完成认证
Replier(应答方)收到 Final 消息后,验证 Initiator 的签名,确认全部握手参数一致性,最后生成 SharedSecret。这是 Replier 侧的收官函数。
ValidationResult_t PKIDH::process_handshake_reply(HandshakeMessageToken** /*handshake_message_out*/,HandshakeMessageToken&& handshake_message_in,PKIHandshakeHandle& handshake_handle,SecurityException& exception)
3.1.5.1 阶段一:验证 Final 消息字段
① 验证消息类型 class_id == “DDS:Auth:PKI-DH:1.0+Final”
② challenge1 比对 └── Final.challenge1 == Reply 中发出的 challenge1
③ challenge2 比对 └── Final.challenge2 == Reply 中发出的 challenge2
④ hash_c1 比对(可选字段) └── 若存在,Final.hash_c1 == Reply 中的 hash_c1
⑤ hash_c2 比对(可选字段) └── 若存在,Final.hash_c2 == Reply 中的 hash_c2
⑥ dh1 比对(可选字段) └── 若存在,Final.dh1 == Reply 中的 dh1
⑦ dh2 比对(可选字段) └── 若存在,Final.dh2 == Reply 中的 dh2
3.1.5.2 阶段二:验证 Initiator 的签名
// 重组 6 个字段(Initiator 侧的签名顺序)CDRMessage::addUInt32(&cdrmessage, 6);CDRMessage::addBinaryProperty(&cdrmessage, *hash_c1);CDRMessage::addBinaryProperty(&cdrmessage, *challenge1);CDRMessage::addBinaryProperty(&cdrmessage, *dh1);CDRMessage::addBinaryProperty(&cdrmessage, *challenge2);CDRMessage::addBinaryProperty(&cdrmessage, *dh2);CDRMessage::addBinaryProperty(&cdrmessage, *hash_c2);// 用 Initiator 证书公钥验证签名check_sign_sha256(rih->cert_, cdrmessage.buffer, cdrmessage.length, *signature, exception)
3.1.5.3 阶段三:生成 SharedSecret
handshake_handle->sharedsecret_ = generate_sharedsecret(handshake_handle->dhkeys_, // 本端密钥对(pub_B + priv_B)handshake_handle->peerkeys_, // 对端公钥(pub_A,Reply 阶段已存储)exception);// 存入 challenge(*handle)->data_.emplace_back("Challenge1", challenge1->value());(*handle)->data_.emplace_back("Challenge2", challenge2->value());
3.2 DH协商密钥

3.2.1 密钥协商算法选择
密钥协商算法选择 DH+MODP-2048-256 ECDH+prime256v1-CEUM
staticintget_dh_type(const std::string& algorithm){auto raw_alg = algorithm.c_str();if (strcmp(DH_2048_256, raw_alg) == 0){return EVP_PKEY_DH;}else if (strcmp(ECDH_prime256v1, raw_alg) == 0){return EVP_PKEY_EC;}return 0;}
static const char* const DH_2048_256 = "DH+MODP-2048-256";static const char* const ECDH_prime256v1 = "ECDH+prime256v1-CEUM";
3.2.2 生成临时 DH 密钥对
生成密钥对 依赖openssl库
static EVP_PKEY* generate_dh_key(int type, SecurityException& exception){// ── 分支1: ECDH (椭圆曲线) ──if (type == EVP_PKEY_EC) {pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1); // P-256 曲线EVP_PKEY_paramgen(pctx, ¶ms);}// ── 分支2: DH (有限域) ──else if (type == EVP_PKEY_DH) {params = EVP_PKEY_new();DH* dh = DH_get_2048_256(); // RFC 5114 2048-bit 素数 + 256-bit 子群EVP_PKEY_assign(params, dh_type, dh);}// ── 生成密钥对 ──EVP_PKEY* keys = nullptr;EVP_PKEY_CTX* kctx = EVP_PKEY_CTX_new(params, NULL);EVP_PKEY_keygen_init(kctx);EVP_PKEY_keygen(kctx, &keys); // ← 核心:生成 (私钥, 公钥) 对return keys;}
3.2.3 序列化公钥(用于网络传输)
生成的临时密钥对中的公钥提取出来,序列化为字节流,以便通过网络发送给对端。这是握手流程中"公钥交换"步骤的实现。
staticboolstore_dh_public_key(EVP_PKEY* dhkey,int type,std::vector<uint8_t>& buffer,SecurityException& exception)
3.2.4 从字节流重构对方公钥
从对端发来的字节流中反序列化重建对方的 DH/ECDH 公钥,构造出一个仅含公钥的 EVP_PKEY 对象,供后续 generate_sharedsecret() 进行密钥派生使用。
static EVP_PKEY* generate_dh_peer_key(const std::vector<uint8_t>& buffer,SecurityException& exception,int alg_kind)
// DH: 字节数组 → BIGNUM → EVP_PKEYBN_deserialize_raw(&pub_key, buffer, buffer.size(), exception);DH_set0_key(dh, pub_key, NULL);EVP_PKEY_assign(key, type, dh);// ECDH: 字节数组 → EC_KEY → EVP_PKEYEC_KEY_oct2key(ec, pointer, buffer.size(), NULL);EVP_PKEY_assign_EC_KEY(key, ec);
3.2.5 DH 密钥派生 + SHA-256
用本端私钥和对端公钥执行 DH 密钥派生,再经 SHA-256 哈希生成固定 32 字节的 SharedSecret,供后续 AES-GCM-GMAC 加密插件使用。
std::shared_ptr<SecretHandle> PKIDH::generate_sharedsecret(EVP_PKEY* private_key, // 自己的 DH 私钥EVP_PKEY* public_key, // 对方的 DH 公钥SecurityException& exception) const{EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(private_key, NULL);EVP_PKEY_derive_init(ctx); // 初始化密钥派生EVP_PKEY_derive_set_peer(ctx, public_key); // 设置对方公钥EVP_PKEY_derive(ctx, NULL, &length); // 获取派生密钥长度EVP_PKEY_derive(ctx, raw_data, &length); // ← 核心:计算 DH 共享密钥// (数学: g^(a*b) mod p)EVP_Digest(raw_data, length, md, // ← SHA-256 哈希NULL, EVP_sha256(), NULL); // 得到 32 字节 SharedSecretdata.value().assign(md, md + 32);// 存入 SharedSecretHandle}
4.参考文献
fast dds https://fast-dds.docs.eprosima.com/en/latest/02-formalia/titlepage.html
DDS-SECURITY https://www.omg.org/spec/DDS-SECURITY/1.2/About-DDS-SECURITY
fast dds git https://github.com/eProsima/Fast-DDS/tree/master
夜雨聆风