1. 服务概述
统一诊断服务(UDS)中的 SecurityAccess 服务(服务ID = 0x27)用于控制对 ECU 内部受保护数据或功能的访问权限。许多诊断操作(如写入数据、执行例程、固件升级等)涉及安全隐患,必须在 ECU 解锁相应安全级别后才能执行。安全访问机制通过“种子-密钥”挑战响应方式防止未授权访问。
典型应用场景:
使用 0x2E(写入数据服务)修改非易失性参数
使用 0x31(例程控制)执行敏感操作(如复位、校准)
使用 0x34/0x36/0x37(下载相关服务)刷新固件
安全访问遵循固定的四步握手流程。下图展示了完整的交互过程(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 可支持多个安全级别,高等级通常提供更多权限
方向
服务 + 子服务
数据字节
请求种子
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:超过最大尝试次数(需要等待延时或上电复位)
若 ECU 已处于解锁状态,再次收到同一级别的“请求种子”时,应返回 seed 全为 0 的正响应(表示已解锁),并保持解锁状态。
若收到更低或不同级别的请求种子,应根据厂商策略决定是否返回有效 seed 或 NRC。
以下示例演示了一个简化但完整的 ECU 端安全访问模块的实现。代码包含:
种子生成器(模拟随机数)
密钥验证算法(示例采用 CRC-8)
状态机管理安全访问序列
处理诊断请求并生成响应
3.2 实现文件// 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.3 使用示例(模拟诊断仪与ECU交互)// 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.4 编译与运行示例// 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;
}
使用任意 C++17 编译器:
g++ -std=c++17 main.cpp SecurityAccess.cpp -o security_example
./security_example
预期输出(种子随机):
4. 总结与注意事项=== 安全访问 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
算法安全性 :生产环境中应使用强加密算法(如 AES-128-CMAC、HMAC-SHA256)或非对称算法(如 ECDSA),避免简单的 CRC 或 XOR。
种子长度与熵 :种子应足够长(通常 4~8 字节)且具有不可预测性,建议使用真随机数源(如硬件 RNG)。
重试机制 :连续失败后应增加延时或锁定一段时间,防止暴力破解。
状态管理 :不同安全级别通常相互独立,但高等级解锁可能隐式解锁低等级(视厂商策略)。代码示例中采用独立管理,符合多数标准。
会话层支持 :安全访问状态通常与诊断会话绑定,默认会话(0x01)下解锁状态可能失效,需结合 0x10 服务管理。
上述实现提供了清晰的安全访问服务框架,可作为实际 UDS 协议栈的参考模块。
热门跟贴