news 2026/11/1 18:42:06

DLMS/COSEM 蓝皮书解读(二十七):COSEM data protection(class_id = 30)—— 给“数据本身“加认证/加密/签名,而非只保护通道

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DLMS/COSEM 蓝皮书解读(二十七):COSEM data protection(class_id = 30)—— 给“数据本身“加认证/加密/签名,而非只保护通道

DLMS/COSEM 蓝皮书解读(二十七):COSEM data protection(class_id = 30)—— 给"数据本身"加认证/加密/签名,而非只保护通道

系列说明:本系列基于 DLMS UA《Blue Book(蓝皮书)第 16 版 · 第 2 部分》,一个接口类一篇。第 26 篇讲了Auto connect(class_id = 29),它解决的是"电能表何时、用什么方式主动拨出去建立连接"。本篇的COSEM data protection(class_id = 30)解决的则是更底层的问题:数据在写入/读出/推送/调用方法之前,如何被认证、加密或签名,让敏感数据即使经过客户端也保持机密,且第三方签名可防抵赖。

上篇回顾:前面的Modem configuration/Auto answer/Auto connect(27/28/29)把"怎么连出去"讲完了;但连出去之后传的到底是什么、能不能被人偷看或篡改,是另一个维度的问题。本篇就是 DLMS 里专门做"数据级安全"的那个类。


0. 为什么需要这个类 —— 通道保护与数据保护是两回事

做主站集成时容易有个误区:以为"上了 TLS / 链路层加密,数据就安全了"。其实那只是保护 xDLMS APDU 这条通道(蓝皮书称为protection on xDLMS APDUs)。但有些场景通道保护覆盖不到:

  • 第三方要读服务器里的敏感数据,却又不想让中间客户端看见明文。比如电力公司把燃气数据放在表里,燃气公司这个"第三方"通过电力公司这台客户端来读——通道对客户端是解密的,但客户端不该看到燃气明文。
  • 对端要能证明"这条数据确实是我发的、没被改过"(non-repudiation,防抵赖),需要数字签名。
  • 某些属性/方法要单独加一层认证或加密,而不是整条 APDU 都加。

蓝皮书原文(COSEM data protection, Overview):
“Instances of this IC allow applying cryptographic protection on COSEM data i.e. on attribute values and method invocation and return parameters. This is achieved by accessing attributes and/or methods of other COSEM objects indirectly through instances of the ?Data protection? interface class that provide the necessary mechanisms and parameters to apply / verify / remove protection on COSEM data.”

关键点:你不是直接去 GET 那个敏感属性,而是通过一个Data protection对象间接地读写它——这个中间对象负责加/验/拆保护。它支持认证(authentication)、加密(encryption)、数字签名(digital signature)任意组合、且可分层叠加。


1. 类蓝图

Data protection 0…n class_id = 30, version = 0
属性静态/动态数据类型MinMaxDefShort name
logical_namestaticoctet-stringx
protection_bufferdyn.octet-stringx + 0x08
protection_object_liststaticarrayx + 0x10
protection_parameters_getstaticarrayx + 0x18
protection_parameters_setstaticarray0x20
required_protectionstaticenumx + 0x28

注:protection_parameters_set的 Short name 原文写为x + 0x20(其余按x + 0x08步进),此处照原文保留。

方法必选/可选 (m/o)Short name
get_protected_attributes (data)mx + 0x30
set_protected_attributes (data)mx + 0x38
invoke_protected_method (data)mx + 0x40

只有 version = 0,无多版本差异。


2. 属性逐条解读

2.1 logical_name

标识本Data protection对象实例(6 字节 OBIS),与其他对象一致。

2.2 protection_buffer(dyn.)

受保护数据的容器,是"读/写保护数据"这条路径的核心。

蓝皮书原文(protection_buffer):
“When read, the attributes determined by protection_object_list are captured then protection is applied according to protection_parameters_get. When written, the protected Data is put into the protection_buffer, then the protection is verified / removed according to protection_parameters_set and the attributes determined by protection_object_list are set.”

也就是说:

  • 读protection_buffer:按protection_object_list抓属性 → 按protection_parameters_get加保护 → 结果放进protection_buffer返回(也可被Profile generic捕获、被Push setup推送)。
  • 写protection_buffer:把受保护数据写进来 → 用protection_parameters_set验/拆保护 → 还原出的明文写回protection_object_list指定的属性。

读出来的最终类型是octet-string——即一串已经加好密的字节,接收方自己拿参数去验/解。

