网易首页 > 网易号 > 正文 申请入驻

UDS 安全访问服务(0x27)详解及C++实现

0
分享至


1. 服务概述

统一诊断服务(UDS)中的 SecurityAccess 服务(服务ID = 0x27)用于控制对 ECU 内部受保护数据或功能的访问权限。许多诊断操作(如写入数据、执行例程、固件升级等)涉及安全隐患,必须在 ECU 解锁相应安全级别后才能执行。安全访问机制通过“种子-密钥”挑战响应方式防止未授权访问。

典型应用场景

  • 使用 0x2E(写入数据服务)修改非易失性参数

  • 使用 0x31(例程控制)执行敏感操作(如复位、校准)

  • 使用 0x34/0x36/0x37(下载相关服务)刷新固件

2. 服务交互序列

安全访问遵循固定的四步握手流程。下图展示了完整的交互过程(Level 1 为例):

诊断仪(Tester)                          ECU
| |
| [1] 请求种子: 27 01 |
|----------------------------------->|
| |
| [2] 种子响应: 67 01 12 34 56 78 |
|<-----------------------------------|
| |
| [3] 发送密钥: 27 02 9A BC DE F0 |
|----------------------------------->|
| |
| [4] 验证响应: 67 02 |
|<-----------------------------------|
| |
| (解锁成功,可执行受保护服务) |
2.1 子服务规则
  • 请求种子 :子服务为奇数(0x01, 0x03, 0x05, …, 0x7F)

  • 发送密钥 :子服务 = 请求种子子服务 + 1(偶数)

  • 不同子服务对代表不同安全级别(例如 Level 1: 0x01/0x02,Level 2: 0x03/0x04)

  • ECU 可支持多个安全级别,高等级通常提供更多权限

2.2 报文格式

方向

服务 + 子服务

数据字节

请求种子

27 01

无(或可选的数据,如厂商自定义参数)

种子响应

67 01

seed[0..m-1] (m 长度由厂商定义)

发送密钥

27 02

key[0..n-1] (n 长度通常等于种子长度)

密钥验证正响应

67 02

无(或可选的数据,如剩余重试次数)

负响应

7F 27

NRC (1字节)


2.3 负响应码(NRC)

常见 NRC 列表:

  • 0x13 :不正确的消息长度或格式

  • 0x22 :条件不满足(如已经解锁)

  • 0x24 :请求序列错误(例如在未请求种子前直接发送密钥)

  • 0x35 :无效的密钥(key 计算错误)

  • 0x36 :超过最大尝试次数(需要等待延时或上电复位)

2.4 特殊行为
  • 若 ECU 已处于解锁状态,再次收到同一级别的“请求种子”时,应返回 seed 全为 0 的正响应(表示已解锁),并保持解锁状态。

  • 若收到更低或不同级别的请求种子,应根据厂商策略决定是否返回有效 seed 或 NRC。

3. C++ 代码示例

以下示例演示了一个简化但完整的 ECU 端安全访问模块的实现。代码包含:

  • 种子生成器(模拟随机数)

  • 密钥验证算法(示例采用 CRC-8)

  • 状态机管理安全访问序列

  • 处理诊断请求并生成响应

3.1 安全访问类定义

// SecurityAccess.h
#pragma once
#include
#include
#include

enum class SecurityLevel : uint8_t {
Level1 = 0x01, // 奇数子服务代表请求种子
Level2 = 0x03,
Level3 = 0x05
};

// 支持的级别最大数
constexpr uint8_t MAX_SECURITY_LEVEL = 3;

class SecurityAccessManager {
public:
SecurityAccessManager();

// 处理诊断请求报文(包含服务ID和子服务)
// 输入:rawRequest - 完整诊断请求(至少包含服务ID+子服务)
// 输出:响应报文(可能为正响应或负响应)
std::vector processRequest(const std::vector& rawRequest);

// 重置安全访问状态(例如上电或重新通信)
void reset();

// 获取当前解锁状态(调试用)
bool isUnlocked(SecurityLevel level) const;

private:
// 种子生成:根据安全级别生成一段随机字节
std::vector generateSeed(SecurityLevel level);

// 密钥验证算法:示例采用 CRC-8 校验种子 + 固定密钥偏移
bool verifyKey(SecurityLevel level, const std::vector& seed,
const std::vector& key);

// 发送负响应
std::vector buildNegativeResponse(uint8_t requestSid, uint8_t nrc);

// 状态存储
struct SecurityContext {
bool unlocked = false; // 当前级别是否已解锁
std::vector lastSeed; // 最近一次发送的种子
SecurityLevel lastRequestedLevel; // 最近一次请求种子的级别
bool seedRequested = false; // 是否已请求种子等待密钥
uint8_t failCounter = 0; // 连续失败次数
};

SecurityContext contexts_[MAX_SECURITY_LEVEL];
// 将子服务值转换为索引 (0,1,2)
static size_t levelToIndex(SecurityLevel level);
static bool isSeedRequestSubfunc(uint8_t subfunc); // 奇数
static bool isKeySendSubfunc(uint8_t subfunc); // 偶数
static SecurityLevel subfuncToLevel(uint8_t subfunc);
};
3.2 实现文件

