自动标签与显式编码及隐式编码

纽扣第一颗就扣错了,可你扣到最后一颗才发现,有些事一开始就是错误

Posted by yishuifengxiao on 2025-04-27

类型与类型定义

类型列表

基础类型的标签号

Tag 类型 Tag 类型 Tag 类型
1 (hex:01) BER保留 12(hex:0C) UTF8String 23(hex:17) UTCTime
2 (hex:02) BOOLEAN 13(hex:0D) RELATIVE-OID 24(hex:18) GeneralizedTime
3(hex:03) INTEGER 14(hex:0E) 保留 25(hex:10) GraphicString
4(hex:04) BIT STRING 15(hex:0F) 保留 26(hex:1A) VisibleString,ISO646String
5(hex:05) NULL 16(hex:10) SEQUENCE,SEQUENCE OF 27(hex:1B) GeneralString
6(hex:06) OBJECT IDENTIFIER 17(hex:11) SET,SET OF 28(hex:1C) UniversalString
7(hex:07) ObjectDescripion 18(hex:12) NumericString 29(hex:1D) CHARACTER STRING
8(hex:08) EXTERNAL,INSTANCE OF 19(hex:13) PrintableString 30(hex:1E) BMPString
9(hex:09) REAL 20(hex:14) TeletexString,T61String 31(hex:1F) 保留
10(hex:0A) ENUMERATED 21(hex:15) VideotexString
11(hex:0B) EMBEDDED PDV 22(hex:16) IA5String

Tag Class(标签类别)

标签类别用于标识标签的上下文,共有 4 种类别:

类别 值(二进制) 值(十六进制) 描述
Universal 00 0x00 通用标签,用于 ASN.1 内置类型(如 INTEGERSEQUENCE 等)。
Application 01 0x40 应用标签,由应用程序定义。
Context-specific 10 0x80 上下文特定标签,用于特定上下文中定义的类型。
Private 11 0xC0 私有标签,由私有协议定义。
  • 如果标签是 通用类型(如 INTEGERSEQUENCE),则 Tag Class = 0x00
  • 如果标签是 应用类型(如 [APPLICATION n]),则 Tag Class = 0x40
  • 如果标签是 上下文特定类型(如 [n]),则 Tag Class = 0x80
  • 如果标签是 私有类型,则 Tag Class = 0xC0

Primitive/Constructed(原始/构造)

Primitive/Constructed 用于标识数据的编码方式:

类型 值(二进制) 值(十六进制) 描述
Primitive 0 0x00 原始类型,表示数据是简单的值(如 INTEGEROCTET STRING)。
Constructed 1 0x20 构造类型,表示数据是复杂的结构(如 SEQUENCESEQUENCE OF)。
  • 如果数据类型是 原始类型(如 INTEGEROCTET STRING),则 Primitive/Constructed = 0x00
  • 如果数据类型是 构造类型(如 SEQUENCESET),则 Primitive/Constructed = 0x20

ASN.1中的类型

内建数据类型

类型 含义
NULL 只包含一个值NULL,用于传送一个报告或者作为CHOICE类型中某些值
INTEGER 全部整数(包括正数和负数)
REAL 实数,表示浮点数
ENUMERATED 标识符的枚举(实例状态机的状态)
BIT STRING 比特串
OCTET STRING 字节串
OBJECT IDENTIFIER,RELATIVE-OID 一个实体的标识符,它在一个全世界范围树状结构中注册
EXTERNAL, EMBEDDED PDV 表示层上下文交换类型
…String(除了BIT STRING、OCTET STRING外) 各种字符串,有NumericString、PrintableString、VisibleStirng、ISO64String、IA5String、TeletexStirng、T61String、VideotexString、GraphicString、GeneralString、UniversalString、BMPString和UTF8String
CHARACTER STRING 允许为字符串协商一个明确的字符表
UTCTime, GeneralizedTime 日期

组合类型

类型 含义
CHOICE 在类型中选择(相当于C中的联合)
SEQUENCE 由不同类型的值组成一个有序的结构(相当于C中的结构体)
SET 由不同类型的值组成一个无序的结构
SEQUENCE OF 由相同类型的值组成一个有序的结构(相当于C中的数组)
SET OF 由相同类型的值组成一个无序的结构

基本类型

类型 UNIVERSAL Tag 取值
BOOLEAN 1 TRUE,FALSE
NULL 5 NULL
INTEGER 2 整数
ENUMERATED 10 类型定义中列出的成员
REAL 9 实数
BIT STRING 3 比特串
OCTET STRING 4 八位组串,字节流
OBJECT IDENTIFIER 6
RELATIVE-OID 13

字符串类型

Known-multiple Character String Type指字符串每个字符编码所占字节数都一样。