2.3 protection_object_list(static)

定义"读保护缓冲时抓哪些属性、写保护缓冲时写哪些属性"。它是array,元素是object_definition:

object_definition ::= structure { class_id: long-unsigned, logical_name: octet-string, attribute_index: integer, data_index: long-unsigned, restriction: restriction_element }
  • attribute_index:指向对象内属性。1=第 1 个属性(即logical_name),2=第 2 个……0=全部公开属性。
  • data_index三档含义:
    • 0x0000= 整个属性;
    • 0x0001–0x0FFF= 复杂属性(structure/array)里的某个元素(首元素序号 1);
    • 0x1000–0xFFFF= 对Profile generic缓冲的选择性访问(按最近时间片/条目+列)。
  • restriction用于Profile generic缓冲的绝对选择性访问(见下)。

两种互斥的选择性访问机制:相对(由data_index控制,相对于当前时间/条目)+绝对(由restriction控制,明确日期或条目区间)。

restriction_element ::= structure { restriction_type: enum: (0) none, (1) restriction by date, (2) restriction by entry restriction_value: CHOICE { null-data, restriction_by_date, restriction_by_entry } } restriction_by_date ::= structure { from_date: octet-string, to_date: octet-string } restriction_by_entry ::= structure { from_entry: double-long-unsigned, to_entry: double-long-unsigned }

蓝皮书原文(restriction_element):
“restriction_element defines absolute selective access to a ?Profile generic? buffer by date range (from_date to to_date) or by entries (from_entry to to_entry)… otherwise, the restriction_type element holds (0) none and the restriction_value element holds null-data. This choice shall be taken also if relative selective access is to be used.”

2.4 protection_parameters_get / protection_parameters_set(static)

分别是"读时加保护"和"写时验/拆保护"所需的全部参数,都是array of protection_parameters_element:

protection_parameters_element ::= structure { protection_type: enum: (0) authentication, (1) encryption, (2) authentication and encryption, (3) digital signature protection_options: structure { transaction_id: octet-string, originator_system_title: octet-string, recipient_system_title: octet-string, other_information: octet-string, key_info: key_info_element } }
  • protection_type:0 认证 / 1 加密 / 2 认证+加密 / 3 数字签名。
  • 四个 title/info 字段都是 A-XDR 编码的octet-string,长度与值会纳入 AAD(Additional Authenticated Data)参与认证。
  • key_info决定接收方怎么拿到正确的密钥(见 2.6)。

蓝皮书原文(protection_parameters_get):
“This attribute is first written by the client. The server may need to fill in some additional elements, in which case this attribute has to be read back by the client… so that they can use these parameters to verify / remove protection.”

——这是实战极易踩的点:get这套参数先由客户端写,服务器可能回填一部分(如 ECDH 公钥),客户端必须再读回来转交给真正的接收第三方。

2.5 required_protection(static, enum)

规定"通过本对象访问的属性值 / 方法调用与返回参数"必须满足的保护等级。按位解释(把 enum 当无符号数看):

Bit含义
0未用,置 0
1未用,置 0
2authenticated request(请求需认证)
3encrypted request(请求需加密)
4digitally signed request(请求需签名)
5authenticated response(响应需认证)
6encrypted response(响应需加密)
7digitally signed response(响应需签名)

例如required_protection = 0x44(二进制0100 0100)= Bit2 + Bit6 = 请求认证 + 响应加密。若方法调用时protection_parameters不满足此要求,调用直接失败。

2.6 key_info(密钥信息选择)

key_info_element ::= structure { key_info_type: enum: (0) identified_key, (1) wrapped_key, (2) agreed_key key_info_options: CHOICE { identified_key_info_options, -- (0) global_unicast_encryption_key / (1) global_broadcast_encryption_key wrapped_key_info_options, -- kek_id: (0) master_key; key_ciphered_data: octet-string agreed_key_info_options -- key_parameters: octet-string; key_ciphered_data: octet-string } }
  • identified_key:用全局密钥(GUEK/GBEK),密钥由系统标题识别。
  • wrapped_key:用主密钥(KEK)包裹传输的随机密钥(key_ciphered_data是被 KEK 包后的密钥)。
  • agreed_key:双方用 ECC CDH 协商密钥(key_parameters标识方案:0x01= C(1e,1s,ECC CDH),0x02= C(0e,2s,ECC CDH);一pass DH 时key_ciphered_data是 U 方临时密钥对公钥 Qu 用其签名私钥签过的值)。

