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

393 阅读8分钟

前言

在日常开发中,我们经常接触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在通信协议中应用广泛,特别是电信、密码领域:

-- 电话号码的ASN.1定义
PhoneNumber ::= SEQUENCE {
    countryCode   INTEGER,
    areaCode      INTEGER,
    subscriberNumber  INTEGER
}

2. 密码学和安全

X.509证书、PKCS系列标准都使用ASN.1:

-- 证书基本信息的简化定义
Certificate ::= SEQUENCE {
    version       INTEGER,
    serialNumber  INTEGER,
    signature     AlgorithmIdentifier,
    issuer        Name,
    subject       Name,
    validity      Validity,
    subjectPublicKeyInfo SubjectPublicKeyInfo
}

3. 物联网和工业控制

在IoT设备和工业控制系统中,ASN.1用于数据交换:

-- 传感器数据定义
SensorData ::= SEQUENCE {
    sensorId      INTEGER,
    timestamp     GeneralizedTime,
    temperature   REAL,
    humidity      REAL,
    status        ENUMERATED { normal, warning, error }
}

4. 医疗领域

DICOM(医学数字成像和通信)标准使用ASN.1:

-- 医疗影像信息
PatientRecord ::= SEQUENCE {
    patientId     INTEGER,
    name          UTF8String,
    birthDate     DATE,
    examination   SEQUENCE OF ExaminationInfo
}

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. 项目结构

asn1-parser/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/asn1/
│   │   │       ├── Asn1ParserApplication.java
│   │   │       ├── controller/
│   │   │       │   └── Asn1Controller.java
│   │   │       ├── service/
│   │   │       │   └── Asn1ParserService.java
│   │   │       ├── dto/
│   │   │       │   ├── Asn1ParseRequest.java
│   │   │       │   └── Asn1ParseResponse.java
│   │   │       └── exception/
│   │   │           └── Asn1ParseException.java
│   │   └── resources/
│   │       ├── application.yml
│   │       └── static/
│   │           ├── index.html
│   │           ├── style.css
│   │           └── script.js
└── pom.xml

2. 依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>springboot-asn1</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <name>SpringBoot ASN.1 Parser</name>
    <description>在线ASN.1解析工具</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/>
    </parent>

    <properties>
        <java.version>11</java.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <bouncycastle.version>1.70</bouncycastle.version>
        </properties>

    <dependencies>
        <!-- Spring Boot Starter Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>${bouncycastle.version}</version>
        </dependency>

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>${bouncycastle.version}</version>
        </dependency>

        <!-- Lombok for reducing boilerplate code -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

3. 数据传输对象

package com.example.asn1.dto;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import jakarta.validation.constraints.NotEmpty;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Asn1ParseRequest {

    @NotEmpty(message = "ASN.1数据不能为空")
    private String asn1Data;

    private String encodingType = "HEX"; // HEX, BASE64, RAW

    private boolean verbose = false;
}
package com.example.asn1.dto;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.util.List;
import java.util.Map;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Asn1ParseResponse {

    private boolean success;

    private String message;

    private Asn1Structure rootStructure;

    private List<String> warnings;

    private Map<String, Object> metadata;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Asn1Structure {
        private String tag;
        private int tagNumber;
        private String tagClass;
        private String type;
        private String value;
        private int length;
        private int offset;
        private List<Asn1Structure> children;
        private Map<String, Object> properties;
    }
}

4. 解析服务

package com.example.asn1.service;

