ASN.1如何编码对象标识符?

Cra*_*lus 23 security binary encoding asn.1

我无法理解ASN.1的基本概念.

如果类型是OID,相应的数字是否实际编码在二进制数据中?

例如,在这个定义中:

id-ad-ocsp         OBJECT IDENTIFIER ::= { id-ad 1 }
Run Code Online (Sandbox Code Playgroud)

相应的1.3.6.1.5.5.7.48.1是否完全像这样编码在二进制文件中?

我问这个是因为我试图理解我在DER文件(证书)中看到的特定值,即04020500,我不知道如何解释它.

Cod*_*odo 36

是的,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/.

  • 04020500是八位字符串(04),其中两个字节(02)编码NULL(05,长度为00). (3认同)

Des*_*tar 17

如果你的所有数字都小于或等于127,那么你幸运,因为它们每个都可以用一个八位字节表示.棘手的部分是你有更多的常见数字,例如1.2.840.113549.1.1.5 (sha1WithRsaEncryption).这些例子侧重于解码,但编码正好相反.

1.前两个'数字'用一个字节表示

您可以通过将第一个字节读入整数来进行解码

var firstByteNumber = 42;
var firstDigit = firstByteNumber / 40;
var secondDigit = firstByteNumber % 40;
Run Code Online (Sandbox Code Playgroud)

产生价值

1.2
Run Code Online (Sandbox Code Playgroud)

2.使用可变长度数量表示后续字节,也称为基数128.

VLQ有两种形式,

短格式 - 如果八位字节以0开头,则使用剩余的7位简单表示.

长格式 - 如果八位字节以1(最高有效位)开始,则组合该八位字节的后7位加上每个后续八位字节的7位,直到遇到一个以0为最高位的八位位组(这标记为最后一个八位字节).

值840将用以下两个字节表示,

10000110
01001000

Combine to 00001101001000 and read as int.
Run Code Online (Sandbox Code Playgroud)

BER编码的重要资源,http://luca.ntop.org/Teaching/Appunti/asn1.html

第一个八位字节的值为40*value1 + value2.(这是明确的,因为value1限于值0,1和2;当value1为0或1时,value2被限制在0到39的范围内;并且,根据X.208,n总是至少为2.)

以下八位字节(如果有)编码value3,...,valuen.每个值编码为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编码:

06 06 2a 86 48 86 f7 0d


最后,这是我刚刚在Perl中编写的OID解码器.

sub getOid {
    my $bytes = shift;

    #first 2 nodes are 'special';
    use integer;
    my $firstByte = shift @$bytes;
    my $number = unpack "C", $firstByte;
    my $nodeFirst = $number / 40;
    my $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;
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 我不确定你的解码前两位数的代码是否正确.考虑来自{2 100 3}的T-REC-X.690的示例.为此,第一个字节将是180.因此,如果第一个字节小于80,解码它似乎是你的解决方案.任何大于或等于80意味着第一个数字是2,第二个数字可以通过减去80. (4认同)

myk*_*hal 9

OID编码为假人:):

  • 每个OID组件编码为一个或多个字节(八位字节)
  • OID编码只是这些OID组件编码的串联
  • 前两个组件以特殊方式编码(见下文)
  • 如果OID组件二进制值小于7位,则编码只是一个八位字节,保持组件值(注意,最重要的位,最左边,总是0)
  • 否则,如果它有8位或更多位,则将值"扩展"为多个八位字节 - 将二进制表示分成7位块(从右侧),如果需要,用第0个左边填充零,并从这些septets形成八位字节通过添加最重要(左)位1,除了最后一个块,其中将有0位.
  • 前两个分量(XY)被编码为类似于具有值40*X + Y的单个分量

这是ITU-T建议X.6908.19章的重写


Ale*_*ska 7

这是上述内容的简单 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)
Run Code Online (Sandbox Code Playgroud)