类型名字 Tag 字符表 ESC Multi
NumericString 18 字符“0”到“9”,空格
PrintableString 19 字符“A” 到 “Z”,“a”到“z”,“0”到“9”,空格,单引号(’),圆括号(( ,)),加号(+),逗号(,),减号(-),点(.),斜杠(/),冒号(:),等号(=),问号(?)
VisibleStringISO646String 26 [ISOReg] entry no. 6; space
IA5String 22 [ISOReg] entry no. 1 & 6;space, delete
TeletexStringT61String 20 [ISOReg] entry no. 6, 87, 102,103, 106, 107, 126, 144, 150,153, 156, 164, 165, 168; space, delete
VideotexString 21 [ISOReg] entry no. 1, 13, 72,73, 87, 89, 102, 108, 126, 128,129, 144, 150, 153, 164, 165,168; space, delete
GraphicString 25 all the graphical sets (called`G’) of [ISOReg]; space
GeneralString 27 all the graphical sets (calledG') and all the control characters(calledC’) of [ISOReg];space, delete
UniversalString 28 [ISO10646-1]
BMPString 30 the basic multilingual plane[ISO10646-1] (65,536 cells)
UTF8String 12 [ISO10646-1]
类型 编码标识 备注
BMPString(UNICODE_STRING) 0x1E 基本多语言平面 (BMP) 是一种字符编码,包含通用字符集 (UCS) 的第一个平面。 共有17架编号为 0 到 16 的飞机。 BMP 占用平面 0,包含从 0x0000 到 0xFFFF 的 65,536 个码位。 这是 Unicode 字符映射的一部分,到目前为止,大多数字符分配都已在此处。 它包括拉丁语、中东语、亚洲语、非洲语和其他语言。
IA5String(IA5_STRING) 0x16 国际字母数字 5 (IA5) 通常等效于 ASCII 字母表,但不同的版本可以包括特定于区域语言的重音或其他字符。 以下示例演示 AlternativeNames 证书扩展的 ASN.1 定义中使用的 IA5String 类型
PrintableString(PRINTABLE_STRING) 0x13 PrintableString 数据类型最初旨在表示可用于大型机输入终端的有限字符集,但仍常用。 它包含以下字符:A-Z a-z 0-9 ' ( ) + , - . / : = ? [space]
TeletexString 0x14 TeletexString 和相关 T61String 数据类型在 8 位 (编码,或复合字符) 的 16 位编码。 它们都有0x14的标记号。 它们未得到广泛使用。
UTF8String(UTF8_STRING) 0x0C 8 位 UCS/Unicode 转换格式 (UTF-8) 是一种长度可变的字符编码,可以将任何通用字符表示为 Unicode 字符,同时允许初始码位与 ASCII 保持一致。 UTF-8 使用 1 到 4 个字节。 标记号为0x0C。
八进制字符串(OCTET_STRING) 0x04 八进制字符串是任意大的字节数组。 但是,与 BIT STRING 类型不同,无法为字符串中的特定位和字节分配名称。 八位字节一词是一种独立于平台的方式来引用内存词。 在证书注册 API 的上下文中,八位字节和字节可互换
BIT STRING 0x03 位或二进制字符串是任意长度的位数组。 特定位可以通过带圆括号的整数和分配的名称进行标识

字符串数据类型使用实例

对于以下ASN.1定义

MyHTTP
DEFINITIONS
AUTOMATIC TAGS ::=
BEGIN
Person ::= SEQUENCE {
id [0] INTEGER,
name UTF8String ,
age INTEGER OPTIONAL,
octelSTRING OCTET STRING,
bitSTRING BIT STRING ,
numericString NumericString ,
printableString PrintableString ,
teletexString TeletexString ,
iA5String IA5String
}
END

示例数据1

使用以下Java代码

Person person = new Person();
person.setId(1);
person.setName("张三");
person.setAge(18);
person.setOctelSTRING(new byte[]{0x01, 0x02, 0x03});
person.setBitSTRING(new BitSet(3));
person.setNumericString("123");
person.setPrintableString("345");
person.setTeletexString("78");
person.setIA5String("9");

生成的数据如下:

30 (27)
80 (01) 01 # id [0] INTEGER :1
0C (06) E5BCA0E4B889 # name UTF8String :张三
02 (01) 12 # age INTEGER OPTIONAL :18
04 (03) 010203 # octelSTRING OCTET STRING :0x01, 0x02, 0x03
03 (01) 00 # BIT STRING :BitSet(3)
12 (03) 313233 # NumericString :123
13 (03) 333435 # PrintableString :345
14 (02) 3738 # TeletexString :78
16 (01) 39 # IA5String :9

示例数据2

对于另一组数据

Person person = new Person();
person.setId(1);
person.setName("张三");
person.setAge(18);
person.setOctelSTRING(new byte[]{0x01, 0x02, 0x03});
person.setBitSTRING(new BitSet(3));
person.setNumericString("AAAA");
person.setPrintableString("BBB");
person.setTeletexString("CCCC");
person.setIA5String("GGGG");

得到的数据如下

原始的hex= 302D8001010C06E5BCA0E4B88902011204030102030301001204414141411303424242140443434343160447474747
格式化后的TLV结构:
30 (2D)
80 (01) 01 # id [0] INTEGER :1
0C (06) E5BCA0E4B889 # name UTF8String :张三
02 (01) 12 # age INTEGER OPTIONAL :18
04 (03) 010203 # octelSTRING OCTET STRING :0x01, 0x02, 0x03
03 (01) 00 # BIT STRING :BitSet(3)
12 (04) 41414141 # NumericString :AAAA
13 (03) 424242 # PrintableString :BBB
14 (04) 43434343 # TeletexString :CCCC
16 (04) 47474747 # IA5String :GGGG

示例数据3

Person person = new Person();
person.setId(1);
person.setName("张三");
person.setAge(18);
person.setOctelSTRING(new byte[]{0x01, 0x02, 0x03});
person.setBitSTRING(new BitSet(3));
person.setNumericString("数字字符串");
person.setPrintableString("可打印字符串");
person.setTeletexString("电传字符串");
person.setIA5String("IA5字符串");

得到的数据结果如下

原始的hex= 305A8001010C06E5BCA0E4B8890201120403010203030100120FE695B0E5AD97E5AD97E7ACA6E4B8B21312E58FAFE68993E58DB0E5AD97E7ACA6E4B8B2140FE794B5E4BCA0E5AD97E7ACA6E4B8B2160C494135E5AD97E7ACA6E4B8B2
格式化后的TLV结构:
30 (5A)
80 (01) 01
0C (06) E5BCA0E4B889
02 (01) 12
04 (03) 010203
03 (01) 00
12 (0F) E695B0E5AD97E5AD97E7ACA6E4B8B2
13 (12) E58FAFE68993E58DB0E5AD97E7ACA6E4B8B2
14 (0F) E794B5E4BCA0E5AD97E7ACA6E4B8B2
16 (0C) 494135E5AD97E7ACA6E4B8B2

UTF8String

UTF8String “张三” 转换为 hex E5BCA0E4B889

  • UTF8String 使用UTF-8编码,这是一种可变长度编码,支持所有Unicode字符。
  • “张” 的UTF-8编码:
    • “张” 的Unicode码点是U+5F20。
    • UTF-8编码规则:对于U+0800到U+FFFF的字符,使用3字节:1110xxxx 10xxxxxx 10xxxxxx
    • U+5F20的二进制:0101 1111 0010 0000。
    • 分割为三部分:0101、111100、100000(从高位到低位)。
    • 编码后:
      • 第一个字节:11100101 → 0xE5
      • 第二个字节:10111100 → 0xBC
      • 第三个字节:10100000 → 0xA0
    • 所以 “张” 的UTF-8编码为 E5 BC A0
  • “三” 的UTF-8编码:
    • “三” 的Unicode码点是U+4E09。
    • U+4E09的二进制:0100 1110 0000 1001。
    • 分割为三部分:0100、111000、001001。
    • 编码后:
      • 第一个字节:11100100 → 0xE4
      • 第二个字节:10111000 → 0xB8
      • 第三个字节:10001001 → 0x89
    • 所以 “三” 的UTF-8编码为 E4 B8 89
  • 因此,”张三” 的完整hex编码为 E5 BC A0 E4 B8 89,即 E5BCA0E4B889(长度6字节)。

NumericString

NumericString “AAAA” 转换为 hex 41414141

  • NumericString 理论上只允许数字(0-9)和空格,但实际编码中有时会处理其他字符(如字母),但不符合标准。在您的示例中,”AAAA” 被编码为NumericString,这可能是一种简化或错误使用。

  • 转换基于ASCII编码:每个字符的ASCII值直接转换为hex。

    • ‘A’ 的ASCII值为十进制65,十六进制41。
    • 因此,”AAAA” 的hex编码为 41 41 41 41,即 41414141(长度4字节)。

NumericString类型的数据123对应的hex值为313233

在ASN.1编码中,NumericString类型的数据使用ASCII编码来表示每个字符。ASCII(American Standard Code for Information Interchange)是一种常见的字符编码标准,其中每个字符对应一个唯一的数字值(十六进制)。

对于数字字符”123”:

  • 字符 ‘1’ 的ASCII值为十进制49,十六进制31。
  • 字符 ‘2’ 的ASCII值为十进制50,十六进制32。
  • 字符 ‘3’ 的ASCII值为十进制51,十六进制33。

    因此,字符串”123”在编码为NumericString时,其值部分直接转换为hex值313233。

    详细转换过程:

  1. NumericString类型定义:NumericString只允许包含数字(0-9)和空格。在ASN.1编码中,它使用标签0x12(但标签本身不包含在值部分中)。

  2. 编码规则:当编码NumericString时,每个字符都使用其ASCII值的十六进制形式表示。

  3. 示例”123”

    • 取每个字符的ASCII值:’1’ → 0x31, ‘2’ → 0x32, ‘3’ → 0x33。
    • 组合这些值得到313233。

NumericString “数字字符串” 转换为 hex E695B0E5AD97E5AD97E7ACA6E4B8B2

  • 字符串: “数字字符串”(注意:根据hex值解码,实际字符串可能为”数字符串”或包含重复字符,但这里以您提供的字符串为准)。
  • 编码规则: NumericString标准只允许数字(0-9)和空格,但这里中文字符使用了UTF-8编码。
  • 转换过程
    • 每个中文字符转换为UTF-8字节序列:
      • “数” -> UTF-8: E6 95 B0
      • “字” -> UTF-8: E5 AD 97
      • “符” -> UTF-8: E7 AC A6
      • “串” -> UTF-8: E4 B8 B2
    • 最终hex值:E695B0 (数) + E5AD97 (字) + E5AD97 (字) + E7ACA6 (符) + E4B8B2 (串) = E695B0E5AD97E5AD97E7ACA6E4B8B2
  • 说明: 这不符合NumericString标准,建议使用UTF8String中文字符。

PrintableString

PrintableString “BBB” 转换为 hex 424242

  • PrintableString 允许特定的可打印字符,包括大写字母(A-Z)、数字(0-9)和某些符号(如空格、单引号、括号等)。
  • 转换基于ASCII编码:每个字符的ASCII值直接转换为hex。
    • ‘B’ 的ASCII值为十进制66,十六进制42。
    • 因此,”BBB” 的hex编码为 42 42 42,即 424242(长度3字节)。

PrintableString “可打印字符串” 转换为 hex E58FAFE68993E58DB0E5AD97E7ACA6E4B8B2

  • 字符串: “可打印字符串”
  • 编码规则: PrintableString标准只允许可打印ASCII字符(如字母、数字、空格和特定符号),但这里中文字符使用了UTF-8编码。
  • 转换过程
    • 每个中文字符转换为UTF-8字节序列:
      • “可” -> UTF-8: E5 8F AF
      • “打” -> UTF-8: E6 89 93
      • “印” -> UTF-8: E5 8D B0
      • “字” -> UTF-8: E5 AD 97
      • “符” -> UTF-8: E7 AC A6
      • “串” -> UTF-8: E4 B8 B2
    • 最终hex值:E58FAF (可) + E68993 (打) + E58DB0 (印) + E5AD97 (字) + E7ACA6 (符) + E4B8B2 (串) = E58FAFE68993E58DB0E5AD97E7ACA6E4B8B2
  • 说明: 这不符合PrintableString标准,建议使用UTF8String中文字符。

TeletexString

TeletexString “CCCC” 转换为 hex 43434343

  • TeletexString 通常使用ISO/IEC 8859-1编码(Latin-1),这是一种8位编码,与ASCII兼容(前128字符相同)。
  • 转换基于ASCII或Latin-1编码:每个字符的编码值直接转换为hex。
    • ‘C’ 的ASCII值为十进制67,十六进制43。
    • 因此,”CCCC” 的hex编码为 43 43 43 43,即 43434343(长度4字节)。

TeletexString “电传字符串” 转换为 hex E794B5E4BCA0E5AD97E7ACA6E4B8B2

  • 字符串: “电传字符串”
  • 编码规则: TeletexString标准通常使用T.61或ISO-8859-1编码,但这里中文字符使用了UTF-8编码。
  • 转换过程
    • 每个中文字符转换为UTF-8字节序列:
      • “电” -> UTF-8: E7 94 B5
      • “传” -> UTF-8: E4 BC A0
      • “字” -> UTF-8: E5 AD 97
      • “符” -> UTF-8: E7 AC A6
      • “串” -> UTF-8: E4 B8 B2
    • 最终hex值:E794B5 (电) + E4BCA0 (传) + E5AD97 (字) + E7ACA6 (符) + E4B8B2 (串) = E794B5E4BCA0E5AD97E7ACA6E4B8B2

IA5String

IA5String “GGGG” 转换为 hex 47474747

  • IA5String 使用ASCII编码,支持国际字母数字字符(但实际是ASCII子集)。
  • 转换基于ASCII编码:每个字符的ASCII值直接转换为hex。
    • ‘G’ 的ASCII值为十进制71,十六进制47。
    • 因此,”GGGG” 的hex编码为 47 47 47 47,即 47474747(长度4字节)。

IA5String “IA5字符串” 转换为 hex 494135E5AD97E7ACA6E4B8B2

  • 字符串: “IA5字符串”
  • 编码规则: IA5String标准只允许ASCII字符(0-127),但这里中文字符使用了UTF-8编码,而英文字符”IA5”使用ASCII编码。
  • 转换过程
    • 英文字符部分”IA5”使用ASCII编码:
      • “I” -> ASCII: 49 (hex)
      • “A” -> ASCII: 41 (hex)
      • “5” -> ASCII: 35 (hex)
    • 中文字符部分”字符串”使用UTF-8编码:
      • “字” -> UTF-8: E5 AD 97
      • “符” -> UTF-8: E7 AC A6
      • “串” -> UTF-8: E4 B8 B2
    • 最终hex值:49 (I) + 41 (A) + 35 (5) + E5AD97 (字) + E7ACA6 (符) + E4B8B2 (串) = 494135E5AD97E7ACA6E4B8B2
  • 说明: 这不符合IA5String标准,因为中文字符不是ASCII。建议将整个字符串使用UTF8String编码

整体ASN.1数据实例解析

您提供的hex数据是一个SEQUENCE(标签30),总长度2D(十进制45字节)。内部字段包括:

  • 80 01 01:上下文特定标签[0],长度1,值01(整数1)。
  • 0C 06 E5BCA0E4B889:UTF8String(标签0C),长度6,值”张三”的UTF-8编码。
  • 02 01 12:INTEGER(标签02),长度1,值12(十进制18)。
  • 04 03 010203:OCTET STRING(标签04),长度3,值010203。
  • 03 01 00:BIT STRING(标签03),长度1,值00(表示3比特位,但值为0)。
  • 12 04 41414141:NumericString(标签12),长度4,值”AAAA”的ASCII编码。
  • 13 03 424242:PrintableString(标签13),长度3,值”BBB”的ASCII编码。
  • 14 04 43434343:TeletexString(标签14),长度4,值”CCCC”的ASCII编码。
  • 16 04 47474747:IA5String(标签16),长度4,值”GGGG”的ASCII编码。

总结

字符串到hex的转换依赖于字符的编码标准:

  • UTF8String使用UTF-8编码。
  • NumericString、PrintableString、TeletexString和IA5String通常使用ASCII编码(或Latin-1编码,但前128字符相同)。
  • 每个字符的ASCII值直接转换为十六进制字节。

转换解析示例

package org.example;

import java.nio.charset.StandardCharsets;

public class Demo {

public static void main(String[] args) {
String txt = "IA5字符串";
for (int i = 0; i < txt.length(); i++) {
String e = String.valueOf(txt.charAt(i));
byte[] bytes = e.getBytes(StandardCharsets.UTF_8);
String hex = bytesToHex(bytes);
System.out.println("字符=" + e + " ASCII=" + bytes2String(bytes) + " ASCII=" + bytes2ASCII(bytes) + " HEX=" + hex);
}

}

private static String bytes2String(byte[] bytes) {
String val = "";
for (byte b : bytes) {
val += b + " , ";
}
return val;
}

private static String bytes2ASCII(byte[] bytes) {
String val = "";
for (byte b : bytes) {
val += (b & 0xff) + " , ";
}
return val;
}

private static String bytesToHex(byte[] bytes) {
if (bytes == null) {
return null;
}
StringBuilder buff = new StringBuilder();
int len = bytes.length;
for (int j = 0; j < len; j++) {
buff.append(String.format("%02X", bytes[j] & 0xff));
}
return buff.toString().toUpperCase();
}
}

得到的运行结果为

字符=I  ASCII=73 ,   ASCII=73 ,   HEX=49
字符=A ASCII=65 , ASCII=65 , HEX=41
字符=5 ASCII=53 , ASCII=53 , HEX=35
字符=字 ASCII=-27 , -83 , -105 , ASCII=229 , 173 , 151 , HEX=E5AD97
字符=符 ASCII=-25 , -84 , -90 , ASCII=231 , 172 , 166 , HEX=E7ACA6
字符=串 ASCII=-28 , -72 , -78 , ASCII=228 , 184 , 178 , HEX=E4B8B2

UTF-8编码原理

UTF-8是一种可变长度的字符编码标准,用于表示Unicode字符。汉字“字”的Unicode码点是U+5B57(十六进制),其二进制表示为:

  • Unicode码点 U+5B57: 0101 1011 0101 0111(二进制)

根据UTF-8编码规则,对于U+0800到U+FFFF范围内的字符(如汉字),使用三字节编码模式:

  • 第一字节以1110开头,后跟Unicode码点的前4位。
  • 第二字节以10开头,后跟Unicode码点的中间6位。
  • 第三字节以10开头,后跟Unicode码点的最后6位。

具体编码过程:

  • Unicode码点 U+5B57 的二进制分割:前4位 0101,中间6位 101101,最后6位 010111
  • 第一字节:1110 + 0101 = 11100101(二进制) → 十六进制 0xE5,无符号十进制 229
  • 第二字节:10 + 101101 = 10101101(二进制) → 十六进制 0xAD,无符号十进制 173
  • 第三字节:10 + 010111 = 10010111(二进制) → 十六进制 0x97,无符号十进制 151

因此,汉字“字”的UTF-8编码为三个字节:0xE5, 0xAD, 0x97(十六进制),对应无符号十进制值 229, 173, 151

为什么显示为负数(-27, -83, -105)

在计算机系统中,字节(8位)通常可以被解释为无符号整数(范围0到255)或有符号整数(范围-128到127)。当您看到负数值时,通常是因为这些字节值被解释为有符号整数(采用二的补码表示法)。

转换规则:无符号字节值大于127时,在有符号表示中会变为负数。具体计算为:

  • 有符号值 = 无符号值 - 256(因为256是模数)

应用到这里:

  • 无符号值 229 → 有符号值: 229 - 256 = -27
  • 无符号值 173 → 有符号值: 173 - 256 = -83
  • 无符号值 151 → 有符号值: 151 - 256 = -105

因此,如果您在编程环境或工具中看到这些字节被作为有符号整数处理(例如在Java的byte类型中,默认是有符号的),它们就会显示为-27、-83、-105。但这并不影响实际的编码数据,因为存储时仍然是原始的字节值(0xE5, 0xAD, 0x97)。

为什么这些值不在ASCII码对照表中

ASCII码(American Standard Code for Information Interchange)是一种7位编码标准,只定义值从0到127的字符(包括英文字母、数字、标点符号和控制字符)。UTF-8编码的汉字使用多字节,且每个字节的值通常大于127(即超出ASCII范围),因此这些字节值不会出现在ASCII码对照表中。

  • ASCII码只覆盖0-127的值,例如字母’A’的ASCII值是65(十六进制0x41)。
  • UTF-8编码中,汉字使用三字节,每个字节的值都在128以上(如229、173、151),所以它们不是ASCII字符,而是UTF-8编码的一部分。

  • 汉字“字”的UTF-8编码字节为无符号值229、173、151(十六进制E5、AD、97)。

  • 当这些字节被解释为有符号整数时,它们显示为-27、-83、-105,这是数值表示方式的差异,并不改变实际编码。
  • 这些值不在ASCII码对照表中,因为ASCII仅限于基本英文字符,而汉字需要多字节UTF-8编码。

日期时间类型

MyHTTP
DEFINITIONS
AUTOMATIC TAGS ::=
BEGIN
Person ::= SEQUENCE {
id [0] INTEGER,
uTCTime UTCTime ,
generalizedTime GeneralizedTime
}
END

java代码如下

Person person = new Person();
person.setId(1);
person.setUTCTime("230101120000Z");
person.setGeneralizedTime("20230101120000Z");

生成的数据如下:

30 (23)
80 (01) 01
17 (0D) 3233303130313132303030305A
18 (0F) 32303233303130313132303030305A

上述数据相当于

person Person ::= {
id 123,
uTCTime "230101120000Z", -- 表示2023年1月1日12:00:00 UTC
generalizedTime "20230101120000Z" -- 表示2023年1月1日12:00:00 UTC
}
  • id: 这是一个整数字段,带有标签[0]。示例值为123,是一个任意选择的整数。
  • uTCTime: 这是一个UTCTime类型的字段,表示时间。格式为YYMMDDhhmmssZ,其中YY是年份的后两位,MM是月份,DD是日期,hh是小时,mm是分钟,ss是秒,Z表示UTC时间。示例值"230101120000Z"对应2023年1月1日12:00:00 UTC。
  • generalizedTime: 这是一个GeneralizedTime类型的字段,也表示时间。格式为YYYYMMDDhhmmssZ,其中YYYY是四位年份。示例值"20230101120000Z"同样对应2023年1月1日12:00:00 UTC。

例如 32 35 30 32 31 39 30 39 32 36 32 35 5A ;转换为ASCII字符串:250219092625Z按UTCTime格式YYMMDDHHMMSSZ

解析:

  • 25 → 年份 2025
  • 02 → 月份 2月
  • 19 → 日期 19日
  • 09 → 小时 09时
  • 26 → 分钟 26分
  • 25 → 秒 25秒
  • Z → UTC时区
    最终时间2025-02-19 09:26:25 UTC

非常好的问题!这涉及到 ASN.1 时间类型中一个非常重要且常见的细节。

Z 的含义

UTCTimeGeneralizedTime 类型中,后缀字母 Z 表示 “Zulu Time”

  • Zulu 是航空和军事领域中对 协调世界时(UTC) 的代号。
  • 因此,Z 的含义是:该时间字符串所表示的时间是 UTC 时区 的时间,其相对于 UTC 的时区偏移量为 +00:00

简单来说:Z 就等于 +00:00,代表零时区,即格林威治标准时间。

示例中的 "230101120000Z""20230101120000Z" 都明确表示这是一个 UTC 时间(2023年1月1日12:00:00 UTC)。


如何表示东八区时间(北京时间)

东八区(例如北京时间)比 UTC 时间 早8个小时。在 ASN.1 中,你不能使用 Z,而是需要使用一个 时区偏移量(Time Zone Offset) 来明确表示与 UTC 的差异。

时区偏移量有两种表示方式:

  1. +HHMM (UTC之后,东区)
  2. -HHMM (UTC之前,西区)

对于 东八区,偏移量为 +0800

因此,你的示例数据如果要表示为本地东八区时间,需要做如下修改:

UTCTime 的表示

  • 原始(UTC): "230101120000Z"
  • 东八区: "230101200000+0800"

解释:

  • 因为东八区比 UTC 早8小时,所以本地时间 20:00:00 对应的是 UTC 时间 12:00:00
  • 将时间部分从 120000 (12:00:00 UTC) 改为 200000 (20:00:00 Local)。
  • 将后缀从 Z 改为 +0800,明确指出这个 20:00:00 是东八区的本地时间。

GeneralizedTime 的表示

  • 原始(UTC): "20230101120000Z"
  • 东八区: "20230101200000+0800"

解释:

  • 同理,将时间从 120000 改为 200000
  • 将后缀 Z 替换为 +0800

总结与关键点

情况 格式示例 含义
UTC 时间 "230101120000Z""20230101120000Z" 2023年1月1日12:00:00 UTC
东八区时间 "230101200000+0800""20230101200000+0800" 2023年1月1日20:00:00 东八区(北京时间)
(这个时间点等同于UTC的12:00:00)
北美东部标准时间(UTC-5) "230101070000-0500" 2023年1月1日07:00:00 UTC-5

重要注意事项:

  1. UTCTime 的限制UTCTime 只能表示年份后两位。按照最新的规范,YY 为 50-99 表示 1950-1999,YY 为 00-49 表示 2000-2049。如果你需要表示 2050 年之后的时间,必须使用 GeneralizedTime
  2. 可读性:使用 Z 是表示 UTC 时间最简洁、最清晰的方式。只要有可能,强烈推荐将时间转换为 UTC 并使用 Z 后缀进行存储和传输,这可以避免时区转换带来的所有歧义和错误。
  3. 编码规则:在 DER(Distinguished Encoding Rules)编码中,UTCTime 必须使用 Z 格式,而不能使用本地时间加偏移量的格式。GeneralizedTime 则允许两种格式,但同样推荐使用 Z 格式以确保唯一性。

在你的代码或协议中,最佳实践是:
在内部处理和存储时,始终使用 UTC 时间。只在需要显示给用户时,才将其转换为本地时间。


类型定义

<新类型的名字> ::= <类型描述>

其中:

  • <新类型的名字>是一个以大写字母开头的标识符;
  • <类型描述>是基于内建类型或在其它地方定义的类型。

如:

Married ::= BOOLEAN
Age ::= INTEGER
Picture ::= BIT STRING
Form ::= SEQUENCE
{
name PrintableString,
age Age,
married Married,
marriage-certificate Picture OPTIONAL
}

Payment-method ::= CHOICE
{
check Check-number,
credit-card SEQUENCE
{
number Card-number,
expiry-date Date
}
}

注意:在SEQUENCESET等(好像应该是所有组合类型的)定义中,最后一个成员结尾没有逗号“,”。

为了接收方能正确解码,发送方为每个值的类型附加一个数,称为tag,在描述中以“[]”标识。缺省情况下,编码器会使用universal的tag。很多时候,缺省情况下不能消除所有的模糊性,有必要明确指出各成员的tag。如:

Coordinates ::= SET
{
x [1] INTEGER, //这证明好像也可以用类来直接声明变量
y [2] INTEGER,
z [3] INTEGER OPTIONAL
}
 
Afters ::= CHOICE
{
cheese [0] PrintableString,
dessert [1] PrintableString
}

注意:ASN.1允许递归式的类型分配(Assignment),但我们应当保证其中包含至少一个非递归的值,因为编码规则无法处理无限的值。当然,绝大多数结构类型的成员都终结于简单类型。

为了准确描述一个类型,我们需要对值的集合进行一定的限制。这用到子类型约束,在类型之后用圆括号进行标识。

如:

Lottery-number ::= INTERGER(1..49)
Lottery-draw ::= SEQUENCE SIZE(6) OF Lottery-number
Upper-case-words ::= IA5String (FROM(“A”..”Z”))
Phone-number ::= NumericString (FROM(“0”..”9”))(SIZE(10))
Coordinates-in-plan ::= Coordinates (WITH COMPONENTS {…, z ABSENT})

有约束的类型当然还是类型,可以用在任何一个可以使用类型的地方。

最后,因为版本升级,在新的描述中,出现新的成员被加入到SEQUENCESET或者CHOICE或者在上述类型基础上添加约束而衍生的子类型时,两个连接的机器(特别是在开放网络中)不一定使用的是相同版本描述而生成的编解码器。为了防止一方因收到过多或者过少数据而出现错误,ASN.1中用符号“”来标记可能以后是其它类型的地方。这样即使是旧的编解码器也不会因为描述扩充而导致编解码错误。如:

Type ::= SEQUENCE
{
component1 INTERGER,
component2 BOOLEAN, -- version 1

}

以后新的版本中,描述可能为:

Type ::= SEQUENCE
{
component1 INTERGER,
component2 BOOLEAN,
…,
[[component3 REAL]], -- version 2

}

注意:新加入的类型成员要嵌套在“[[]]”中。


自动标签AUTOMATIC TAGS

AUTOMATIC TAGS 的核心目的是自动化地为 SEQUENCESETCHOICE 类型中的成员分配标签。它主要解决了两个问题:

  1. 避免手动分配标签的繁琐和错误:当一个结构体中有大量可选字段时,手动为每个字段分配唯一的 [0], [1], [2]… 既枯燥又容易出错(如重复使用同一个标签号)。
  2. 确保编码清晰无歧义:特别是在处理多个 OPTIONALDEFAULT 字段时,编码器(如 BER/DER)需要依靠唯一的上下文特定标签(Context-specific Tags) 来识别哪个字段存在,哪个字段不存在。AUTOMATIC TAGS 保证了这些标签的唯一性。

基本定义

可以在模块定义时,声明模块全局Tag模式。可以是EXPLICIT TAGSIMPLICIT TAGSAUTOMATIC TAGS

IMPLICIT TAGS:模块内所有SEQUENCESETCHOICE的成员都是IMPLICIT模式(除非它是CHOICE类型、开放类型或者一个参数类型)。但不影响IMPORTS的内容。

AUTOMATIC TAGS:模块内所有SEQUENCESETCHOICE类型ASN.1编译器会自动从0开始,步长为1进行自动编码。而其中的成员则用IMPLICIT模式,除非它是CHOICE类型、开放类型或者一个参数类型。

下面两个定义是等效的:

M  DEFINITIONS  AUTOMATIC TAGS ::=
BEGIN
T ::= SEQUENCE
{
a INTEGER,
b CHOICE
{
i INTEGER,
n NULL
},
c REAL
}
END
M  DEFINITIONS ::=
BEGIN
T ::= SEQUENCE
{
a [0] IMPLICIT INTEGER,
b [1] EXPLICIT CHOICE
{
i [0] IMPLICIT INTEGER,
n [1] IMPLICIT NULL
},
c [2] IMPLICIT REAL
}
END

如果使用全局AUTOMATIC TAGS模式时,在描述中给一个类型指定了一个Tag,那么对这个类型的AUTOMATIC TAGS就关闭。如果一个SEQUENCE或者SET类型包含COMPONENTS OF条目,而且这个条目在自动编号前展开;但自动标号又要求在展开前进行,ASN.1编译器要自己检查这种冲突。

对新的协议现在都推荐使用AUTOMATIC TAGS

开启与关闭的触发条件

对于以下ASN.1定义

MyHTTP
DEFINITIONS
AUTOMATIC TAGS ::=
BEGIN
Person ::= SEQUENCE {
name UTF8String,
sex Sex,
age INTEGER OPTIONAL,
email UTF8String OPTIONAL,
phone UTF8String OPTIONAL
}
Sex ::= SEQUENCE{
sexName UTF8String
}
END

使用如下java代码

Person person = new Person();
person.setName("张三");
person.setAge(18);
person.setEmail("email");
person.setPhone("phone");
Sex sex = new Sex();
sex.setSexName("男");
person.setSex(sex);

生成的数据为

原始的hex= 30208006E5BCA0E4B889A1058003E794B78201128305656D61696C840570686F6E65
格式化后的TLV结构:
30 (20)
80 (06) E5BCA0E4B889
A1 (05)
80 (03) E794B7
82 (01) 12
83 (05) 656D61696C
84 (05) 70686F6E65

修改上面的ASN.1定义为

MyHTTP
DEFINITIONS
AUTOMATIC TAGS ::=
BEGIN
Person ::= SEQUENCE {
name UTF8String,
sex Sex,
age INTEGER OPTIONAL,
email [1] UTF8String OPTIONAL,
phone UTF8String OPTIONAL
}
Sex ::= SEQUENCE{
sexName UTF8String
}
END

此时生成的数据为

30 (20)
0C (06) E5BCA0E4B889
30 (05)
80 (03) E794B7
02 (01) 12
81 (05) 656D61696C
0C (05) 70686F6E65

如果使用全局AUTOMATIC TAGS模式时,在描述中给一个类型指定了一个Tag,那么对这个类型的AUTOMATIC TAGS就关闭

工作机制

当你在模块定义中使用 AUTOMATIC TAGS 时,编译器或编码器会遵循一个简单的规则:

自动为 SEQUENCESETCHOICE 类型中的每一个组件分配一个上下文特定的标签号,从 [0] 开始,按定义顺序依次递增。

重要的是,这种自动分配的本质是隐式标签(IMPLICIT)。这意味着在编码时,字段的原始通用标签(如 UNIVERSAL 2 for INTEGER)会被新分配的上下文特定标签所替换,从而产生更紧凑的编码。


示例 1:没有使用 AUTOMATIC TAGS (手动分配标签)

在这种方式下,你必须手动为每个需要区分的可选字段分配唯一的标签号。

PersonManual DEFINITIONS EXPLICIT TAGS ::= BEGIN
Person ::= SEQUENCE {
name UTF8String,
age [0] INTEGER OPTIONAL, -- 手动分配标签 [0]
email [1] UTF8String OPTIONAL, -- 手动分配标签 [1]
phone [2] UTF8String OPTIONAL -- 手动分配标签 [2]
}
END

痛点:如果以后在 ageemail 之间添加一个新的可选字段 nickname,你必须重新编排后面所有的标签号(email 变成 [2], phone 变成 [3]),这非常容易出错且难以维护。

示例 2:使用 AUTOMATIC TAGS (自动分配标签)

现在,我们让 ASN.1 编译器来自动处理标签分配。

PersonAuto DEFINITIONS AUTOMATIC TAGS ::= BEGIN
Person ::= SEQUENCE {
name UTF8String, -- 无OPTIONAL,通常不分配自动标签
age INTEGER OPTIONAL, -- 自动分配标签 [0] IMPLICIT
email UTF8String OPTIONAL, -- 自动分配标签 [1] IMPLICIT
phone UTF8String OPTIONAL -- 自动分配标签 [2] IMPLICIT
}
END

优势

  • 无需手动管理:你不用再写 [0], [1]。编译器会自动完成。
  • 易于扩展:在 ageemail 之间插入 nickname UTF8String OPTIONAL,它会自动获得标签 [1],而 emailphone 会自动变为 [2][3]。代码维护变得非常简单。
  • 避免冲突:绝对保证不会出现标签号重复的错误。

示例 3:混合类型与CHOICE类型

AUTOMATIC TAGS 同样完美处理混合类型和选择类型。

ComplexExample DEFINITIONS AUTOMATIC TAGS ::= BEGIN

Address ::= SEQUENCE {
street UTF8String,
city UTF8String,
zipCode [0] INTEGER OPTIONAL, -- 手动覆盖:显式标签
country UTF8String DEFAULT 'Earth'
}

ContactMethod ::= CHOICE {
email UTF8String, -- 自动标签 [0]
phone UTF8String, -- 自动标签 [1]
postalMail Address, -- 自动标签 [2]
fax UTF8String -- 自动标签 [3]
}

UserRecord ::= SEQUENCE {
id INTEGER,
primaryContact ContactMethod,
backupContact ContactMethod OPTIONAL, -- 自动标签 [1]
isVerified BOOLEAN DEFAULT FALSE
}

END

代码分析

  1. Address:
    • street, city:非可选字段,通常不需要标签。
    • zipCode:我们手动指定了显式标签 [0] EXPLICIT(因为模块是AUTOMATIC TAGS,但被[0]覆盖了规则)。
    • country:这是一个 DEFAULT 字段。注意:虽然 DEFAULT 字段在编码中通常可以省略(如果取默认值),但为了在出现时能够被识别,它也需要一个标签。AUTOMATIC TAGS 会为它自动分配一个标签(在这个例子中,由于 zipCode 是手动 [0]country 可能会被分配到 [1])。
  2. ContactMethod (CHOICE):
    • email, phone, postalMail:自动获得标签 [0], [1], [2]
    • fax:手动覆盖,使用显式标签 [5]。这意味着编码器会创建一个外层标签 [5],里面再包裹 UTF8String 的通用标签和值。这打破了自动的连续性,但提供了灵活性。
  3. UserRecord:
    • id, primaryContact:非可选字段。
    • backupContact:是一个可选字段,因此被自动分配了标签 [1](因为它是序列中第一个可选/DEFAULT组件)。
    • isVerified:是一个 DEFAULT 字段,如果它被自动分配标签,会是 [2]

使用以下java代码

UserRecord userRecord = new UserRecord();
userRecord.setId(1);
userRecord.setIsVerified(true);

ContactMethod contactMethod=new ContactMethod();


Address address=new Address();
address.setStreet("street");
address.setCity("city");
address.setCountry("country");
address.setZipCode(123456);
contactMethod.setPostalMail(address);

userRecord.setPrimaryContact(contactMethod);

生成的数据为

原始的hex= 3026800101A11EA21C0C067374726565740C0463697479800301E2400C07636F756E7472798301FF
格式化后的TLV结构:
30 (26)
80 (01) 01
A1 (1E)
A2 (1C)
0C (06) 737472656574
0C (04) 63697479
80 (03) 01E240
0C (07) 636F756E747279
83 (01) FF
特性 描述
目的 自动化管理 SEQUENCE/SET/CHOICE 中组件的上下文特定标签,防止冲突,简化维护。
机制 [0] 开始,按定义顺序为需要标签的组件(如 OPTIONAL/DEFAULT)自动分配标签。
本质 自动分配的标签是隐式(IMPLICIT) 的,会替换原始类型标签,编码更紧凑。
优势 1. 免去手动分配和管理标签的麻烦。
2. 避免标签号冲突错误。
3. 使数据结构更容易扩展和修改。
注意 解码方必须拥有完全相同的 ASN.1 schema(模式定义),因为类型信息不再包含在编码数据中。

简单来说:AUTOMATIC TAGS 是一个“一劳永逸”的配置,它通过让编译器接管繁琐的标签分配工作,使你能够更专注于数据结构的逻辑设计,从而编写出更清晰、更健壮、更易于扩展的 ASN.1 规范。


隐式编码与显式编码

隐式编码 (IMPLICIT Encoding)

核心思想:替换标签。 使用新的上下文特定标签直接替换数据类型的原始通用标签。

  • 编码方式: 编码器直接使用新指定的标签(如 [5])作为 T,数据的值作为 V 进行编码。原始数据类型的标识信息(通用标签)不会出现在编码字节流中。
  • 解码要求解码器必须预先知道这个特定标签(如 [5])对应的是什么数据类型。它无法从编码数据本身推断出类型。协议规范(ASN.1 模式)就是解码器所需的“说明书”。
  • 优点: 编码更紧凑,体积更小,因为没有内层标签的额外开销。
  • 缺点: 缺乏自描述性,灵活性差。如果协议变更(例如将 [5] 从 INTEGER 改为 STRING),新旧版本将无法兼容。

ASN.1 定义

MyModule DEFINITIONS IMPLICIT TAGS ::= BEGIN
UserRecord ::= SEQUENCE {
name UTF8String,
id [0] IMPLICIT INTEGER, -- 使用隐式编码的上下文标签
level [1] IMPLICIT INTEGER DEFAULT 1
}
END

(注意:在 IMPLICIT TAGS 模块中,[0] 默认就是隐式的,这里的 IMPLICIT 关键字可写可不写,只是为了清晰)。
假设我们有数据:name = "Alice", id = 123

对于 id 字段:

  • 其原始类型是 INTEGER (通用标签 = UNIVERSAL 2)。
  • 我们应用了隐式标签 [0]

编码结果:

30 (0D)
0C (05) 416C696365
80 (01) 7B
81 (01) 02

请注意,编码后的数据中完全没有 UNIVERSAL 2 这个标签的踪影。

典型应用场景:

  • 带宽或存储极其敏感的领域:例如早期的移动通信协议(如 GSM)、卫星通信、某些物联网(IoT)设备协议。每个字节都很宝贵,需要极力缩减开销。
  • 封闭的、高度优化的系统:通信双方由同一实体严格控制,协议规范几乎不会变动,可以牺牲灵活性来换取性能。

显式编码 (EXPLICIT Encoding)

核心思想:包装标签。 新的上下文特定标签作为一个外壳,包裹住完整的原始数据类型的 TLV 结构。

  • 编码方式: 编码器先像没有上下文标签一样,对数据进行正常编码(得到一个完整的 TLV)。然后,它再创建一个新的、外层的 TLV,其中 T 就是新的上下文特定标签(如 [5]),V 则是刚才编码得到的整个内层 TLV。
  • 解码要求: 解码器看到外层标签 [5],知道需要解码一个上下文特定的元素。它接着解码内层的 V 字段,而内层 V 字段本身又是一个完整的 TLV,包含了完整的类型信息。解码器可以检查内层标签是否符合预期,或者甚至处理未知的新类型(如果协议允许扩展)。
  • 优点: 自我描述,灵活性强,向后兼容性好。数据类型信息完整地保存在编码中。
  • 缺点: 编码体积更大,因为有额外的外层标签和长度字节。

ASN.1 定义:

MyModule DEFINITIONS EXPLICIT TAGS ::= BEGIN
UserRecord ::= SEQUENCE {
name UTF8String,
id [0] EXPLICIT INTEGER, -- 使用显式编码的上下文标签
level [1] EXPLICIT INTEGER DEFAULT 1
}
END

(同样,在 EXPLICIT TAGS 模块中,[0] 默认就是显式的)。

2. 编码过程:
同样数据:name = "Alice", id = 123

对于 id 字段:

  • 先对值 123 进行编码: T_inner = UNIVERSAL 2, L_inner = 1, V_inner = 0x7B
  • 然后将整个 inner TLV 作为值,外面再套一个外壳:
    • T_outer = Context-Specific Tag [0]
    • L_outer = Length of the inner TLV
    • V_outer = The inner TLV (T_inner + L_inner + V_inner)

编码结果:

原始的hex= 30110C05416C696365A00302017BA103020102
格式化后的TLV结构:
30 (11)
0C (05) 416C696365
A0 (03)
02 (01) 7B
A1 (03)
02 (01) 02

3. 典型应用场景:

  • 需要强兼容性和扩展性的协议:例如 X.509 公钥证书LDAPSNMP。这些标准广泛使用显式编码。如果未来版本想将 id 字段从 INTEGER 改为 STRING,旧解码器虽然不认识新类型,但能看到内层的 STRING 标签,可以更好地处理错误或忽略未知字段。新解码器则能正确解析。
  • 网络协议和加密标准:这些领域数据结构的复杂性高,且各方实现可能不同,显式编码提供的自描述特性大大降低了互操作性的难度。
  • 绝大多数新项目显式编码是 ASN.1 默认的标签设置,因为它更安全、更灵活。除非有极其严格的效率要求,否则首选显式编码。

对比总结表

特性 隐式编码 (IMPLICIT) 显式编码 (EXPLICIT)
机制 替换原始通用标签 包装完整的原始TLV
编码结构 [新标签] + Length + Value [新标签] + Length + (原标签 + Length + Value)
数据体积 更小(无额外开销) 更大(有外层包装开销)
自描述性 。接收方必须预知类型。 。类型信息包含在编码中。
灵活性 。类型固定,难以扩展和兼容。 。易于处理可选字段和未来类型变更。
安全性 较低。错误配置容易导致解码错误。 较高。编码数据自身包含类型检查信息。
典型应用 专有协议、极限优化场景、旧系统 行业标准(X.509, LDAP, SNMP)、新项目、网络协议

AUTOMATIC TAGS 的作用是自动分配标签号,但它分配标签的方式是隐式的。这意味着:

MyModule DEFINITIONS AUTOMATIC TAGS ::= BEGIN
Person ::= SEQUENCE {
age INTEGER OPTIONAL -- 编译器自动分配标签 [0] IMPLICIT
}
END

这个 age 字段将会使用隐式编码AUTOMATIC TAGS 带来了标签管理的便利,但继承了隐式编码的优缺点(紧凑但不自描述)。

  • 默认选择显式编码(EXPLICIT TAGS):这是最安全、最通用的选择,适用于绝大多数应用场景,尤其是在开放系统和网络协议中。
  • 仅在确有必要时选择隐式编码:只有当性能分析表明编码开销是主要瓶颈,并且你能够严格控制协议规范的演化和所有实现时,才考虑使用隐式编码。

标准 ASCII 码对照表

二进制 八进制 十进制 十六进制 字符/缩写 解释
00000000 000 0 00 NUL (NULL) 空字符
00000001 001 1 01 SOH (Start Of Headling) 标题开始
00000010 002 2 02 STX (Start Of Text) 正文开始
00000011 003 3 03 ETX (End Of Text) 正文结束
00000100 004 4 04 EOT (End Of Transmission) 传输结束
00000101 005 5 05 ENQ (Enquiry) 请求
00000110 006 6 06 ACK (Acknowledge) 回应/响应/收到通知
00000111 007 7 07 BEL (Bell) 响铃
00001000 010 8 08 BS (Backspace) 退格
00001001 011 9 09 HT (Horizontal Tab) 水平制表符
00001010 012 10 0A LF/NL(Line Feed/New Line) 换行键
00001011 013 11 0B VT (Vertical Tab) 垂直制表符
00001100 014 12 0C FF/NP (Form Feed/New Page) 换页键
00001101 015 13 0D CR (Carriage Return) 回车键
00001110 016 14 0E SO (Shift Out) 不用切换
00001111 017 15 0F SI (Shift In) 启用切换
00010000 020 16 10 DLE (Data Link Escape) 数据链路转义
00010001 021 17 11 DC1/XON (Device Control 1/Transmission On) 设备控制1/传输开始
00010010 022 18 12 DC2 (Device Control 2) 设备控制2
00010011 023 19 13 DC3/XOFF (Device Control 3/Transmission Off) 设备控制3/传输中断
00010100 024 20 14 DC4 (Device Control 4) 设备控制4
00010101 025 21 15 NAK (Negative Acknowledge) 无响应/非正常响应/拒绝接收
00010110 026 22 16 SYN (Synchronous Idle) 同步空闲
00010111 027 23 17 ETB (End of Transmission Block) 传输块结束/块传输终止
00011000 030 24 18 CAN (Cancel) 取消
00011001 031 25 19 EM (End of Medium) 已到介质末端/介质存储已满/介质中断
00011010 032 26 1A SUB (Substitute) 替补/替换
00011011 033 27 1B ESC (Escape) 逃离/取消
00011100 034 28 1C FS (File Separator) 文件分割符
00011101 035 29 1D GS (Group Separator) 组分隔符/分组符
00011110 036 30 1E RS (Record Separator) 记录分离符
00011111 037 31 1F US (Unit Separator) 单元分隔符
00100000 040 32 20 (Space) 空格
00100001 041 33 21 !
00100010 042 34 22
00100011 043 35 23 #
00100100 044 36 24 $
00100101 045 37 25 %
00100110 046 38 26 &
00100111 047 39 27
00101000 050 40 28 (
00101001 051 41 29 )
00101010 052 42 2A *
00101011 053 43 2B +
00101100 054 44 2C ,
00101101 055 45 2D -
00101110 056 46 2E .
00101111 057 47 2F /
00110000 060 48 30 0
00110001 061 49 31 1
00110010 062 50 32 2
00110011 063 51 33 3
00110100 064 52 34 4
00110101 065 53 35 5
00110110 066 54 36 6
00110111 067 55 37 7
00111000 070 56 38 8
00111001 071 57 39 9
00111010 072 58 3A :
00111011 073 59 3B ;
00111100 074 60 3C <
00111101 075 61 3D =
00111110 076 62 3E >
00111111 077 63 3F ?
01000000 100 64 40 @
01000001 101 65 41 A
01000010 102 66 42 B
01000011 103 67 43 C
01000100 104 68 44 D
01000101 105 69 45 E
01000110 106 70 46 F
01000111 107 71 47 G
01001000 110 72 48 H
01001001 111 73 49 I
01001010 112 74 4A J
01001011 113 75 4B K
01001100 114 76 4C L
01001101 115 77 4D M
01001110 116 78 4E N
01001111 117 79 4F O
01010000 120 80 50 P
01010001 121 81 51 Q
01010010 122 82 52 R
01010011 123 83 53 S
01010100 124 84 54 T
01010101 125 85 55 U
01010110 126 86 56 V
01010111 127 87 57 W
01011000 130 88 58 X
01011001 131 89 59 Y
01011010 132 90 5A Z
01011011 133 91 5B [
01011100 134 92 5C \
01011101 135 93 5D ]
01011110 136 94 5E ^
01011111 137 95 5F _
01100000 140 96 60 `
01100001 141 97 61 a
01100010 142 98 62 b
01100011 143 99 63 c
01100100 144 100 64 d
01100101 145 101 65 e
01100110 146 102 66 f
01100111 147 103 67 g
01101000 150 104 68 h
01101001 151 105 69 i
01101010 152 106 6A j
01101011 153 107 6B k
01101100 154 108 6C l
01101101 155 109 6D m
01101110 156 110 6E n
01101111 157 111 6F o
01110000 160 112 70 p
01110001 161 113 71 q
01110010 162 114 72 r
01110011 163 115 73 s
01110100 164 116 74 t
01110101 165 117 75 u
01110110 166 118 76 v
01110111 167 119 77 w
01111000 170 120 78 x
01111001 171 121 79 y
01111010 172 122 7A z
01111011 173 123 7B {
01111100 174 124 7C \
01111101 175 125 7D }
01111110 176 126 7E ~
01111111 177 127 7F DEL (Delete) 删除