我无法理解ASN.1的基本概念。
如果类型是 OID,相应的数字是否实际编码在二进制数据中?
例如,在此定义中:
id-ad-ocsp OBJECT IDENTIFIER ::= { id-ad 1 }
相应的 1.3.6.1.5.5.7.48.1 是否完全像这样编码在二进制文件中?
我问这个是因为我试图理解我在 DER 文件(证书)中看到的特定值,这是04020500,我不确定如何解释它。
是的,OID 在二进制数据中编码。您提到的 OID 1.3.6.1.5.5.7.48.1 变为 2b 06 01 05 05 07 30 01(前两个数字编码在一个字节中,所有剩余的数字也编码在一个字节中,因为它们都小于 128)。
此处提供了有关 OID 编码的良好描述。
但是分析ASN.1数据的最佳方法是粘贴到在线解码器中,例如 http://lapo.it/asn1js/。
如果您的所有数字都小于或等于 127,那么您可以用每个八位字节表示它们。当你有较大的常见数字时,例如1.2.840.113549.1.1.5 (sha1WithRsaEncryption)
,那么使用它使用可变长度解码。这些示例侧重于解码,但编码正好相反。
1. 前两个"数字"用单个字节表示
您可以通过将第一个字节读入整数来解码
if ($firstByte >= 80) {
$nodeFirst = 2;
$nodeSecond = $firstByte - 80;
}
else {
$nodeFirst = $firstByte / 40;
$nodeSecond = $firstByte % 40;
}
$oidText = "$nodeFirst.$nodeSecond"
生成值
1.2
2. 后续字节使用可变长度数量表示,也称为基数 128。
VLQ有两种形式,
简写形式 - 如果八位字节以 0 开头,则仅使用剩余的 7 位表示。
长格式 - 如果八位字节以 1(最高有效位)开头,请将该八位组的接下来 7 位加上每个后续八位组的7 位组合在一起,直到遇到以 0 作为最高有效位的八位组(这标志着最后一个八位字节)。
值 840 将用以下两个字节表示,
10000110
01001000
Combine to 00001101001000 and read as int.
<小时 />BER 编码的重要资源,http://luca.ntop.org/Teaching/Appunti/asn1.html
第一个八位字节的值为 40 * 值 1 + 值 2。(这是明确的,由于值 1 仅限于值 0、1 和 2;值 2 限制为当值 1 为 0 或 1 时,范围 0 到 39;并且,根据 X.208,n 是始终至少为 2.)
<小时 />以下八位字节(如果有)对值 3 进行编码, ...,值。每个值都编码为基数 128,最高有效数字在前,使用尽可能少的数字,以及每个数字的最高有效位除值编码中的最后一个八位字节外,设置为"1"。示例:该RSA Data Security, Inc. 对象的 BER 编码的第一个八位字节标识符为 40 * 1 + 2 = 42 = 2a16。编码840=6*128+4816 是 86 48,编码 113549 = 6 * 1282 + 7716 * 128 + d16是 86 F7 0d。这会导致以下 BER 编码:
0606 2a 86 48 86 F7 0D
编辑/免责声明:根据下面的评论修复了第一个八位字节,但尚未对此进行测试。我现在将这段代码片段作为一般参考,但它不能保证是正确的,我不建议盲目复制并粘贴它:)。对于>128 VLQ,您通常使用位移来重新对齐位,而不是一串位。
sub getOid {
my $bytes = shift;
#first 2 nodes are 'special';
use integer;
my $firstByte = shift @$bytes;
my $number = unpack "C", $firstByte;
my $nodeFirst;
my $nodeSecond;
if ($number >= 80) {
$nodeFirst = 2;
$nodeSecond = $number - 80;
}
else {
$nodeFirst = $number / 40;
$nodeSecond = $number % 40;
}
my @oidDigits = ($nodeFirst, $nodeSecond);
while (@$bytes) {
my $num = convertFromVLQ($bytes);
push @oidDigits, $num;
}
return join '.', @oidDigits;
}
sub convertFromVLQ {
my $bytes = shift;
my $firstByte = shift @$bytes;
my $bitString = unpack "B*", $firstByte;
my $firstBit = substr $bitString, 0, 1;
my $remainingBits = substr $bitString, 1, 7;
my $remainingByte = pack "B*", '0' . $remainingBits;
my $remainingInt = unpack "C", $remainingByte;
if ($firstBit eq '0') {
return $remainingInt;
}
else {
my $bitBuilder = $remainingBits;
my $nextFirstBit = "1";
while ($nextFirstBit eq "1") {
my $nextByte = shift @$bytes;
my $nextBits = unpack "B*", $nextByte;
$nextFirstBit = substr $nextBits, 0, 1;
my $nextSevenBits = substr $nextBits, 1, 7;
$bitBuilder .= $nextSevenBits;
}
my $MAX_BITS = 32;
my $missingBits = $MAX_BITS - (length $bitBuilder);
my $padding = 0 x $missingBits;
$bitBuilder = $padding . $bitBuilder;
my $finalByte = pack "B*", $bitBuilder;
my $finalNumber = unpack "N", $finalByte;
return $finalNumber;
}
}
瓜书的 OID 编码:):
- 每个 OID 组件编码为一个或多个字节(八位字节) OID
- 编码只是这些 OID 组件编码的串联
- 前两个组件以特殊方式编码(见下文)
- 如果 OID 组件二进制值小于 7 位,则编码只是一个八位字节,保存组件值(注意,最左边的最高有效位将始终为 0)
- 否则,如果它有 8 位或更多位,则该值将"分散"为多个八位字节 - 将二进制表示拆分为 7 位块(从右起),如果需要,用零左垫第一个块,并通过添加最高有效(左)位 1 从这些七位组形成八位字节,最后一个块除外,那里将有位 0。
- 前两个分量 (X.Y) 的编码就像是值为 40*X + Y 的单个分量一样
这是ITU-T建议书X.690第8.19章的改写
这是上述的简单 Python 3 实现,或者将对象标识符的字符串形式转换为 ASN.1 DER 或 BER 形式。
def encode_variable_length_quantity(v:int) -> list:
# Break it up in groups of 7 bits starting from the lowest significant bit
# For all the other groups of 7 bits than lowest one, set the MSB to 1
m = 0x00
output = []
while v >= 0x80:
output.insert(0, (v & 0x7f) | m)
v = v >> 7
m = 0x80
output.insert(0, v | m)
return output
def encode_oid_string(oid_str:str) -> tuple:
a = [int(x) for x in oid_str.split('.')]
oid = [a[0]*40 + a[1]] # First two items are coded by a1*40+a2
# A rest is Variable-length_quantity
for n in a[2:]:
oid.extend(encode_variable_length_quantity(n))
oid.insert(0, len(oid)) # Add a Length
oid.insert(0, 0x06) # Add a Type (0x06 for Object Identifier)
return tuple(oid)
if __name__ == '__main__':
oid = encode_oid_string("1.2.840.10045.3.1.7")
print(oid)