import com.example.asn1.dto.Asn1ParseResponse;
import com.example.asn1.exception.Asn1ParseException;
import org.bouncycastle.asn1.*;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class Asn1ParserService {

    public Asn1ParseResponse parseAsn1Data(String data, String encodingType, boolean verbose) {
        try {
            byte[] asn1Bytes = decodeAsn1Data(data, encodingType);
            ASN1InputStream asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(asn1Bytes));
            ASN1Primitive asn1Primitive = asn1InputStream.readObject();

            Asn1ParseResponse.Asn1Structure rootStructure = parseStructure(asn1Primitive, 0, verbose);

            List<String> warnings = new ArrayList<>();
            Map<String, Object> metadata = createMetadata(asn1Bytes, encodingType);

            return new Asn1ParseResponse(
                true,
                "ASN.1数据解析成功",
                rootStructure,
                warnings,
                metadata
            );

        } catch (Exception e) {
            throw new Asn1ParseException("ASN.1解析失败: " + e.getMessage(), e);
        }
    }

    private byte[] decodeAsn1Data(String data, String encodingType) throws IOException {
        data = data.trim().replaceAll("\\s+", "");

        switch (encodingType.toUpperCase()) {
            case "HEX":
                return hexStringToByteArray(data);
            case "BASE64":
                return Base64.getDecoder().decode(data);
            case "RAW":
                return data.getBytes();
            default:
                throw new IllegalArgumentException("不支持的编码类型: " + encodingType);
        }
    }

    private byte[] hexStringToByteArray(String hex) {
        if (hex.length() % 2 != 0) {
            hex = "0" + hex;
        }

        byte[] bytes = new byte[hex.length() / 2];
        for (int i = 0; i < hex.length(); i += 2) {
            bytes[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16);
        }
        return bytes;
    }

    private Asn1ParseResponse.Asn1Structure parseStructure(ASN1Primitive asn1, int offset, boolean verbose) {
        Asn1ParseResponse.Asn1Structure structure = new Asn1ParseResponse.Asn1Structure();

        if (asn1 instanceof ASN1TaggedObject) {
            ASN1TaggedObject tagged = (ASN1TaggedObject) asn1;
            structure.setTag("TAGGED");
            structure.setTagNumber(tagged.getTagNo());
            structure.setTagClass(getTagClass(tagged.getTagClass()));
            structure.setOffset(offset);

            ASN1Primitive baseObject = tagged.getObject();
            if (baseObject instanceof ASN1OctetString && !tagged.isExplicit()) {
                structure.setType("IMPLICIT OCTET STRING");
                structure.setValue("0x" + bytesToHex(((ASN1OctetString) baseObject).getOctets()));
            } else {
                Asn1ParseResponse.Asn1Structure childStructure = parseStructure(baseObject, offset, verbose);
                structure.setType(childStructure.getType());
                structure.setValue(childStructure.getValue());
                structure.setChildren(childStructure.getChildren());
            }
        } else if (asn1 instanceof ASN1Sequence) {
            ASN1Sequence sequence = (ASN1Sequence) asn1;
            structure.setTag("SEQUENCE");
            structure.setTagNumber(16);
            structure.setTagClass("UNIVERSAL");
            structure.setType("SEQUENCE");
            structure.setLength(sequence.size());
            structure.setOffset(offset);

            List<Asn1ParseResponse.Asn1Structure> children = new ArrayList<>();
            int childOffset = offset + 2; // 简化的偏移计算
            for (Enumeration<?> e = sequence.getObjects(); e.hasMoreElements(); ) {
                ASN1Primitive element = (ASN1Primitive) e.nextElement();
                children.add(parseStructure(element, childOffset, verbose));
                childOffset += 10; // 简化的长度计算
            }
            structure.setChildren(children);
            structure.setValue(sequence.size() + " 个元素");

        } else if (asn1 instanceof ASN1Set) {
            ASN1Set set = (ASN1Set) asn1;
            structure.setTag("SET");
            structure.setTagNumber(17);
            structure.setTagClass("UNIVERSAL");
            structure.setType("SET");
            structure.setLength(set.size());
            structure.setOffset(offset);
            structure.setValue(set.size() + " 个元素");

            List<Asn1ParseResponse.Asn1Structure> children = new ArrayList<>();
            for (Enumeration<?> e = set.getObjects(); e.hasMoreElements(); ) {
                children.add(parseStructure((ASN1Primitive) e.nextElement(), offset, verbose));
            }
            structure.setChildren(children);

        } else if (asn1 instanceof ASN1Integer) {
            ASN1Integer integer = (ASN1Integer) asn1;
            structure.setTag("INTEGER");
            structure.setTagNumber(2);
            structure.setTagClass("UNIVERSAL");
            structure.setType("INTEGER");
            structure.setValue(integer.getValue().toString());
            structure.setOffset(offset);

        } else if (asn1 instanceof ASN1OctetString) {
            ASN1OctetString octetString = (ASN1OctetString) asn1;
            structure.setTag("OCTET STRING");
            structure.setTagNumber(4);
            structure.setTagClass("UNIVERSAL");
            structure.setType("OCTET STRING");
            structure.setValue("0x" + bytesToHex(octetString.getOctets()));
            structure.setLength(octetString.getOctets().length);
            structure.setOffset(offset);

        } else if (asn1 instanceof DERUTF8String) {
            DERUTF8String utf8String = (DERUTF8String) asn1;
            structure.setTag("UTF8String");
            structure.setTagNumber(12);
            structure.setTagClass("UNIVERSAL");
            structure.setType("UTF8String");
            structure.setValue(utf8String.getString());
            structure.setOffset(offset);

        } else if (asn1 instanceof DERPrintableString) {
            DERPrintableString printableString = (DERPrintableString) asn1;
            structure.setTag("PrintableString");
            structure.setTagNumber(19);
            structure.setTagClass("UNIVERSAL");
            structure.setType("PrintableString");
            structure.setValue(printableString.getString());
            structure.setOffset(offset);

        } else if (asn1 instanceof ASN1ObjectIdentifier) {
            ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) asn1;
            structure.setTag("OBJECT IDENTIFIER");
            structure.setTagNumber(6);
            structure.setTagClass("UNIVERSAL");
            structure.setType("OBJECT IDENTIFIER");
            structure.setValue(oid.getId());
            structure.setOffset(offset);

        } else if (asn1 instanceof ASN1BitString) {
            ASN1BitString bitString = (ASN1BitString) asn1;
            structure.setTag("BIT STRING");
            structure.setTagNumber(3);
            structure.setTagClass("UNIVERSAL");
            structure.setType("BIT STRING");
            structure.setValue("0x" + bytesToHex(bitString.getBytes()));
            structure.setLength(bitString.getBytes().length);
            structure.setOffset(offset);

        } else {
            structure.setTag("UNKNOWN");
            structure.setTagNumber(-1);
            structure.setTagClass("UNKNOWN");
            structure.setType("UNKNOWN");
            structure.setValue(asn1.toString());
            structure.setOffset(offset);
        }

        // 添加详细属性
        if (verbose) {
            Map<String, Object> properties = new HashMap<>();
            properties.put("className", asn1.getClass().getSimpleName());
            structure.setProperties(properties);
        }

        return structure;
    }

    private String getTagClass(int tagClass) {
        switch (tagClass) {
            case DERTags.UNIVERSAL: return "UNIVERSAL";
            case DERTags.APPLICATION: return "APPLICATION";
            case DERTags.CONTEXT_SPECIFIC: return "CONTEXT_SPECIFIC";
            case DERTags.PRIVATE: return "PRIVATE";
            default: return "UNKNOWN";
        }
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }

    private Map<String, Object> createMetadata(byte[] data, String encodingType) {
        Map<String, Object> metadata = new HashMap<>();
        metadata.put("originalLength", data.length);
        metadata.put("encodingType", encodingType);
        metadata.put("encodingTimestamp", System.currentTimeMillis());

        // 检测可能的编码规则
        String probableEncoding = detectEncodingRule(data);
        metadata.put("probableEncoding", probableEncoding);

        return metadata;
    }

    private String detectEncodingRule(byte[] data) {
        // 简化的编码规则检测
        if (data.length > 0) {
            byte firstByte = data[0];
            if ((firstByte & 0x1F) == 0x10) { // SEQUENCE tag
                if (isDerCompliant(data)) {
                    return "DER (Distinguished Encoding Rules)";
                } else {
                    return "BER (Basic Encoding Rules)";
                }
            }
        }
        return "Unknown";
    }

    private boolean isDerCompliant(byte[] data) {
        // 简化的DER合规性检查
        // DER要求长度字段使用最短形式
        if (data.length >= 2) {
            byte lengthByte = data[1];
            if ((lengthByte & 0x80) != 0) {
                int lengthBytes = lengthByte & 0x7F;
                // 检查是否使用了最短形式
                if (lengthBytes == 1) {
                    return (data[2] & 0x80) != 0;
                }
            }
        }
        return true;
    }
}

