如何用Bouncy Castle实现CDR转ASCII及解决AIR记录ASN.1解码字段遍历问题
解决ASN.1 AIR记录遍历与CDR转ASCII问题
我看了你的代码和遇到的问题,核心是没处理好ASN.1的嵌套结构,以及对TaggedObject的理解有误。咱们一步步来解决这两个问题:
一、遍历ASN.1 AIR记录的所有字段
你的代码里有个关键错误:在获取第一个DERTaggedObject后,循环x < obj.getTagNo()完全不对——getTagNo()返回的是这个标签的编号(比如1、2这类标识字段的ID),不是元素数量,这就是你没法遍历后续字段的原因。
ASN.1结构是嵌套的,顶层Sequence里的元素可能又是TaggedObject、子Sequence等,所以必须用递归遍历来处理所有层级的字段。我给你重写了代码,加上递归遍历的逻辑:
import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.bouncycastle.asn1.*; import org.bouncycastle.util.encoders.Hex; public class AIRDecoder { private ASN1InputStream asnIn; // 可选:根据AIR的ASN.1定义,给标签编号映射字段名称,输出更直观 private static final Map<Integer, String> TAG_FIELD_MAP = new HashMap<>(); static { TAG_FIELD_MAP.put(1, "AccountID"); TAG_FIELD_MAP.put(2, "RechargeAmount"); TAG_FIELD_MAP.put(3, "RechargeTimestamp"); // 按你的AIR补充其他标签对应的字段名 } public void decodeAIR(byte[] rawData) throws IOException { asnIn = new ASN1InputStream(new ByteArrayInputStream(rawData)); ASN1Primitive topObj = asnIn.readObject(); // AIR记录顶层一般是TaggedObject,先解析它 if (topObj instanceof ASN1TaggedObject) { ASN1TaggedObject airRecord = (ASN1TaggedObject) topObj; System.out.println("=== AIR Record Start ==="); traverseASN1Element(airRecord.getObject(), 0); System.out.println("=== AIR Record End ==="); } else { System.err.println("Invalid AIR record format: top element is not a TaggedObject"); } } // 递归遍历所有ASN.1元素的核心方法 private void traverseASN1Element(ASN1Encodable element, int indent) { String indentStr = " ".repeat(indent); if (element instanceof ASN1Sequence) { ASN1Sequence sequence = (ASN1Sequence) element; System.out.printf("%s[SEQUENCE] (包含 %d 个元素)%n", indentStr, sequence.size()); for (int i = 0; i < sequence.size(); i++) { System.out.printf("%s 第 %d 个元素: ", indentStr, i+1); traverseASN1Element(sequence.getObjectAt(i), indent + 2); } } else if (element instanceof ASN1TaggedObject) { ASN1TaggedObject taggedObj = (ASN1TaggedObject) element; int tagNo = taggedObj.getTagNo(); String fieldName = TAG_FIELD_MAP.getOrDefault(tagNo, "未知字段"); System.out.printf("%s[标签字段] 编号: %d (%s), 显式标签: %b%n", indentStr, tagNo, fieldName, taggedObj.isExplicit()); // 递归解析标签内的实际内容 traverseASN1Element(taggedObj.getObject(), indent + 2); } else { // 处理基本类型(字符串、整数、二进制等) ASN1Primitive primitive = element.toASN1Primitive(); System.out.printf("%s[基础类型] %s%n", indentStr, primitive.getClass().getSimpleName()); // 尝试转换为ASCII可读格式 if (primitive instanceof ASN1UTF8String) { String asciiVal = ((ASN1UTF8String) primitive).getString(); System.out.printf("%s ASCII值: %s%n", indentStr, asciiVal); } else if (primitive instanceof ASN1Integer) { long intVal = ((ASN1Integer) primitive).getValue().longValue(); System.out.printf("%s 数值: %d%n", indentStr, intVal); } else if (primitive instanceof ASN1OctetString) { byte[] octetBytes = ((ASN1OctetString) primitive).getOctets(); try { String asciiVal = new String(octetBytes, "US-ASCII"); System.out.printf("%s ASCII值(二进制转): %s%n", indentStr, asciiVal); } catch (Exception e) { // 转ASCII失败就输出十六进制 System.out.printf("%s 十六进制值: %s%n", indentStr, Hex.toHexString(octetBytes)); } } } } }
代码说明:
- 新增
traverseASN1Element递归方法,能处理所有ASN.1类型(Sequence、TaggedObject、基础类型),不管嵌套多少层都能遍历到。 - 加入了标签到字段名的映射,输出更易读;对不同基础类型做了针对性的ASCII转换。
二、将CDR转换为ASCII格式
CDR转ASCII本质就是把每个ASN.1字段的内容解析后,以可读的ASCII格式输出,上面的代码已经实现了核心逻辑。如果需要更规整的结构化ASCII输出(比如键值对或JSON),可以在递归遍历的时候收集字段数据,最后统一输出:
比如添加一个Map<String, Object>来存储解析后的字段:
private Map<String, Object> cdrData = new HashMap<>(); // 在traverseASN1Element处理TaggedObject时,把字段值存入map if (primitive instanceof ASN1UTF8String) { String asciiVal = ((ASN1UTF8String) primitive).getString(); cdrData.put(fieldName, asciiVal); } else if (primitive instanceof ASN1Integer) { long intVal = ((ASN1Integer) primitive).getValue().longValue(); cdrData.put(fieldName, intVal); }
最后直接输出这个map的ASCII格式:
System.out.println("=== CDR ASCII格式 ==="); cdrData.forEach((key, value) -> System.out.printf("%s: %s%n", key, value));
这样就能得到清晰的键值对格式ASCII输出。
内容的提问来源于stack exchange,提问作者wisam shaheen
相关产品推荐
相关产品推荐