// SecurityAccess.cpp
#include "SecurityAccess.h"
#include
#include
#include
#include

// CRC-8 计算(多项式 x^8 + x^2 + x + 1, 初始0x00)
static uint8_t crc8(const std::vector& data) {
uint8_t crc = 0x00;
for (uint8_t byte : data) {
crc ^= byte;
for (int i = 0; i < 8; ++i) {
if (crc & 0x80)
crc = (crc << 1) ^ 0x07;
else
crc <<= 1;
}
}
return crc;
}

SecurityAccessManager::SecurityAccessManager() {
reset();
}

void SecurityAccessManager::reset() {
for (auto& ctx : contexts_) {
ctx.unlocked = false;
ctx.lastSeed.clear();
ctx.seedRequested = false;
ctx.failCounter = 0;
}
}

size_t SecurityAccessManager::levelToIndex(SecurityLevel level) {
// 安全级别映射: 0x01->0, 0x03->1, 0x05->2
return (static_cast(level) - 1) / 2;
}

bool SecurityAccessManager::isSeedRequestSubfunc(uint8_t subfunc) {
return (subfunc & 0x01) == 0x01; // 奇数
}

bool SecurityAccessManager::isKeySendSubfunc(uint8_t subfunc) {
return (subfunc & 0x01) == 0x00; // 偶数
}

SecurityLevel SecurityAccessManager::subfuncToLevel(uint8_t subfunc) {
// 将奇数子服务转换为安全级别枚举
uint8_t odd = subfunc & 0xFE; // 确保是奇数对应的基础值
if (odd == 0x00) odd = 0x01; // 最小级别
return static_cast (odd);
}

std::vector SecurityAccessManager::generateSeed(SecurityLevel level) {
// 使用随机设备生成 4 字节种子(实际长度可配置)
static std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
std::uniform_int_distribution dist(0, 255);
std::vector seed(4);
for (auto& b : seed) b = static_cast(dist(rng));
return seed;
}

bool SecurityAccessManager::verifyKey(SecurityLevel level,
const std::vector& seed,
const std::vector& key) {
// 示例算法:key 应为 seed 的 CRC-8 取反后扩展为4字节
// 实际项目应使用厂商保密的对称或非对称算法(如 AES-CMAC、私有多项式等)
uint8_t expectedCrc = crc8(seed);
uint8_t expectedKeyByte = ~expectedCrc; // 取反作为key的低字节
// 假设key长度为4字节,所有字节应等于 expectedKeyByte(简化演示)
if (key.size() != 4) return false;
for (size_t i = 0; i < key.size(); ++i) {
if (key[i] != expectedKeyByte) return false;
}
return true;
}

std::vector SecurityAccessManager::buildNegativeResponse(uint8_t requestSid, uint8_t nrc) {
return {0x7F, requestSid, nrc};
}