5. 异常处理

package com.example.asn1.exception;

import lombok.Getter;

@Getter
public class Asn1ParseException extends RuntimeException {

    private final String errorCode;

    public Asn1ParseException(String message) {
        super(message);
        this.errorCode = "ASN1_PARSE_ERROR";
    }

    public Asn1ParseException(String message, Throwable cause) {
        super(message, cause);
        this.errorCode = "ASN1_PARSE_ERROR";
    }

    public Asn1ParseException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }
}

6. 控制器

package com.example.asn1.controller;

import com.example.asn1.dto.Asn1ParseRequest;
import com.example.asn1.dto.Asn1ParseResponse;
import com.example.asn1.service.Asn1ParserService;
import com.example.asn1.exception.Asn1ParseException;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import jakarta.validation.Valid;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/asn1")
@Validated
public class Asn1Controller {

    private final Asn1ParserService asn1ParserService;

    public Asn1Controller(Asn1ParserService asn1ParserService) {
        this.asn1ParserService = asn1ParserService;
    }

    @PostMapping("/parse")
    public ResponseEntity<Asn1ParseResponse> parseAsn1(@Valid @RequestBody Asn1ParseRequest request) {
        try {
            Asn1ParseResponse response = asn1ParserService.parseAsn1Data(
                request.getAsn1Data(),
                request.getEncodingType(),
                request.isVerbose()
            );
            return ResponseEntity.ok(response);
        } catch (Asn1ParseException e) {
            Asn1ParseResponse errorResponse = new Asn1ParseResponse();
            errorResponse.setSuccess(false);
            errorResponse.setMessage(e.getMessage());
            errorResponse.setRootStructure(null);
            errorResponse.setWarnings(null);
            errorResponse.setMetadata(Map.of("errorCode", e.getErrorCode()));
            return ResponseEntity.badRequest().body(errorResponse);
        }
    }

