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

前言

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

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

2. 密码学和安全

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

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

3. 物联网和工业控制

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

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

4. 医疗领域

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

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

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

css 复制代码
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 复制代码
<?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. 数据传输对象

java 复制代码
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;
}
java 复制代码
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. 解析服务

java 复制代码
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. 异常处理

java 复制代码
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. 控制器

java 复制代码
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. 前端界面

html 复制代码
<!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>
javascript 复制代码
// 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. 应用主类

java 复制代码
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证书解析

bash 复制代码
# 启动应用
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...

相关推荐
JaguarJack17 小时前
深入理解 PHP-FPM 的最佳配置
后端·php
Kiri霧18 小时前
在actix-web应用用构建集成测试
后端·rust·集成测试
Victor35618 小时前
Redis(67)Redis的SETNX命令是如何工作的?
后端
Victor35618 小时前
Redis(66)Redis如何实现分布式锁?
后端
凤山老林19 小时前
新一代Java应用日志可视化与监控系统开源啦
java·后端·开源
Kiri霧1 天前
Rust开发环境搭建
开发语言·后端·rust
间彧1 天前
Spring事件监听与消息队列(如Kafka)在实现解耦上有何异同?
后端
间彧1 天前
Java如何自定义事件监听器,有什么应用场景
后端
叶梅树1 天前
从零构建A股量化交易工具:基于Qlib的全栈系统指南
前端·后端·算法