std::vector SecurityAccessManager::processRequest(const std::vector& req) {
// 最小长度: 2字节 (服务ID + 子服务)
if (req.size() < 2) {
return buildNegativeResponse(0x27, 0x13); // 长度错误
}

uint8_t sid = req[0];
if (sid != 0x27) {
// 非本服务消息,应由上层分派,此处返回不支持
return buildNegativeResponse(sid, 0x11);
}

uint8_t subfunc = req[1];
// 处理请求种子(奇数子服务)
if (isSeedRequestSubfunc(subfunc)) {
SecurityLevel level = subfuncToLevel(subfunc);
size_t idx = levelToIndex(level);
// 检查是否已经解锁该级别
if (contexts_[idx].unlocked) {
// 已解锁: 返回全0种子,同时保持解锁状态
std::vector seedZero(4, 0x00);
std::vector response;
response.push_back(0x67);
response.push_back(subfunc);
response.insert(response.end(), seedZero.begin(), seedZero.end());
return response;
}
// 检查失败次数(超过3次需要延时,这里仅返回NRC)
if (contexts_[idx].failCounter >= 3) {
return buildNegativeResponse(0x27, 0x36); // 超过重试次数
}
// 生成新种子
std::vector newSeed = generateSeed(level);
contexts_[idx].lastSeed = newSeed;
contexts_[idx].lastRequestedLevel = level;
contexts_[idx].seedRequested = true;
// 构建正响应: 67 + subfunc + seed
std::vector response;
response.push_back(0x67);
response.push_back(subfunc);
response.insert(response.end(), newSeed.begin(), newSeed.end());
return response;
}
// 处理发送密钥(偶数子服务)
else if (isKeySendSubfunc(subfunc)) {
// 根据偶数子服务找到对应的请求种子子服务(奇数)
uint8_t seedSubfunc = subfunc - 1;
SecurityLevel level = subfuncToLevel(seedSubfunc);
size_t idx = levelToIndex(level);
// 序列检查: 必须先请求种子
if (!contexts_[idx].seedRequested) {
return buildNegativeResponse(0x27, 0x24); // 序列错误
}
// 密钥数据从req[2]开始
if (req.size() < 2 + contexts_[idx].lastSeed.size()) {
return buildNegativeResponse(0x27, 0x13); // 数据长度错误
}
std::vector key(req.begin() + 2, req.end());
// 验证密钥
if (verifyKey(level, contexts_[idx].lastSeed, key)) {
// 成功:解锁该级别
contexts_[idx].unlocked = true;
contexts_[idx].seedRequested = false;
contexts_[idx].failCounter = 0; // 清空失败计数
// 正响应: 67 + subfunc(无数据)
return {0x67, subfunc};
} else {
// 失败:增加计数,清除种子请求标志
contexts_[idx].failCounter++;
contexts_[idx].seedRequested = false;
return buildNegativeResponse(0x27, 0x35); // 无效密钥
}
}
else {
// 未定义的子服务
return buildNegativeResponse(0x27, 0x12); // 子服务不支持
}
}

bool SecurityAccessManager::isUnlocked(SecurityLevel level) const {
size_t idx = levelToIndex(level);
return contexts_[idx].unlocked;
}
3.3 使用示例(模拟诊断仪与ECU交互)

// main.cpp
#include "SecurityAccess.h"
#include
#include
#include

void printHex(const std::vector& data) {
for (uint8_t b : data) {
std::cout << std::hex << std::setw(2) << std::setfill('0')
<< static_cast(b) << " ";
}
std::cout << std::dec << std::endl;
}

int main() {
SecurityAccessManager ecu;
// 模拟 Level 1 安全访问完整流程
std::cout << "=== 安全访问 Level 1 测试 ===" << std::endl;
// Step 1: 请求种子 (27 01)
std::vector seedReq = {0x27, 0x01};
std::vector seedResp = ecu.processRequest(seedReq);
std::cout << "诊断仪请求种子: "; printHex(seedReq);
std::cout << "ECU 返回种子: "; printHex(seedResp);
// 假设 seedResp 为 [0x67, 0x01, 0x12, 0x34, 0x56, 0x78] (4字节种子)
if (seedResp.size() >= 4 && seedResp[0] == 0x67) {
std::vector seed(seedResp.begin() + 2, seedResp.end());
// Step 2: 诊断仪计算 key(使用与ECU相同的算法)
// 这里模拟计算:CRC8(seed)取反,重复4次
auto calcKey = [](const std::vector& seed) -> std::vector {
uint8_t crc = 0x00;
for (uint8_t b : seed) {
crc ^= b;
for (int i = 0; i < 8; ++i) {
if (crc & 0x80) crc = (crc << 1) ^ 0x07;
else crc <<= 1;
}
}
uint8_t keyByte = ~crc;
return {keyByte, keyByte, keyByte, keyByte};
};
std::vector key = calcKey(seed);
std::vector keyReq = {0x27, 0x02};
keyReq.insert(keyReq.end(), key.begin(), key.end());
std::cout << "诊断仪发送密钥: "; printHex(keyReq);
std::vector verifyResp = ecu.processRequest(keyReq);
std::cout << "ECU 验证结果: "; printHex(verifyResp);
// 检查解锁状态
if (verifyResp.size() == 2 && verifyResp[0] == 0x67 && verifyResp[1] == 0x02) {
std::cout << "解锁成功!当前Level1状态: "
<< (ecu.isUnlocked(SecurityLevel::Level1) ? "已解锁" : "锁定")
<< std::endl;
} else {
std::cout << "解锁失败。" << std::endl;
}
}
// 测试错误密钥
std::cout << "\n=== 错误密钥测试 ===" << std::endl;
{
std::vector seedReq = {0x27, 0x01};
auto seedResp = ecu.processRequest(seedReq);
// 故意发送错误key: 全0x00
std::vector wrongKey = {0x00, 0x00, 0x00, 0x00};
std::vector keyReq = {0x27, 0x02};
keyReq.insert(keyReq.end(), wrongKey.begin(), wrongKey.end());
auto nrcResp = ecu.processRequest(keyReq);
std::cout << "错误密钥响应: "; printHex(nrcResp); // 预期 7F 27 35
}
// 测试序列错误(直接发送密钥)
std::cout << "\n=== 序列错误测试 ===" << std::endl;
{
std::vector keyAlone = {0x27, 0x02, 0xAA, 0xBB, 0xCC, 0xDD};
auto resp = ecu.processRequest(keyAlone);
std::cout << "未请求种子直接发密钥: "; printHex(resp); // 预期 7F 27 24
}
return 0;
}
3.4 编译与运行示例