    @GetMapping("/info")
    public ResponseEntity<Map<String, Object>> getAsn1Info() {
        Map<String, Object> info = new HashMap<>();
        info.put("application", "ASN.1在线解析工具");
        info.put("version", "1.0.0");
        info.put("supportedEncodings", new String[]{"HEX", "BASE64", "RAW"});
        info.put("supportedTypes", new String[]{
            "SEQUENCE", "SET", "INTEGER", "OCTET STRING",
            "UTF8String", "PrintableString", "OBJECT IDENTIFIER",
            "BIT STRING", "TAGGED"
        });
        return ResponseEntity.ok(info);
    }
}

7. 前端界面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ASN.1在线解析工具</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <header>
            <h1>ASN.1在线解析工具</h1>
            <p>支持HEX、Base64等格式的ASN.1数据解析</p>
        </header>

        <main>
            <section class="input-section">
                <h2>输入ASN.1数据</h2>

                <div class="encoding-selector">
                    <label for="encodingType">编码类型:</label>
                    <select id="encodingType">
                        <option value="HEX">HEX</option>
                        <option value="BASE64">Base64</option>
                        <option value="RAW">Raw</option>
                    </select>
                </div>

                <div class="textarea-container">
                    <textarea id="asn1Input" placeholder="请输入ASN.1数据(HEX格式,例如:308201023081d9...)"></textarea>
                    <div class="sample-data">
                        <h3>示例数据:</h3>
                        <button class="sample-btn" data-sample="certificate">X.509证书示例</button>
                        <button class="sample-btn" data-sample="sequence">SEQUENCE示例</button>
                        <button class="sample-btn" data-sample="integer">INTEGER示例</button>
                    </div>
                </div>

                <div class="options">
                    <label>
                        <input type="checkbox" id="verbose">
                        详细输出
                    </label>
                </div>

                <button id="parseBtn" class="parse-btn">解析ASN.1</button>
            </section>

            <section class="result-section">
                <h2>解析结果</h2>
                <div id="resultContainer" class="result-container">
                    <div class="placeholder">解析结果将在这里显示...</div>
                </div>
            </section>
        </main>

        <footer>
            <p>基于SpringBoot和BouncyCastle实现 | 支持BER、DER等编码规则</p>
        </footer>
    </div>

    <script src="script.js"></script>