蓝皮书原文(Key_information):
“agreed_key_options … key_parameters: Identifier of the key agreement scheme: 0x01: C(1e, 1s, ECC CDH); 0x02: C(0e, 2s, ECC CDH). All other reserved.”


3. 方法

本类三个方法全部为必选(m),入参都是data ::= integer(0)。

  • get_protected_attributes (data):取object_list指定的属性值,并施加get_protected_attributes_response中的保护参数。
    • 请求:get_protected_attributes_request { object_list: array object_definition, protection_parameters: array protection_parameters_element }
    • 响应:get_protected_attributes_response { protection_parameters: array protection_parameters_element, protected_attributes: octet-string }
  • set_protected_attributes (data):先用protection_parameters验/拆protected_attributes,再把明文写到object_list指定的属性。
    • 请求:set_protected_attributes_request { object_list, protection_parameters, protected_attributes: octet-string }
  • invoke_protected_method (data):先拆protected_method_invocation_parameters,调用object_method指定的远端方法,再对返回参数施加保护。
    • 请求:invoke_protected_method_request { object_method: object_method_definition { class_id, logical_name, method_index }, protection_parameters, protected_method_invocation_parameters: octet-string }
    • 响应:invoke_protected_method_response { protection_parameters, protected_method_return_parameters: octet-string }

蓝皮书原文(invoke_protected_method):
“If the method invoked does not provide return parameters then invoke_protected_method_response shall be a structure, with protection_parameters an array of zero elements and protected_method_return_parameters an octet-string of zero length.”


4. 【实战举例】

示例 1:第三方只读敏感电量,客户端看不到明文

场景:燃气公司(第三方)想读电表里的燃气累积量(某Register,OBIS7-0:1.8.0.255)。电力公司客户端只想做中转。

  1. 建一个Data protection对象,protection_object_list放一条object_definition { class_id=4, logical_name=07 00 01 08 00 FF, attribute_index=2, data_index=0x0000, restriction=(0)none }。
  2. 客户端写protection_parameters_get:protection_type=(1) encryption,recipient_system_title= 燃气公司系统标题,key_info=identified_key / global_unicast_encryption_key。
  3. 客户端 GETprotection_buffer→ 拿到一串加密octet-string,转给燃气公司。
  4. 燃气公司用自己的 GUEK 解密得到明文电量。电力公司客户端全程只见过密文。

示例 2:get_protected_attributes 结构示意(非原文)

构造的请求(A-XDR 风格,示意):

structure { object_list: array[1] of object_definition protection_parameters: array[1] of protection_parameters_element }

响应里protection_parameters会"复制请求的参数 + 服务器可能回填的字段";接收方用响应里的参数去验/解protected_attributes。

示例 3:远端触发一个脚本(invoke_protected_method)

请求里的object_method指向某Script table(class_id=9)的execute方法:

object_method_definition { class_id=9, logical_name=00 00 0A 00 00 FF, method_index=1 }

先拆protected_method_invocation_parameters,执行脚本,再对返回参数加保护回传。这样"谁触发了什么脚本"也受到保护、可审计。

示例 4:required_protection 组合

要求:请求要加密 + 响应要签名 required_protection = Bit3 + Bit7 = 0x08 | 0x80 = 0x88

客户端若只带了认证参数(Bit2),不满足0x88,方法直接失败——这是防止"降级攻击"的关键闸口。

示例 5:agreed_key 的回填陷阱

用agreed_key(key_info_type=2,一pass DH)时:

蓝皮书原文(保护参数表备注):
“When the One-pass Diffie-Hellman c(1e, 1s, ECC CDH) scheme is used, this element is filled by the server and the remote party has to read back protection_parameters_get in order to be able to verify and remove protection.”

即key_ciphered_data是服务器填的,客户端/第三方必须再 GET 一次protection_parameters_get才能拿到协商公钥——不少实现漏了这一步导致解不开。

示例 6:双层保护(认证加密 + 签名)的构造顺序

protection_type支持认证/加密/签名任意组合,且可分层叠加:

蓝皮书原文(NOTE 4):
“In the example shown two layers of protection are applied: the first layer is a combination of compression / encryption / authentication as determined by the Security control byte SC, resulting ©Data; the second layer is digital signature applied to ©Data.”