使用任意 C++17 编译器:

g++ -std=c++17 main.cpp SecurityAccess.cpp -o security_example
./security_example

预期输出(种子随机):

=== 安全访问 Level 1 测试 ===
诊断仪请求种子: 27 01
ECU 返回种子: 67 01 a3 b7 8c 5d
诊断仪发送密钥: 27 02 1d 1d 1d 1d
ECU 验证结果: 67 02
解锁成功!当前Level1状态: 已解锁

=== 错误密钥测试 ===
错误密钥响应: 7F 27 35

=== 序列错误测试 ===
未请求种子直接发密钥: 7F 27 24
4. 总结与注意事项
  1. 算法安全性 :生产环境中应使用强加密算法(如 AES-128-CMAC、HMAC-SHA256)或非对称算法(如 ECDSA),避免简单的 CRC 或 XOR。

  2. 种子长度与熵 :种子应足够长(通常 4~8 字节)且具有不可预测性,建议使用真随机数源(如硬件 RNG)。

  3. 重试机制 :连续失败后应增加延时或锁定一段时间,防止暴力破解。

  4. 状态管理 :不同安全级别通常相互独立,但高等级解锁可能隐式解锁低等级(视厂商策略)。代码示例中采用独立管理,符合多数标准。

  5. 会话层支持 :安全访问状态通常与诊断会话绑定,默认会话(0x01)下解锁状态可能失效,需结合 0x10 服务管理。

上述实现提供了清晰的安全访问服务框架,可作为实际 UDS 协议栈的参考模块。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
看完世界杯第2场,球迷看清了3个不争事实,韩国最擅长踢欧洲球队

看完世界杯第2场,球迷看清了3个不争事实,韩国最擅长踢欧洲球队

侃球熊弟
2026-06-12 11:56:01
菲律宾地震第3天,美日还是没动作,菲外长对华发声,态度很强硬

菲律宾地震第3天,美日还是没动作,菲外长对华发声,态度很强硬

小陆搞笑日常
2026-06-12 10:41:32
一定要大量存钱,2026到2030年现金要比想象中更金贵!

一定要大量存钱,2026到2030年现金要比想象中更金贵!

猫叔东山再起
2026-06-12 08:15:11
鹅腿阿姨用鸭腿翻车成就人大食堂主任:冤屈终于洗白,只卖真鹅腿

鹅腿阿姨用鸭腿翻车成就人大食堂主任:冤屈终于洗白,只卖真鹅腿

蜜桔娱乐
2026-06-11 11:06:32
鲁伊-科斯塔:皇马还没付穆帅1500万欧解约金,但钱随时会到账

鲁伊-科斯塔:皇马还没付穆帅1500万欧解约金,但钱随时会到账

懂球帝
2026-06-12 03:53:31
自古以来,古人见官就跪?宋明两朝并非如此,全民下跪不到三百年

自古以来,古人见官就跪?宋明两朝并非如此,全民下跪不到三百年