</body>
</html>
// script.js
document.addEventListener('DOMContentLoaded', function() {
    const asn1Input = document.getElementById('asn1Input');
    const encodingType = document.getElementById('encodingType');
    const verboseCheckbox = document.getElementById('verbose');
    const parseBtn = document.getElementById('parseBtn');
    const resultContainer = document.getElementById('resultContainer');

    // 示例数据
    const sampleData = {
        certificate: '308201023081d9...(实际的X.509证书HEX数据)',
        sequence: '3009020101020101020101',
        integer: '020101'
    };

    // 示例数据按钮事件
    document.querySelectorAll('.sample-btn').forEach(btn => {
        btn.addEventListener('click', function() {
            const sampleType = this.dataset.sample;
            if (sampleData[sampleType]) {
                asn1Input.value = sampleData[sampleType];
                asn1Input.focus();
            }
        });
    });

    // 解析按钮事件
    parseBtn.addEventListener('click', parseAsn1);

    // 输入框回车事件
    asn1Input.addEventListener('keydown', function(e) {
        if (e.key === 'Enter' && e.ctrlKey) {
            parseAsn1();
        }
    });

    async function parseAsn1() {
        const data = asn1Input.value.trim();

        if (!data) {
            showError('请输入ASN.1数据');
            return;
        }

        // 显示加载状态
        parseBtn.innerHTML = '<span class="loading"></span>解析中...';
        parseBtn.disabled = true;

        try {
            const response = await fetch('/api/asn1/parse', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    asn1Data: data,
                    encodingType: encodingType.value,
                    verbose: verboseCheckbox.checked
                })
            });

            const result = await response.json();

            if (result.success) {
                showSuccess(result);
            } else {
                showError(result.message);
            }
        } catch (error) {
            showError('网络错误或服务器异常: ' + error.message);
        } finally {
            // 恢复按钮状态
            parseBtn.innerHTML = '解析ASN.1';
            parseBtn.disabled = false;
        }
    }

    function showSuccess(result) {
        let html = '<div class="success-message">✓ ' + result.message + '</div>';

        if (result.rootStructure) {
            html += renderStructure(result.rootStructure);
        }

        if (result.metadata) {
            html += '<div style="margin-top: 20px;"><h3>元数据</h3><div class="structure-details">';
            for (const [key, value] of Object.entries(result.metadata)) {
                html += `<div><strong>${key}:</strong> ${value}</div>`;
            }
            html += '</div></div>';
        }

        if (result.warnings && result.warnings.length > 0) {
            html += '<div style="margin-top: 15px;"><h3>警告</h3><div style="color: #f39c12;">';
            result.warnings.forEach(warning => {
                html += `<div>⚠ ${warning}</div>`;
            });
            html += '</div></div>';
        }

        resultContainer.innerHTML = html;
    }

    function showError(message) {
        resultContainer.innerHTML = `<div class="error-message">✗ ${message}</div>`;
    }

    function renderStructure(structure, level = 0) {
        const indent = '  '.repeat(level);
        const tagClass = getTagClass(structure.tagClass);

        let html = `
            <div class="structure-item" style="margin-left: ${level * 20}px;">
                <div class="structure-header">
                    <span class="tag-info tag-${tagClass}">${structure.tagClass}</span>
                    <span>${structure.tag}</span>
                    ${structure.tagNumber >= 0 ? `[${structure.tagNumber}]` : ''}
                </div>
                <div class="structure-details">
                    <div><strong>类型:</strong> ${structure.type}</div>
                    <div><strong>值:</strong> ${structure.value}</div>
                    ${structure.length ? `<div><strong>长度:</strong> ${structure.length}</div>` : ''}
                    ${structure.offset ? `<div><strong>偏移:</strong> 0x${structure.offset.toString(16).toUpperCase()}</div>` : ''}
                </div>
        `;

        if (structure.properties) {
            html += '<div class="structure-details">';
            for (const [key, value] of Object.entries(structure.properties)) {
                html += `<div><em>${key}:</em> ${value}</div>`;
            }
            html += '</div>';
        }

        html += '</div>';

        if (structure.children && structure.children.length > 0) {
            html += '<div class="structure-children">';
            structure.children.forEach(child => {
                html += renderStructure(child, level + 1);
            });
            html += '</div>';
        }

        return html;
    }

    function getTagClass(tagClass) {
        switch (tagClass) {
            case 'UNIVERSAL': return 'universal';
            case 'APPLICATION': return 'application';
            case 'CONTEXT_SPECIFIC': return 'context';
            case 'PRIVATE': return 'private';
            default: return 'unknown';
        }
    }
});

8. 应用主类

package com.example.asn1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Asn1ParserApplication {

    public static void main(String[] args) {
        SpringApplication.run(Asn1ParserApplication.class, args);
    }
}

测试使用

1. 测试X.509证书解析

# 启动应用
mvn spring-boot:run

# 测试证书解析
curl -X POST http://localhost:8080/api/asn1/parse \
  -H "Content-Type: application/json" \
  -d '{
    "asn1Data": "308201023081d9...",
    "encodingType": "HEX",
    "verbose": true
  }'

2. 前端测试

访问 http://localhost:8080 即可使用Web界面进行ASN.1数据解析。

总结

ASN.1在通信、安全、物联网等专业领域有着重要作用。理解ASN.1的原理和应用场景,有助于我们在特定项目中选择合适的数据格式。

这个工具不仅可以帮助开发者理解ASN.1数据结构,还可以用于调试和验证ASN.1编码的数据。在实际项目中,你可以根据需要进一步扩展功能。

github.com/yuboon/java…