前言
在日常开发中,我们经常接触JSON、XML等数据格式,但你是否听说过ASN.1?这种在通信、安全、物联网领域广泛使用的数据描述语言。
可能对一部分开发者来说有些陌生,但在特定场景下却有着不可替代的作用。今天,我们就来深入了解一下ASN.1,并用SpringBoot实现一个在线解析工具。
什么是ASN.1?
基本概念
ASN.1(Abstract Syntax Notation One)是一种标准化的数据描述语言,由ITU-T(国际电信联盟)和ISO(国际标准化组织)共同制定。它提供了一种平台无关的语言来描述数据结构,并定义了数据的编码规则。
ASN.1的特点
- 平台无关性:不受编程语言、操作系统、硬件平台的限制
- 自描述性:数据结构本身就包含了类型信息
- 高效性:二进制编码,比文本格式更紧凑
- 标准化:有完善的国际标准支持
ASN.1的核心组件
- 模块(MODULE):ASN.1的基本组织单位
- 类型定义(TYPE):定义数据结构
- 值定义(VALUE):定义具体的值
- 编码规则:如BER、DER、PER等
ASN.1的应用场景
1. 通信协议
ASN.1在通信协议中应用广泛,特别是电信、密码领域:
1-- 电话号码的ASN.1定义 2PhoneNumber ::= SEQUENCE { 3 countryCode INTEGER, 4 areaCode INTEGER, 5 subscriberNumber INTEGER 6} 7
2. 密码学和安全
X.509证书、PKCS系列标准都使用ASN.1:
1-- 证书基本信息的简化定义 2Certificate ::= SEQUENCE { 3 version INTEGER, 4 serialNumber INTEGER, 5 signature AlgorithmIdentifier, 6 issuer Name, 7 subject Name, 8 validity Validity, 9 subjectPublicKeyInfo SubjectPublicKeyInfo 10} 11
3. 物联网和工业控制
在IoT设备和工业控制系统中,ASN.1用于数据交换:
1-- 传感器数据定义 2SensorData ::= SEQUENCE { 3 sensorId INTEGER, 4 timestamp GeneralizedTime, 5 temperature REAL, 6 humidity REAL, 7 status ENUMERATED { normal, warning, error } 8} 9
4. 医疗领域
DICOM(医学数字成像和通信)标准使用ASN.1:
1-- 医疗影像信息 2PatientRecord ::= SEQUENCE { 3 patientId INTEGER, 4 name UTF8String, 5 birthDate DATE, 6 examination SEQUENCE OF ExaminationInfo 7} 8
ASN.1与其他数据格式的对比
ASN.1 vs JSON
| 特性 | ASN.1 | JSON |
|---|---|---|
| 数据类型 | 丰富的基本类型(INTEGER、REAL、BIT STRING等) | 基本类型(number、string、boolean、array、object) |
| 编码方式 | 二进制(BER、DER、PER) | 文本(UTF-8) |
| 数据大小 | 紧凑,通常比JSON小30-50% | 相对较大 |
| 解析速度 | 快,直接二进制操作 | 较慢,需要文本解析 |
| 可读性 | 机器友好,需要工具查看 | 人类可读 |
| 自描述性 | 强,包含类型信息 | 弱,需要schema定义 |
| 适用场景 | 通信、安全、嵌入式 | Web API、配置文件 |
ASN.1 vs XML
| 特性 | ASN.1 | XML |
|---|---|---|
| 结构化程度 | 严格类型系统 | 标签标记 |
| 编码方式 | 二进制 | 文本 |
| 性能 | 高效 | 相对较低 |
| 复杂性 | 语法简单 | 复杂的语法规则 |
| 扩展性 | 良好 | 极好 |
| 工具支持 | 专业化工具 | 广泛的工具支持 |
编码规则对比
ASN.1支持多种编码规则:
- BER(Basic Encoding Rules):基本编码规则,灵活但不唯一
- DER(Distinguished Encoding Rules):唯一编码规则
- CER(Canonical Encoding Rules):规范编码规则
- PER(Packed Encoding Rules):打包编码规则
实现ASN.1在线解析工具
本示例用SpringBoot实现一个完整的ASN.1在线解析工具。
1. 项目结构
1asn1-parser/ 2├── src/ 3│ ├── main/ 4│ │ ├── java/ 5│ │ │ └── com/example/asn1/ 6│ │ │ ├── Asn1ParserApplication.java 7│ │ │ ├── controller/ 8│ │ │ │ └── Asn1Controller.java 9│ │ │ ├── service/ 10│ │ │ │ └── Asn1ParserService.java 11│ │ │ ├── dto/ 12│ │ │ │ ├── Asn1ParseRequest.java 13│ │ │ │ └── Asn1ParseResponse.java 14│ │ │ └── exception/ 15│ │ │ └── Asn1ParseException.java 16│ │ └── resources/ 17│ │ ├── application.yml 18│ │ └── static/ 19│ │ ├── index.html 20│ │ ├── style.css 21│ │ └── script.js 22└── pom.xml 23
2. 依赖配置
1<?xml version="1.0" encoding="UTF-8"?> 2<project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 5 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 6 <modelVersion>4.0.0</modelVersion> 7 8 <groupId>com.example</groupId> 9 <artifactId>springboot-asn1</artifactId> 10 <version>1.0.0</version> 11 <packaging>jar</packaging> 12 13 <name>SpringBoot ASN.1 Parser</name> 14 <description>在线ASN.1解析工具</description> 15 16 <parent> 17 <groupId>org.springframework.boot</groupId> 18 <artifactId>spring-boot-starter-parent</artifactId> 19 <version>2.7.18</version> 20 <relativePath/> 21 </parent> 22 23 <properties> 24 <java.version>11</java.version> 25 <maven.compiler.source>11</maven.compiler.source> 26 <maven.compiler.target>11</maven.compiler.target> 27 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 28 <bouncycastle.version>1.70</bouncycastle.version> 29 </properties> 30 31 <dependencies> 32 <!-- Spring Boot Starter Web --> 33 <dependency> 34 <groupId>org.springframework.boot</groupId> 35 <artifactId>spring-boot-starter-web</artifactId> 36 </dependency> 37 38 <dependency> 39 <groupId>org.bouncycastle</groupId> 40 <artifactId>bcpkix-jdk15on</artifactId> 41 <version>${bouncycastle.version}</version> 42 </dependency> 43 44 <dependency> 45 <groupId>org.bouncycastle</groupId> 46 <artifactId>bcprov-jdk15on</artifactId> 47 <version>${bouncycastle.version}</version> 48 </dependency> 49 50 <!-- Lombok for reducing boilerplate code --> 51 <dependency> 52 <groupId>org.projectlombok</groupId> 53 <artifactId>lombok</artifactId> 54 <optional>true</optional> 55 </dependency> 56 </dependencies> 57</project> 58
3. 数据传输对象
1package com.example.asn1.dto; 2 3import lombok.Data; 4import lombok.NoArgsConstructor; 5import lombok.AllArgsConstructor; 6import jakarta.validation.constraints.NotEmpty; 7 8@Data 9@NoArgsConstructor 10@AllArgsConstructor 11public class Asn1ParseRequest { 12 13 @NotEmpty(message = "ASN.1数据不能为空") 14 private String asn1Data; 15 16 private String encodingType = "HEX"; // HEX, BASE64, RAW 17 18 private boolean verbose = false; 19} 20
1package com.example.asn1.dto; 2 3import lombok.Data; 4import lombok.NoArgsConstructor; 5import lombok.AllArgsConstructor; 6import java.util.List; 7import java.util.Map; 8 9@Data 10@NoArgsConstructor 11@AllArgsConstructor 12public class Asn1ParseResponse { 13 14 private boolean success; 15 16 private String message; 17 18 private Asn1Structure rootStructure; 19 20 private List<String> warnings; 21 22 private Map<String, Object> metadata; 23 24 @Data 25 @NoArgsConstructor 26 @AllArgsConstructor 27 public static class Asn1Structure { 28 private String tag; 29 private int tagNumber; 30 private String tagClass; 31 private String type; 32 private String value; 33 private int length; 34 private int offset; 35 private List<Asn1Structure> children; 36 private Map<String, Object> properties; 37 } 38} 39
4. 解析服务
1package com.example.asn1.service; 2 3import com.example.asn1.dto.Asn1ParseResponse; 4import com.example.asn1.exception.Asn1ParseException; 5import org.bouncycastle.asn1.*; 6import org.springframework.stereotype.Service; 7 8import java.io.ByteArrayInputStream; 9import java.io.IOException; 10import java.util.*; 11import java.util.stream.Collectors; 12 13@Service 14public class Asn1ParserService { 15 16 public Asn1ParseResponse parseAsn1Data(String data, String encodingType, boolean verbose) { 17 try { 18 byte[] asn1Bytes = decodeAsn1Data(data, encodingType); 19 ASN1InputStream asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(asn1Bytes)); 20 ASN1Primitive asn1Primitive = asn1InputStream.readObject(); 21 22 Asn1ParseResponse.Asn1Structure rootStructure = parseStructure(asn1Primitive, 0, verbose); 23 24 List<String> warnings = new ArrayList<>(); 25 Map<String, Object> metadata = createMetadata(asn1Bytes, encodingType); 26 27 return new Asn1ParseResponse( 28 true, 29 "ASN.1数据解析成功", 30 rootStructure, 31 warnings, 32 metadata 33 ); 34 35 } catch (Exception e) { 36 throw new Asn1ParseException("ASN.1解析失败: " + e.getMessage(), e); 37 } 38 } 39 40 private byte[] decodeAsn1Data(String data, String encodingType) throws IOException { 41 data = data.trim().replaceAll("\\s+", ""); 42 43 switch (encodingType.toUpperCase()) { 44 case "HEX": 45 return hexStringToByteArray(data); 46 case "BASE64": 47 return Base64.getDecoder().decode(data); 48 case "RAW": 49 return data.getBytes(); 50 default: 51 throw new IllegalArgumentException("不支持的编码类型: " + encodingType); 52 } 53 } 54 55 private byte[] hexStringToByteArray(String hex) { 56 if (hex.length() % 2 != 0) { 57 hex = "0" + hex; 58 } 59 60 byte[] bytes = new byte[hex.length() / 2]; 61 for (int i = 0; i < hex.length(); i += 2) { 62 bytes[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16); 63 } 64 return bytes; 65 } 66 67 private Asn1ParseResponse.Asn1Structure parseStructure(ASN1Primitive asn1, int offset, boolean verbose) { 68 Asn1ParseResponse.Asn1Structure structure = new Asn1ParseResponse.Asn1Structure(); 69 70 if (asn1 instanceof ASN1TaggedObject) { 71 ASN1TaggedObject tagged = (ASN1TaggedObject) asn1; 72 structure.setTag("TAGGED"); 73 structure.setTagNumber(tagged.getTagNo()); 74 structure.setTagClass(getTagClass(tagged.getTagClass())); 75 structure.setOffset(offset); 76 77 ASN1Primitive baseObject = tagged.getObject(); 78 if (baseObject instanceof ASN1OctetString && !tagged.isExplicit()) { 79 structure.setType("IMPLICIT OCTET STRING"); 80 structure.setValue("0x" + bytesToHex(((ASN1OctetString) baseObject).getOctets())); 81 } else { 82 Asn1ParseResponse.Asn1Structure childStructure = parseStructure(baseObject, offset, verbose); 83 structure.setType(childStructure.getType()); 84 structure.setValue(childStructure.getValue()); 85 structure.setChildren(childStructure.getChildren()); 86 } 87 } else if (asn1 instanceof ASN1Sequence) { 88 ASN1Sequence sequence = (ASN1Sequence) asn1; 89 structure.setTag("SEQUENCE"); 90 structure.setTagNumber(16); 91 structure.setTagClass("UNIVERSAL"); 92 structure.setType("SEQUENCE"); 93 structure.setLength(sequence.size()); 94 structure.setOffset(offset); 95 96 List<Asn1ParseResponse.Asn1Structure> children = new ArrayList<>(); 97 int childOffset = offset + 2; // 简化的偏移计算 98 for (Enumeration<?> e = sequence.getObjects(); e.hasMoreElements(); ) { 99 ASN1Primitive element = (ASN1Primitive) e.nextElement(); 100 children.add(parseStructure(element, childOffset, verbose)); 101 childOffset += 10; // 简化的长度计算 102 } 103 structure.setChildren(children); 104 structure.setValue(sequence.size() + " 个元素"); 105 106 } else if (asn1 instanceof ASN1Set) { 107 ASN1Set set = (ASN1Set) asn1; 108 structure.setTag("SET"); 109 structure.setTagNumber(17); 110 structure.setTagClass("UNIVERSAL"); 111 structure.setType("SET"); 112 structure.setLength(set.size()); 113 structure.setOffset(offset); 114 structure.setValue(set.size() + " 个元素"); 115 116 List<Asn1ParseResponse.Asn1Structure> children = new ArrayList<>(); 117 for (Enumeration<?> e = set.getObjects(); e.hasMoreElements(); ) { 118 children.add(parseStructure((ASN1Primitive) e.nextElement(), offset, verbose)); 119 } 120 structure.setChildren(children); 121 122 } else if (asn1 instanceof ASN1Integer) { 123 ASN1Integer integer = (ASN1Integer) asn1; 124 structure.setTag("INTEGER"); 125 structure.setTagNumber(2); 126 structure.setTagClass("UNIVERSAL"); 127 structure.setType("INTEGER"); 128 structure.setValue(integer.getValue().toString()); 129 structure.setOffset(offset); 130 131 } else if (asn1 instanceof ASN1OctetString) { 132 ASN1OctetString octetString = (ASN1OctetString) asn1; 133 structure.setTag("OCTET STRING"); 134 structure.setTagNumber(4); 135 structure.setTagClass("UNIVERSAL"); 136 structure.setType("OCTET STRING"); 137 structure.setValue("0x" + bytesToHex(octetString.getOctets())); 138 structure.setLength(octetString.getOctets().length); 139 structure.setOffset(offset); 140 141 } else if (asn1 instanceof DERUTF8String) { 142 DERUTF8String utf8String = (DERUTF8String) asn1; 143 structure.setTag("UTF8String"); 144 structure.setTagNumber(12); 145 structure.setTagClass("UNIVERSAL"); 146 structure.setType("UTF8String"); 147 structure.setValue(utf8String.getString()); 148 structure.setOffset(offset); 149 150 } else if (asn1 instanceof DERPrintableString) { 151 DERPrintableString printableString = (DERPrintableString) asn1; 152 structure.setTag("PrintableString"); 153 structure.setTagNumber(19); 154 structure.setTagClass("UNIVERSAL"); 155 structure.setType("PrintableString"); 156 structure.setValue(printableString.getString()); 157 structure.setOffset(offset); 158 159 } else if (asn1 instanceof ASN1ObjectIdentifier) { 160 ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) asn1; 161 structure.setTag("OBJECT IDENTIFIER"); 162 structure.setTagNumber(6); 163 structure.setTagClass("UNIVERSAL"); 164 structure.setType("OBJECT IDENTIFIER"); 165 structure.setValue(oid.getId()); 166 structure.setOffset(offset); 167 168 } else if (asn1 instanceof ASN1BitString) { 169 ASN1BitString bitString = (ASN1BitString) asn1; 170 structure.setTag("BIT STRING"); 171 structure.setTagNumber(3); 172 structure.setTagClass("UNIVERSAL"); 173 structure.setType("BIT STRING"); 174 structure.setValue("0x" + bytesToHex(bitString.getBytes())); 175 structure.setLength(bitString.getBytes().length); 176 structure.setOffset(offset); 177 178 } else { 179 structure.setTag("UNKNOWN"); 180 structure.setTagNumber(-1); 181 structure.setTagClass("UNKNOWN"); 182 structure.setType("UNKNOWN"); 183 structure.setValue(asn1.toString()); 184 structure.setOffset(offset); 185 } 186 187 // 添加详细属性 188 if (verbose) { 189 Map<String, Object> properties = new HashMap<>(); 190 properties.put("className", asn1.getClass().getSimpleName()); 191 structure.setProperties(properties); 192 } 193 194 return structure; 195 } 196 197 private String getTagClass(int tagClass) { 198 switch (tagClass) { 199 case DERTags.UNIVERSAL: return "UNIVERSAL"; 200 case DERTags.APPLICATION: return "APPLICATION"; 201 case DERTags.CONTEXT_SPECIFIC: return "CONTEXT_SPECIFIC"; 202 case DERTags.PRIVATE: return "PRIVATE"; 203 default: return "UNKNOWN"; 204 } 205 } 206 207 private String bytesToHex(byte[] bytes) { 208 StringBuilder sb = new StringBuilder(); 209 for (byte b : bytes) { 210 sb.append(String.format("%02X", b)); 211 } 212 return sb.toString(); 213 } 214 215 private Map<String, Object> createMetadata(byte[] data, String encodingType) { 216 Map<String, Object> metadata = new HashMap<>(); 217 metadata.put("originalLength", data.length); 218 metadata.put("encodingType", encodingType); 219 metadata.put("encodingTimestamp", System.currentTimeMillis()); 220 221 // 检测可能的编码规则 222 String probableEncoding = detectEncodingRule(data); 223 metadata.put("probableEncoding", probableEncoding); 224 225 return metadata; 226 } 227 228 private String detectEncodingRule(byte[] data) { 229 // 简化的编码规则检测 230 if (data.length > 0) { 231 byte firstByte = data[0]; 232 if ((firstByte & 0x1F) == 0x10) { // SEQUENCE tag 233 if (isDerCompliant(data)) { 234 return "DER (Distinguished Encoding Rules)"; 235 } else { 236 return "BER (Basic Encoding Rules)"; 237 } 238 } 239 } 240 return "Unknown"; 241 } 242 243 private boolean isDerCompliant(byte[] data) { 244 // 简化的DER合规性检查 245 // DER要求长度字段使用最短形式 246 if (data.length >= 2) { 247 byte lengthByte = data[1]; 248 if ((lengthByte & 0x80) != 0) { 249 int lengthBytes = lengthByte & 0x7F; 250 // 检查是否使用了最短形式 251 if (lengthBytes == 1) { 252 return (data[2] & 0x80) != 0; 253 } 254 } 255 } 256 return true; 257 } 258} 259
5. 异常处理
1package com.example.asn1.exception; 2 3import lombok.Getter; 4 5@Getter 6public class Asn1ParseException extends RuntimeException { 7 8 private final String errorCode; 9 10 public Asn1ParseException(String message) { 11 super(message); 12 this.errorCode = "ASN1_PARSE_ERROR"; 13 } 14 15 public Asn1ParseException(String message, Throwable cause) { 16 super(message, cause); 17 this.errorCode = "ASN1_PARSE_ERROR"; 18 } 19 20 public Asn1ParseException(String errorCode, String message) { 21 super(message); 22 this.errorCode = errorCode; 23 } 24} 25
6. 控制器
1package com.example.asn1.controller; 2 3import com.example.asn1.dto.Asn1ParseRequest; 4import com.example.asn1.dto.Asn1ParseResponse; 5import com.example.asn1.service.Asn1ParserService; 6import com.example.asn1.exception.Asn1ParseException; 7import org.springframework.http.ResponseEntity; 8import org.springframework.validation.annotation.Validated; 9import org.springframework.web.bind.annotation.*; 10 11import jakarta.validation.Valid; 12import java.util.HashMap; 13import java.util.Map; 14 15@RestController 16@RequestMapping("/api/asn1") 17@Validated 18public class Asn1Controller { 19 20 private final Asn1ParserService asn1ParserService; 21 22 public Asn1Controller(Asn1ParserService asn1ParserService) { 23 this.asn1ParserService = asn1ParserService; 24 } 25 26 @PostMapping("/parse") 27 public ResponseEntity<Asn1ParseResponse> parseAsn1(@Valid @RequestBody Asn1ParseRequest request) { 28 try { 29 Asn1ParseResponse response = asn1ParserService.parseAsn1Data( 30 request.getAsn1Data(), 31 request.getEncodingType(), 32 request.isVerbose() 33 ); 34 return ResponseEntity.ok(response); 35 } catch (Asn1ParseException e) { 36 Asn1ParseResponse errorResponse = new Asn1ParseResponse(); 37 errorResponse.setSuccess(false); 38 errorResponse.setMessage(e.getMessage()); 39 errorResponse.setRootStructure(null); 40 errorResponse.setWarnings(null); 41 errorResponse.setMetadata(Map.of("errorCode", e.getErrorCode())); 42 return ResponseEntity.badRequest().body(errorResponse); 43 } 44 } 45 46 @GetMapping("/info") 47 public ResponseEntity<Map<String, Object>> getAsn1Info() { 48 Map<String, Object> info = new HashMap<>(); 49 info.put("application", "ASN.1在线解析工具"); 50 info.put("version", "1.0.0"); 51 info.put("supportedEncodings", new String[]{"HEX", "BASE64", "RAW"}); 52 info.put("supportedTypes", new String[]{ 53 "SEQUENCE", "SET", "INTEGER", "OCTET STRING", 54 "UTF8String", "PrintableString", "OBJECT IDENTIFIER", 55 "BIT STRING", "TAGGED" 56 }); 57 return ResponseEntity.ok(info); 58 } 59} 60
7. 前端界面
1<!DOCTYPE html> 2<html lang="zh-CN"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>ASN.1在线解析工具</title> 7 <link rel="stylesheet" href="style.css"> 8</head> 9<body> 10 <div class="container"> 11 <header> 12 <h1>ASN.1在线解析工具</h1> 13 <p>支持HEX、Base64等格式的ASN.1数据解析</p> 14 </header> 15 16 <main> 17 <section class="input-section"> 18 <h2>输入ASN.1数据</h2> 19 20 <div class="encoding-selector"> 21 <label for="encodingType">编码类型:</label> 22 <select id="encodingType"> 23 <option value="HEX">HEX</option> 24 <option value="BASE64">Base64</option> 25 <option value="RAW">Raw</option> 26 </select> 27 </div> 28 29 <div class="textarea-container"> 30 <textarea id="asn1Input" placeholder="请输入ASN.1数据(HEX格式,例如:308201023081d9...)"></textarea> 31 <div class="sample-data"> 32 <h3>示例数据:</h3> 33 <button class="sample-btn" data-sample="certificate">X.509证书示例</button> 34 <button class="sample-btn" data-sample="sequence">SEQUENCE示例</button> 35 <button class="sample-btn" data-sample="integer">INTEGER示例</button> 36 </div> 37 </div> 38 39 <div class="options"> 40 <label> 41 <input type="checkbox" id="verbose"> 42 详细输出 43 </label> 44 </div> 45 46 <button id="parseBtn" class="parse-btn">解析ASN.1</button> 47 </section> 48 49 <section class="result-section"> 50 <h2>解析结果</h2> 51 <div id="resultContainer" class="result-container"> 52 <div class="placeholder">解析结果将在这里显示...</div> 53 </div> 54 </section> 55 </main> 56 57 <footer> 58 <p>基于SpringBoot和BouncyCastle实现 | 支持BER、DER等编码规则</p> 59 </footer> 60 </div> 61 62 <script src="script.js"></script> 63</body> 64</html> 65
1// script.js 2document.addEventListener('DOMContentLoaded', function() { 3 const asn1Input = document.getElementById('asn1Input'); 4 const encodingType = document.getElementById('encodingType'); 5 const verboseCheckbox = document.getElementById('verbose'); 6 const parseBtn = document.getElementById('parseBtn'); 7 const resultContainer = document.getElementById('resultContainer'); 8 9 // 示例数据 10 const sampleData = { 11 certificate: '308201023081d9...(实际的X.509证书HEX数据)', 12 sequence: '3009020101020101020101', 13 integer: '020101' 14 }; 15 16 // 示例数据按钮事件 17 document.querySelectorAll('.sample-btn').forEach(btn => { 18 btn.addEventListener('click', function() { 19 const sampleType = this.dataset.sample; 20 if (sampleData[sampleType]) { 21 asn1Input.value = sampleData[sampleType]; 22 asn1Input.focus(); 23 } 24 }); 25 }); 26 27 // 解析按钮事件 28 parseBtn.addEventListener('click', parseAsn1); 29 30 // 输入框回车事件 31 asn1Input.addEventListener('keydown', function(e) { 32 if (e.key === 'Enter' && e.ctrlKey) { 33 parseAsn1(); 34 } 35 }); 36 37 async function parseAsn1() { 38 const data = asn1Input.value.trim(); 39 40 if (!data) { 41 showError('请输入ASN.1数据'); 42 return; 43 } 44 45 // 显示加载状态 46 parseBtn.innerHTML = '<span class="loading"></span>解析中...'; 47 parseBtn.disabled = true; 48 49 try { 50 const response = await fetch('/api/asn1/parse', { 51 method: 'POST', 52 headers: { 53 'Content-Type': 'application/json', 54 }, 55 body: JSON.stringify({ 56 asn1Data: data, 57 encodingType: encodingType.value, 58 verbose: verboseCheckbox.checked 59 }) 60 }); 61 62 const result = await response.json(); 63 64 if (result.success) { 65 showSuccess(result); 66 } else { 67 showError(result.message); 68 } 69 } catch (error) { 70 showError('网络错误或服务器异常: ' + error.message); 71 } finally { 72 // 恢复按钮状态 73 parseBtn.innerHTML = '解析ASN.1'; 74 parseBtn.disabled = false; 75 } 76 } 77 78 function showSuccess(result) { 79 let html = '<div class="success-message">✓ ' + result.message + '</div>'; 80 81 if (result.rootStructure) { 82 html += renderStructure(result.rootStructure); 83 } 84 85 if (result.metadata) { 86 html += '<div style="margin-top: 20px;"><h3>元数据</h3><div class="structure-details">'; 87 for (const [key, value] of Object.entries(result.metadata)) { 88 html += [`<div><strong>${key}:</strong> ${value}</div>`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.div.md); 89 } 90 html += '</div></div>'; 91 } 92 93 if (result.warnings && result.warnings.length > 0) { 94 html += '<div style="margin-top: 15px;"><h3>警告</h3><div style="color: #f39c12;">'; 95 result.warnings.forEach(warning => { 96 html += [`<div>⚠ ${warning}</div>`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.div.md); 97 }); 98 html += '</div></div>'; 99 } 100 101 resultContainer.innerHTML = html; 102 } 103 104 function showError(message) { 105 resultContainer.innerHTML = [`<div class="error-message">✗ ${message}</div>`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.div.md); 106 } 107 108 function renderStructure(structure, level = 0) { 109 const indent = ' '.repeat(level); 110 const tagClass = getTagClass(structure.tagClass); 111 112 let html = ` 113 <div class="structure-item" style="margin-left: ${level * 20}px;"> 114 <div class="structure-header"> 115 <span class="tag-info tag-${tagClass}">${structure.tagClass}</span> 116 <span>${structure.tag}</span> 117 ${structure.tagNumber >= 0 ? `[${structure.tagNumber}]` : ''} 118 </div> 119 <div class="structure-details"> 120 <div><strong>类型:</strong> ${structure.type}</div> 121 <div><strong>值:</strong> ${structure.value}</div> 122 ${structure.length ? [`<div><strong>长度:</strong> ${structure.length}</div>`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.div.md) : ''} 123 ${structure.offset ? [`<div><strong>偏移:</strong> 0x${structure.offset.toString(16).toUpperCase()}</div>`](function toString() { [native code] }) : ''} 124 </div> 125 `; 126 127 if (structure.properties) { 128 html += '<div class="structure-details">'; 129 for (const [key, value] of Object.entries(structure.properties)) { 130 html += [`<div><em>${key}:</em> ${value}</div>`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.div.md); 131 } 132 html += '</div>'; 133 } 134 135 html += '</div>'; 136 137 if (structure.children && structure.children.length > 0) { 138 html += '<div class="structure-children">'; 139 structure.children.forEach(child => { 140 html += renderStructure(child, level + 1); 141 }); 142 html += '</div>'; 143 } 144 145 return html; 146 } 147 148 function getTagClass(tagClass) { 149 switch (tagClass) { 150 case 'UNIVERSAL': return 'universal'; 151 case 'APPLICATION': return 'application'; 152 case 'CONTEXT_SPECIFIC': return 'context'; 153 case 'PRIVATE': return 'private'; 154 default: return 'unknown'; 155 } 156 } 157}); 158
8. 应用主类
1package com.example.asn1; 2 3import org.springframework.boot.SpringApplication; 4import org.springframework.boot.autoconfigure.SpringBootApplication; 5 6@SpringBootApplication 7public class Asn1ParserApplication { 8 9 public static void main(String[] args) { 10 SpringApplication.run(Asn1ParserApplication.class, args); 11 } 12} 13
测试使用
1. 测试X.509证书解析
1# 启动应用 2mvn spring-boot:run 3 4# 测试证书解析 5curl -X POST http://localhost:8080/api/asn1/parse \ 6 -H "Content-Type: application/json" \ 7 -d '{ 8 "asn1Data": "308201023081d9...", 9 "encodingType": "HEX", 10 "verbose": true 11 }' 12
2. 前端测试
访问 http://localhost:8080 即可使用Web界面进行ASN.1数据解析。
总结
ASN.1在通信、安全、物联网等专业领域有着重要作用。理解ASN.1的原理和应用场景,有助于我们在特定项目中选择合适的数据格式。
这个工具不仅可以帮助开发者理解ASN.1数据结构,还可以用于调试和验证ASN.1编码的数据。在实际项目中,你可以根据需要进一步扩展功能。
《除了JSON/XML,你还应该了解的数据描述语言ASN.1 —— 附《SpringBoot实现ASN.1在线解析工具》》 是转载文章,点击查看原文。