寰球经纬所
2026-06-05 21:36:01
审批不看唱功、观众只为情怀买单,如今的演唱会早就变了味道

审批不看唱功、观众只为情怀买单,如今的演唱会早就变了味道

暖心萌阿菇凉
2026-06-11 09:57:51
15个副省级市已明确,浙江2个,江苏仅有1个,湖南、河北1个都无

15个副省级市已明确,浙江2个,江苏仅有1个,湖南、河北1个都无

混沌录
2026-06-01 21:47:13
曾经落地近90万的神车!路虎揽胜极光L跌至17.98万

曾经落地近90万的神车!路虎揽胜极光L跌至17.98万

中国能源网
2026-06-11 10:58:56
人类战争史第一次!10架无人机自己开火打死俄军,全程没人下令

人类战争史第一次!10架无人机自己开火打死俄军,全程没人下令

战域笔墨
2026-06-12 06:06:08
伊朗多地传出爆炸声!伊朗:正评估将马斯克旗下企业列入打击范围,包括“星链”、SpaceX相关设施!特朗普遭以色列和伊朗同时“打脸”

伊朗多地传出爆炸声!伊朗:正评估将马斯克旗下企业列入打击范围,包括“星链”、SpaceX相关设施!特朗普遭以色列和伊朗同时“打脸”

每日经济新闻
2026-06-12 09:20:07
哈特:要特别感谢一下OG 他帮我避免了一辈子的遗憾

哈特:要特别感谢一下OG 他帮我避免了一辈子的遗憾

北青网-北京青年报
2026-06-11 19:51:03
东北林业大学副校长刘守新履新中南林业科技大学党委副书记

东北林业大学副校长刘守新履新中南林业科技大学党委副书记

澎湃新闻
2026-06-12 09:02:28
普京官邸防空塔从7座增至27座,外媒称其外孙已搬进“堡垒”

普京官邸防空塔从7座增至27座,外媒称其外孙已搬进“堡垒”

桂系007
2026-06-11 22:27:41
叶挺一时心软,没能立即枪决的副师长,二十年后竟成为粟裕大敌!

叶挺一时心软,没能立即枪决的副师长,二十年后竟成为粟裕大敌!

浩渺青史
2026-06-12 09:54:01
索马里籍名裁阿尔坦被美拒绝入境,加拿大:我们欢迎他,但……

索马里籍名裁阿尔坦被美拒绝入境,加拿大:我们欢迎他,但……

环球网资讯
2026-06-11 23:05:21
3-1变1-3!都在骂福克斯,但谁注意到文班亚马?3场都因为他输球

3-1变1-3!都在骂福克斯,但谁注意到文班亚马?3场都因为他输球

阿纂看事
2026-06-11 15:06:35
随着韩国2-1逆转捷克,墨西哥2-0南非,世界杯首支出局队基本确定

随着韩国2-1逆转捷克,墨西哥2-0南非,世界杯首支出局队基本确定

小火箭爱体育
2026-06-12 12:19:43
神级操作!利物浦5500万回购青训中卫,已谈妥个人条款

神级操作!利物浦5500万回购青训中卫,已谈妥个人条款

夜白侃球
2026-06-12 10:46:43
身材太性感了!《古墓丽影》新作劳拉获演员力挺

身材太性感了!《古墓丽影》新作劳拉获演员力挺

3DM游戏
2026-06-12 09:09:41
2026-06-12 13:23:00
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
998文章数 348关注度
往期回顾 全部

科技要闻

SpaceX IPO募资750亿美元,马斯克身家万亿

头条要闻

"中医匠人"卖课号称"行走的CT" 自称学技术可挣钱改命

头条要闻

"中医匠人"卖课号称"行走的CT" 自称学技术可挣钱改命

体育要闻

比起总冠军,更大的悬念成了FMVP?

娱乐要闻

与热巴恋情曝光1天,陈飞宇现身

财经要闻

万亿美元顺差背后,透露这些信号

汽车要闻

佟湘北:全新smart#6 自成一派好看更好开

态度原创

游戏
家居
房产
健康
手机

《最终幻想7:重生》Steam版4折史低 多作品优惠

家居要闻

空间微调 移形换境

房产要闻

科城·美林学筑5月领跑崖州湾:成交价、销售套数、转化率三项第一

为什么不建议晚上吃粽子?

手机要闻

苹果升级iOS 27版健康App:卡片布局、评估食物营养等

无障碍浏览 进入关怀版