培训教育类网站模板营销策划与运营
一、ASN.1 是什么?
ASN.1(Abstract Syntax Notation One),中文名称为抽象语法记法一,是一种描述数据结构与编码规则的国际标准,是一种对数据进行表示、编码、传输和解码的数据格式。数字1被ISO加在ASN的后边,是为了保持ASN的开放性,可以让以后功能更加强大的ASN被命名为ASN.2等,但截止到2026年也仅仅到1。
ASN.1是一种独立于硬件和编程语言的数据描述语言,由ISO和ITU-T联合制定。它的核心目标是解决异构系统间的数据兼容性问题,其核心价值:
- 平台无关性:独立于硬件架构和编程语言
- 双向能力:既能描述数据结构,也能定义编码规则
- 广泛应用:X.509证书、SNMP、5G NAS协议、LDAP等
最常见的包括基本编码规则(BER)、正则编码规则(CER)和非典型编码规则(DER):
二、编码规范
1. 数据类型分类
数据类型 | 描述 | ASN.1定义语法 | 编码规则(标签和结构) |
---|---|---|---|
基本类型 | |||
INTEGER | 整数 | INTEGER | 标签 0x02 (基本类型),BER编码为不定长整数。 |
BOOLEAN | 布尔值 | BOOLEAN | 标签 0x01 ,值为 0 (false)或 0xFF (true)。 |
BIT STRING | 位字符串 | BIT STRING | 标签 0x03 ,包含位数和字节数据。 |
OCTET STRING | 字节字符串 | OCTET STRING | 标签 0x04 ,直接存储字节。 |
NULL | 空值 | NULL | 标签 0x05 ,长度为 0 。 |
OBJECT IDENTIFIER | 对象标识符 | OBJECT IDENTIFIER | 标签 0x06 ,编码为区分编码规则(DER)。 |
REAL | 浮点数 | REAL | 标签 0x09 ,支持多种格式(如十进制、十六进制)。 |
ENUMERATED | 枚举类型 | ENUMERATED { value1, value2, ... } | 标签 0x0A ,编码方式与 INTEGER 相同。 |
字符字符串类型 | |||
UTF8String | UTF-8编码字符串 | UTF8String | 标签 0x0C (Universal 12.04),存储UTF-8字节。 |
PrintableString | 可打印字符(ASCII 32-126) | PrintableString | 标签 0x13 (Universal 19)。 |
IA5String | ASCII字符串 | IA5String | 标签 0x16 (Universal 22)。 |
NumericString | 数字和空格(0-9, 空格) | NumericString | 标签 0x12 (Universal 18)。 |
VisibleString | 可见ASCII字符(32-126) | VisibleString | 标签 0x26 (Universal 26)。 |
GeneralString | 扩展ASCII(ISO 8859-1) | GeneralString | 标签 0x27 (Universal 27)。 |
UniversalString | Unicode UCS-4 | UniversalString | 标签 0x28 (Universal 28)。 |
BMPString | Unicode UCS-2 | BMPString | 标签 0x30 (Universal 30)。 |
时间类型 | |||
UTCTime | UTC时间(格式:YYMMDDHHMMSSZ) | UTCTime | 标签 0x17 (Universal 23)。 |
GeneralizedTime | 扩展时间格式(如:YYYYMMDDHHMMSSZ) | GeneralizedTime | 标签 0x18 (Universal 24)。 |
构造类型 | |||
SEQUENCE | 序列结构(按顺序编码元素) | SEQUENCE { ... } | 标签 0x30 (构造类型,0x16 + 构造标识),元素按顺序编码。 |
SET | 集合结构(元素按特定顺序编码) | SET { ... } | 标签 0x31 (构造类型,0x17 + 构造标识),元素按字典序排列。 |
CHOICE | 选择类型(从多个类型中选一个) | CHOICE { ... } | 根据选中的类型编码,标签由选中类型决定。 |
ANY | 任意类型(直接传递原始编码) | ANY | 标签 0x00 (Universal 00),直接传递原始编码。 |
EXTERNAL | 外部数据(包含编码标识和数据) | EXTERNAL { ... } | 标签 0x0C (Universal 8,构造类型),包含编码标识和数据。 |
INSTANCE OF | 实例类型(根据实例类型编码) | INSTANCE OF Type | 根据实例类型编码,标签由实例类型决定。 |
OPEN TYPE | 开放类型(通常与CHOICE或SEQUENCE结合使用) | [0] IMPLICIT Type | 通过类型标识符动态确定编码方式。 |
说明:
- 标签值:所有标签均为十六进制表示,属于通用类(Universal Class)。
- 构造类型:标签值为基本标签(如
0x16
对应SEQUENCE
)加上构造标识位0x20
,例如SEQUENCE
的编码标签为0x30
(0x16 | 0x20
)。 - 编码规则:BER(基本编码规则)、CER(规范编码规则)、DER(确定编码规则)的标签一致,但编码结构可能不同(如
SET
在 DER 中要求元素按字典序排列)。 - 扩展类型:如
SEQUENCE OF
和SET OF
是构造类型的扩展形式,标签与SEQUENCE
和SET
相同。
此表格覆盖了ASN.1的主要数据类型及其编码规则,适用于大多数应用场景。
2. 编码规则家族
- BER(Basic Encoding Rules):最基础的TLV编码(Tag-Length-Value)
- DER(Distinguished Encoding Rules):BER的子集,保证唯一编码形式(用于数字证书)
- PER(Packed Encoding Rules):针对带宽敏感场景的紧凑编码
三、核心编码规则
1. BER(基本编码规则)
采用TLV(Type-Length-Value)三元组结构:
+----------+----------+-----------------+
| 类型标签 | 长度字段 | 值字段 |
| (1字节) | (1-n字节)| (变长) |
+----------+----------+-----------------+
2. DER(可辨别编码规则)
BER的子集,增加约束:
- 长度字段必须使用确定形式
- 布尔值必须用
0xFF
- 集合元素按标签排序
3. PER(压缩编码规则)
删除TLV元数据,极致压缩:
+-----------------------+
| 纯数据值(无标签信息) |
+-----------------------+
4.实战演示:X.509证书片段解析
我们解析一个X.509证书的片段:30 0D 02 01 0A 01 01 FF 02 01 0B
对应ASN.1定义:
CertFragment ::= SEQUENCE {version INTEGER, -- 值=10isValid BOOLEAN, -- 值=TRUEserial INTEGER -- 值=11
}
四、C++实现ANS.1编码解析
需求内容: 解析以下ASN.1的字符串:
3077021f321171f81a5e0b5b6348549365b60691889e8499e21509634f2cf8e73626b20220611a1f010df10f999ffdfd24b9b458865919ff10d12c77547f74c7f7b5efed7e042078b6643339ba91202a0e005929f9162afa087b78db422f44a09c352bc30dc2cc04101ae5aacfe295350a42b96aa111ffcb94
1.定义TLV结构体
// ======================================
// ASN.1 节点结构体
// ======================================
struct ASN1Node
{unsigned char tag; // 节点类型标识unsigned int length; // 数据长度(字节)std::vector<unsigned char> value; // 原始数据值
};
2.定义解析后的序列结构
// ======================================
// ASN.1 解析结果结构体
// ======================================
struct ParsedSequence
{std::vector<ASN1Node> children; // 序列中的子节点
};
3.解析以下ASN.1串
#include <cctype>
#include <cstdint>
#include <stdexcept>
#include <cctype>
#include <cstddef>
#include <iomanip>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>// ======================================
// ASN.1 节点结构体
// ======================================
struct ASN1Node
{unsigned char tag; // 节点类型标识unsigned int length; // 数据长度(字节)std::vector<unsigned char> value; // 原始数据值
};// ======================================
// ASN.1 解析结果结构体
// ======================================
struct ParsedSequence
{std::vector<ASN1Node> children; // 序列中的子节点
};// ======================================
// 辅助函数:将十六进制字符转换为字节
// ======================================
static unsigned char HexCharToByte(char c)
{if (c >= '0' && c <= '9')return static_cast<unsigned char>(c - '0');if (c >= 'a' && c <= 'f')return static_cast<unsigned char>(c - 'a' + 10);if (c >= 'A' && c <= 'F')return static_cast<unsigned char>(c - 'A' + 10);// 防御:无效输入立即终止throw std::invalid_argument("无效十六进制字符: " + std::string(1, c));
}// ======================================
// 核心函数:十六进制字符串转字节数组
// 符合规范 2.1:显式初始化变量
// 符合规范 3.1:严格边界检查
// ======================================
static std::vector<unsigned char> HexStringToBytes(const std::string& hexStr)
{if (hexStr.length() % 2 != 0){throw std::invalid_argument("十六进制字符串长度必须为偶数");}std::vector<unsigned char> result;result.reserve(hexStr.size() / 2);// 安全处理:逐对字节转换for (size_t i = 0; i < hexStr.length(); i += 2){try{unsigned char highNibble = HexCharToByte(hexStr[i]);unsigned char lowNibble = HexCharToByte(hexStr[i + 1]);result.push_back(static_cast<unsigned char>((highNibble << 4) | lowNibble));}catch (const std::invalid_argument& e){throw std::invalid_argument("位置 " + std::to_string(i) + ": " + e.what());}}return result;
}// ======================================
// 核心函数:解析长度字段(含长格式支持)
// 符合规范 5.5.1:资源操作防御性编程
// ======================================
static unsigned int ParseASN1Length(const std::vector<unsigned char>& data, size_t& currentIndex)
{// 前置条件校验(强制防御编程)if (currentIndex >= data.size()){throw std::out_of_range("索引越界: index=" +std::to_string(currentIndex) +", size=" +std::to_string(data.size()));}unsigned char firstByte = data[currentIndex++];// 处理短格式长度if (firstByte < 0x80){return static_cast<unsigned int>(firstByte);}// 处理长格式长度unsigned int lengthBytes = firstByte & 0x7F;// 长度域防护:防止恶意超长if (lengthBytes > 4){throw std::runtime_error("ASN.1长度字段超过4字节限制: " +std::to_string(lengthBytes));}// 边界检查:确保数据足够if (currentIndex + lengthBytes > data.size()){throw std::out_of_range("长格式长度字段越界: position=" +std::to_string(currentIndex) +", requiredBytes=" +std::to_string(lengthBytes));}unsigned int calculatedLength = 0;for (unsigned int i = 0; i < lengthBytes; ++i){calculatedLength = (calculatedLength << 8) |data[currentIndex++];}// 二次验证:计算结果有效性if (calculatedLength == 0){throw std::logic_error("ASN.1长度不能为零(长格式)");}return calculatedLength;
}// ======================================
// 核心函数:解析单个ASN.1 TLV节点
// 符合规范 3.2:分层异常捕获机制
// 符合规范 4.7:完整记录异常上下文
// ======================================
static ASN1Node ParseTLVNode(const std::vector<unsigned char>& data, size_t& currentIndex
)
{ASN1Node node;// 边界防护if (currentIndex >= data.size()){throw std::out_of_range("TLV解析起始位置越界");}// 1. 读取TAGnode.tag = data[currentIndex++];// 2. 读取长度(自动处理长格式)node.length = ParseASN1Length(data, currentIndex);// 3. 校验值域边界if (currentIndex + node.length > data.size()){throw std::out_of_range("值字段越界: 预期长度=" +std::to_string(node.length) +", 剩余字节=" +std::to_string(data.size() - currentIndex));}// 4. 安全拷贝值数据node.value.resize(node.length);for (size_t i = 0; i < node.length; ++i){node.value[i] = data[currentIndex + i];}currentIndex += node.length; // 更新索引位置return node;
}// ======================================
// 主解析函数:处理SEQUENCE和子节点
// 符合规范 2.5:单一职责原则
// ======================================
static ParsedSequence ParseASN1Sequence(const std::vector<unsigned char>& bytes)
{size_t index = 0;ParsedSequence result;// 1. 解析顶层SEQUENCEASN1Node sequenceNode = ParseTLVNode(bytes, index);// 强类型校验if (sequenceNode.tag != 0x30){throw std::runtime_error("根元素必须为SEQUENCE(0x30),实际为: 0x" +(std::ostringstream{} << std::hex << static_cast<int>(sequenceNode.tag)).str());}// 2. 序列内部解析状态size_t innerIndex = 0;const std::vector<unsigned char>& innerData = sequenceNode.value;// 3. 解析四个子元素for (int expectedCount = 0; expectedCount < 4; ++expectedCount){if (innerIndex >= innerData.size()){throw std::runtime_error("元素数量不足: 预期4个,实际" +std::to_string(result.children.size()));}ASN1Node child = ParseTLVNode(innerData, innerIndex);// 标签合法性校验 (INTEGER/OCTET_STRING)if ((expectedCount < 2 && child.tag != 0x02) ||(expectedCount >= 2 && child.tag != 0x04)){std::string expectedType =(expectedCount < 2) ? "INTEGER(0x02)" : "OCTET_STRING(0x04)";throw std::runtime_error("位置[" + std::to_string(expectedCount) +"]应是" + expectedType +", 实际标签: 0x" +(std::ostringstream{} << std::hex << static_cast<int>(child.tag)).str());}result.children.push_back(child);}// 4. 尾部数据防护if (innerIndex != innerData.size()){throw std::runtime_error("序列尾部存在冗余数据: " +std::to_string(innerData.size() - innerIndex) + "字节");}// 5. 最后验证元素数量if (result.children.size() != 4){throw std::logic_error("解析逻辑错误:元素计数不一致");}return result;
}// ======================================
// 格式化输出函数(符合XML风格)
// 符合规范 8.3:中文注释及结构化文档
// ======================================
static std::string FormatXMLOutput(const ParsedSequence& sequence)
{std::ostringstream xmlStream;xmlStream << "<SEQUENCE>\n";for (size_t i = 0; i < sequence.children.size(); ++i){const ASN1Node& node = sequence.children[i];std::string tagName;// 确定标签名称(强类型匹配)switch (node.tag){case 0x02: tagName = "INTEGER"; break;case 0x04: tagName = "OCTET_STRING"; break;default:// 防御性处理(逻辑应确保不会执行)throw std::logic_error("内部错误:未知节点类型");}// 将二进制值转为大写的十六进制字符串xmlStream << " <" << tagName << ">0x";xmlStream << std::hex << std::uppercase << std::setfill('0');for (const auto& byte : node.value){xmlStream << std::setw(2) << static_cast<unsigned int>(byte);}xmlStream << "</" << tagName << ">\n";}xmlStream << "</SEQUENCE>";return xmlStream.str();
}// ======================================
// 函数: ASN1Dump
// 功能: 将ParsedSequence对象转成格式化字符串列表
// 输入: seq - 解析后的ASN1序列对象
// 输出: 包含所有子节点格式信息的字符串列表
// 规范:
// 1. 每个子节点单独一行
// 2. 每行三个元素空格分隔:
// 当前节点类型 节点长度(十进制) 值(十六进制大写)
// 3. 值数据不带0x前缀
// ======================================
std::vector<std::string> ASN1Dump(const ParsedSequence& seq)
{// 结果容器预分配空间std::vector<std::string> dumpResult;dumpResult.reserve(seq.children.size());// 遍历所有子节点for (const ASN1Node& node : seq.children){// 元素1: 根据ASN.1标签标识获取类型名称std::string typeName;switch (node.tag){case 0x02: typeName = "INTEGER"; break;case 0x04: typeName = "OCTET_STRING"; break;case 0x30: typeName = "SEQUENCE"; break;case 0x03: typeName = "BIT_STRING"; break;case 0x06: typeName = "OBJECT_ID"; break;default: // 防御性处理未知类型std::ostringstream hexTag;hexTag << "UNKNOWN_0x"<< std::hex << std::uppercase<< std::setw(2) << std::setfill('0')<< static_cast<int>(node.tag);typeName = hexTag.str();}// 元素2: 十进制长度const std::string lengthStr = std::to_string(node.length);// 元素3: 十六进制值字符串(不带0x前缀)std::ostringstream hexStr;hexStr << std::hex << std::uppercase << std::setfill('0');for (const unsigned char byte : node.value){hexStr << std::setw(2) << static_cast<int>(byte);}// 组装单行格式std::ostringstream line;line << typeName << " " << lengthStr << " " << hexStr.str();dumpResult.push_back(line.str());}return dumpResult;
}// ======================================
// 公开接口函数(符合规范8.5)
// ======================================
std::string ParseASN1HexString(const std::string& hexInput)
{try{// 保障性检查:输入完整性if (hexInput.empty()){throw std::invalid_argument("输入字符串为空");}// 严格转换HEX数据(包含验证)std::vector<unsigned char> bytes = HexStringToBytes(hexInput);// 核心解析过程ParsedSequence parsed = ParseASN1Sequence(bytes);// 格式化输出auto result = ASN1Dump(parsed);// 组合结果std::string output;for (auto& v : result){output += v + "\n";}return output;}catch (const std::exception& e){// 结构化错误处理(符合规范4.9)throw std::runtime_error(std::string("ASN1解析失败: ") + e.what() +"\n输入数据: " + hexInput);}
}
4.代码测试
int main()
{std::string ans1_text = "3077021f321171f81a5e0b5b6348549365b60691889e8499e21509634f2cf8e73626b20220611a1f010df10f999ffdfd24b9b458865919ff10d12c77547f74c7f7b5efed7e042078b6643339ba91202a0e005929f9162afa087b78db422f44a09c352bc30dc2cc04101ae5aacfe295350a42b96aa111ffcb94";auto rst = ParseASN1HexString(ans1_text);std::cout << "result:\n" << rst << std::endl;return 0;
}
测试结果:
result:
INTEGER 31 321171F81A5E0B5B6348549365B60691889E8499E21509634F2CF8E73626B2
INTEGER 32 611A1F010DF10F999FFDFD24B9B458865919FF10D12C77547F74C7F7B5EFED7E
OCTET_STRING 32 78B6643339BA91202A0E005929F9162AFA087B78DB422F44A09C352BC30DC2CC
OCTET_STRING 16 1AE5AACFE295350A42B96AA111FFCB94