构造顺序(示意,非蓝皮书原文):

  1. 先按 Security control byte(SC)对原始 Data 做 压缩/加密/认证 → 得到(C)Data;
  2. 再对(C)Data施加数字签名(第三层protection_type=3 digital signature);
  3. 最终octet-string放进protection_buffer。
    接收方逆序:先验证签名,再按 SC 解压/解密/验认证。两层用不同密钥体系(包密钥 vs 签名私钥),安全性更高,但实现也更复杂。

5. 工程上容易踩的坑

  1. 别把"数据保护"和"APDU 保护"混为一谈:通道加密解不开"第三方经客户端读敏感数据"的问题;要端到端保密/防抵赖,必须走Data protection(或下一篇Push setup的push_protection_parameters)。
  2. protection_parameters_set的 Short name:原文此版写作x + 0x20,与相邻属性的x + 0x08步进不一致,写死偏移时以设备对象列表为准、别想当然。
  3. get 参数要"写→读回":客户端先写protection_parameters_get,服务器可能回填(key_ciphered_data、系统标题等),不读回就转交第三方,对方无法验/解保护。
  4. required_protection不满足即失败:安全等级是"闸口"不是"建议"——降级调用会被拒,联调时先确认双方等级一致。
  5. protection_buffer是octet-string密文:主站拿到后别当明文解析,要按protection_parameters走对应的验/解流程。
  6. 密钥随Security setup走:实际主密钥/证书由关联的Association SN/Association LN引用的Security setup持有,本类只放"用哪把密钥"的参数,不存密钥本身。
  7. 调用计数器与密钥绑定:施加保护时对应调用计数器 +1;换密钥时计数器必须归零,否则前后密文计数错乱、旧数据解不开。
  8. restriction与相对访问互斥:用了相对选择性访问(data_index0x1000+),restriction_type必须是 (0) none,否则语义冲突。
  9. 无返回参数的方法:invoke_protected_method返回仍是 structure,protected_method_return_parameters为空octet-string、protection_parameters为空数组,解析时别当成错误。
  10. 双层保护顺序别搞反:示例 6 的"先 SC 层(压缩/加密/认证)得 ©Data,再对 ©Data 签名"是固定次序;接收方必须先验签名再解内层,颠倒会导致连签名都验不了就先解密、泄露结构信息。

6. 小结 & 下期预告

本篇要点:

  1. Data protection(30)给数据本身加认证/加密/签名,区别于通道级 APDU 保护,支持第三方保密与防抵赖。
  2. 两条路径:直接读写protection_buffer,或调用get_/set_protected_attributes、invoke_protected_method。
  3. protection_object_list用object_definition(含data_index、restriction)精确圈定"保护谁";protection_parameters_get/set用protection_parameters_element描述"怎么保护"(类型 + 密钥信息)。
  4. required_protection按位定义请求/响应各自的认证/加密/签名要求,是安全闸口。
  5. 版本只有 v0;三个方法全必选,入参integer(0)。

下一篇(第 28 篇):Push setup(class_id = 40)—— 把本篇的"数据保护"理念和前几篇的"通信/调度"合在一起:主动把数据推给主站/第三方(定时、告警、外部触发皆可),还内建了与本类同款的保护参数(push_protection_parameters)。我们顺着这条线,把"主动上报"这件事一次讲透。


参考资料:DLMS UA《Blue Book Ed.16 Part 2 – COSEM interface classes》,COSEM data protection (class_id = 30, version = 0) 章节。文中属性、数据类型、Short name 偏移(含protection_parameters_set的x + 0x20)、方法与引文均与原文一致;示例中的 OBIS、配置值、A-XDR 片段为帮助理解而构造(实际以设备对象列表为准)。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/1 10:16:07

Java实现RTP客户端:协议解析、音视频组帧与避坑指南

简介:面向Java开发者的RTP实时传输协议学习资料包,内含jlibrtp-0.2.2开源库及其完整示例源码,重点演示了RTP客户端与服务端的实现方式。资源覆盖RTP数据包结构、RTCP控制报文、会话管理(SSRC设置、网络参数配置)等核心…

作者头像 李华
网站建设 2026/10/19 4:01:06

【电机滤波例程3】新息协方差自适应扩展卡尔曼滤波(EKF)原理,MATLAB。PMSM电流测量噪声在线调整。附下载链接

完整代码,附下载链接。包运行成功 文章目录总述实现流程输出说明运行结果MATLAB源代码总述 采用表贴式永磁同步电机模型,满足 LdLqp.L。通过已知αβ电压驱动机电状态预测,以αβ电流为量测修正电流、机械转速和电角度。 扩展卡尔曼滤波利用…

作者头像 李华