除了JSON/XML,你还应该了解的数据描述语言ASN.1 —— 附《SpringBoot实现ASN.1在线解析工具》

作者:风象南日期:2025/10/17

前言

在日常开发中,我们经常接触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.1JSON
数据类型丰富的基本类型(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.1XML
结构化程度严格类型系统标签标记
编码方式二进制文本
性能高效相对较低
复杂性语法简单复杂的语法规则
扩展性良好极好
工具支持专业化工具广泛的工具支持

编码规则对比

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编码的数据。在实际项目中,你可以根据需要进一步扩展功能。

github.com/yuboon/java…


除了JSON/XML,你还应该了解的数据描述语言ASN.1 —— 附《SpringBoot实现ASN.1在线解析工具》》 是转载文章,点击查看原文


相关推荐


Redis(62)如何优化Redis的连接数?
Victor3562025/10/15

优化 Redis 的连接数可以通过以下策略实现: 使用连接池。 合理配置 Redis。 调整操作系统限制。 使用客户端连接复用。 实现批量操作。 分布式架构及读写分离。 以下将详细探讨这些策略,并提供相应代码示例。 1. 使用连接池 连接池可以重用现有连接,减少连接创建和释放的开销,从而提升性能。 Java 示例(Jedis 连接池) 添加 Jedis 依赖: 确保在 pom.xml 文件中添加 Jedis 依赖: <dependency> <groupId>redis.clie


nginx反向代理与缓存功能
Justin_192025/10/14

目录 一,正向代理和反向代理 1,正向代理概述 2,反向代理概述 二,配置实战 1,实现反向代理负载均衡 三,nginx配置跨域cors 1,跨域的定义 2,同源的定义 3,不同源的限制 4,nginx解决跨域的原理 5,案例 一,正向代理和反向代理 1,正向代理概述 正向代理是一个位于客户端和目标服务器之间的代理服务器。为了从目标服务器得到目标,客户端向代理服务器发送一个请求,并且指定目标服务器,再代理向目标服务器转发请求,将得到的目标返回给客户端。 正向


前端人必看!3个技巧让你彻底搞懂JS条件判断与循环
良山有风来2025/10/13

那些年我们踩过的坑 还记得刚学JavaScript的时候吗?遇到复杂的条件判断就头晕,写循环时总是搞不清该用for还是while。最尴尬的是,代码跑起来总是不按预期的来,bug找得头都大了! 说实话,这些困扰我都经历过。但后来我发现,只要掌握了几个核心技巧,这些看似复杂的问题都能轻松解决。 今天我就把压箱底的干货拿出来,用最通俗易懂的方式,带你彻底搞懂JS的条件判断和循环控制。看完这篇文章,你不仅能写出更优雅的代码,还能避开90%新手都会踩的坑! 条件判断:让你的代码更“聪明” 条件判断就像给代


(1)100天python从入门到拿捏
C嘎嘎嵌入式开发2025/10/11

官方文档 python教程 window系统下查看python版本 win+R键打开cmd输入下面的命令 python --version 或者 python -V 类似c语言以.c为后缀,c++以.cpp为后缀,python文件的扩展名以.py结尾 Jupyter Notebook文件以.ipynb为后缀,该类文件通常包含代码、文本和可视化结果 Python 3简介 Python 3 是一种高级、解释型的编程语言,广泛用于 Web 开发、数据科学、机器学习、自


前端梳理体系从常问问题去完善-框架篇(react生态)
大前端helloworld2025/10/9

前言 国庆去趟了杭州,但是人太多了,走路都觉得空气很闷,天气也很热,玩了两天就回宿舍躺了,感觉人太多,看不到风景,而且消费也很高,性价比不是很值得,就呆在公寓,看了两本书,有一本是名著,《呼啸山庄》虽然是写的是爱情,但爱情背后是人性。爱情啊,这个课题本来就是让人很难读懂得,关于爱,也看了一篇文章。关于爱上人渣得,爱上人渣,或是那些求而不得甚至是受制于禁忌的爱,本质上也是在追求这种刺激,或者说正是因为这样的对象能给自己麻木的感官更大的刺激,从而误以为这就是「爱」的本质,就像是人们虽然知道「吊桥效应


Spec-Kit:AI驱动的软件开发全流程管理工具套件
是魔丸啊2025/10/8

Spec-Kit 是一套专为AI辅助开发场景设计的命令行工具套件,提供了从需求规格到代码实现的标准化工作流程。通过8个核心命令,它确保软件开发的每个环节都保持高质量和一致性。 核心理念 端到端管理:覆盖需求规格化、架构设计、任务分解、代码实现全流程 AI优化设计:专为与Claude等AI助手协作而优化 质量保证:内置多层次验证机制 知识沉淀:项目宪法、设计决策完整记录 这篇文章主要是想记录下spec-kit的核心command具体在干嘛。所以翻译了下所有的command(按照实际使用顺序)


Node.js(十二)插件开发
燎原人生2025/10/6

一、Node.js 原生插件(Native Addon)开发入门教程 1. 准备环境 安装 Node.js(建议最新版) 安装 node-gyp 工具(用于编译 C++ 代码) npm install -g node-gyp 安装编译环境 Windows: 需要安装 Windows Build ToolsmacOS: 需要安装 XcodeLinux: 需要安装 g++、make 等 2. 创建项目目录 mkdir my-native-addon cd my-native-


Qiankun 子应用生命周期及使用场景解析
excel2025/10/5

在前端微前端架构中,Qiankun 是常用的微前端框架。它允许主应用动态加载多个子应用,而子应用必须遵循特定生命周期,保证能够被正确加载、挂载和卸载。本文将详细解析 子应用的三个核心生命周期函数:bootstrap、mount、unmount,并说明它们的使用场景,同时给出具体示例。 1. 子应用生命周期概览 子应用生命周期分为三个阶段: Bootstrap(初始化阶段) Mount(挂载阶段) Unmount(卸载阶段) 每个阶段有明确的调用时机和职责。 2. bootstrap:初始


《Shell脚本门诊部:我治好了日志清理、自动备份和监控报警的“慢性病”》
鋯莂從媊2025/10/4

核心思路: 把Shell脚本比作“药方”,专门治疗运维工作中的那些重复、繁琐的“慢性病”。每个案例都是一个完整的、可复用的脚本。 内容要点与实操步骤: 案例一:日志清理脚本(“磁盘空间肥胖症”) 1.病症: /app/logs 目录动不动就100%,需要自动清理7天前的日志。 2.药方: #!/bin/bash # 日志清理专家 - 专治磁盘空间肥胖症 LOG_DIR="/app/logs" FIND_RESULT=$(find $LOG_DIR -name "*.log" -typ


ElementUI-Table 表格实现行拖拽
宣晨光2025/10/3

1、引入依赖 npm install sortablejs --save 2、table表格设置 1、添加属性 ref="multipleTable"  row-key="id" @row-drag-end="handleDragEnd"  2、添加列 <el-table-column width="50" align="center">             <template >               <i class="el-icon-rank drag-han

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0