DER与BASE64转换

发布时间 2023-06-08 00:57:25作者: ~纯净~

DER与BASE64转换

一、DER

1、DER是什么

DER(Distinguished Encoding Rules)编码是ASN.1(Abstract Syntax Notation One)规范定义的一种数据编码规则。它主要用于将ASN.1数据格式转换为二进制数据以进行传输或存储。

  • DER编码是一种严格的、无歧义的二进制编码方式。它规定了所有数据都必须以唯一的方式编码,并且可以通过严格的解码步骤来确保数据的正确性和完整性。此外,DER编码还遵循一些规则,如数据长度必须使用最小的字节数进行编码等。由于DER编码的严格性和无歧义性,它在许多安全领域中得到了广泛应用,如SSL(Secure Sockets Layer)、TLS(Transport Layer Security)和数字证书等。

2、BER的编码格式

在介绍DER编码格式之前首先了解一下BER编码,因为DER编码是是BER的受限变体

BER编码方式是什么?
  • 基本编码规则的格式指定了用于编码 ASN.1 数据结构的自描述和自定界格式。每个数据元素都被编码为类型标识符、长度描述、实际数据元素以及必要时的内容结束标记。这些类型的编码通常称为类型-长度-值(TLV) 编码。

BER 基本编码规则

BER 编码信息由以下几部分组成:
  • 标识串,表示要编码的ASN.1类型的标识类和标识码以及使用的编码方法(是简单型还是结构型)。
  • 长度串,定长型编码方法中它表示内容串的长度,非定长编码方法中它表示长度不定。
  • 内容串,简单定长型编码方法中它表示要编码类型值的具体内容,结构型编码方法中表示各个成员编码的串联。
  • 内容结束串,只有在结构非定长型编码方法中表示内容串的结束,其他方法中该串省略。

对一个 ASN.1 对象的 BER 编码有三种模式,使用哪一种模式取决于该对象的类型和 该类型数据的长度是否已知,不同模式下编码信息中每个组成部分的编码规则不同:

基本类型定长模式:主要应用于基本类型和从基本类型通过隐式派生得到的类型, 同时需要已知编码值的长度。各部分的 BER 编码规则如下
标识串编码——标识串编码分为低标识编码和高标识编码两种形式
  1. 低标识编码
  • 适用于类型标识值小于30 的类型。
  • 编码结果只有 1 个字节。
  • 其中第 8 位和第 7 位表示 Class 类型,第 8 位和第 7 位的赋值规则参见表
  • 第6位置为0表示是基本类型的编码。
  • 第5位到第1位填充数据类型标识的值
    image
  1. 高标识编码
  • 适用于类型标识值大于 30 的类型。
  • 编码结果至少有2个字节。
  • 除了将第5 到第 1 位置为1,第一个字节与低标识编码获得的标识串的构成相同。
  • 第2个字节以后(包含第2个字节)的字节填充类型标识的值,基数是128,每个 字节的第 8 位作为结束标志,除了最后一个字节外其他都置为 1。
长度串编码——长度串编码分为短型和长型两种形式
  1. 短型长度串编码。
  • 适用于内容长度小于127的类型。
  • 编码只有一个字节。
  • 第8位置为0,其他7位填充长度的值(是内容串的长度)。
  1. 长型长度串编码。
  • 适用于内容长度大于127的类型。
  • 编码由 2~127个字节组成。
  • 第1个字节的第8位置为1,其他7位填充后面填充长度值所有的字节数。
  • 第 2 个字节以后(包含第 2 个字节)的字节,填充内容串的长度值。
内容串编码——表示基本类型的具体编码值
  • 对于OBJECT IDENTIFIER 类型,假设OID=V1.V2.V3...Vn,则其编码规则为:第 1 个字节=40xV1+V2 (V1取值范围为0、1或2, V2 取值范围为0~39);对于V3—.Vn 中的 每个Vx,以128为基数将Vx分解为多个数,除最后一个数外其余数的最高位(第8位) 置为 1 后,成为编码后的多个字节。如 1.2.840.113549,第 1 个字节为 40×1+2—2A(十六进 制); 840—6×128+48(十六进制),则编码后为2个字节86 48; 113549—6×128×128+77×128+0D (十六进制),则编码后为3个字节86 F7 0D.因此1.2.840.113549编码后为06 06 2A 86 48 86 F7 OD
  • 对于 BIT STRING 类型,编码后第 1 个字节表示填充位数或未使用位数。如 keyUsage= 11111,编码后为 03 F8。
  • 对于 INTEGER 类型,由于 DER 编码后第 1 字节第 8 位表示正负整数,因此如果正整 数第1字节第8位为1时,在前填充1个字节0x00。如十进制128(十六进制为80),编 码后为0080。
  • 对于 BOOLEAN 类型,TRUE 编码为 OxFF, FALSE 编码为 0x00。
结构类型定长模式————结构类型定长模式编码,主要应用于基本字符串类型、结构类型、从基本字符串类型 和结构类型通过隐式派生得到的类型、从任意类型通过显式派生得到的类型,也需要已知 编码值的长度。各个部分的 BER 编码规则如下:
  • 标识串编码,与基本类型定长模式编码中的标识串编码规则基本相同,除了将第1 个字节的第 6 位置为1表示结构类型编码。
  • 长度串编码,与基本类型定长模式编码中的长度串编码规则完全相同。
  • 内容串编码,与基本类型定长模式编码中的内容串编码规则完全相同,需要根据结构类型对象的不同类型而采用不同的编码。
结构类型非定长模式————结构类型非定长模式编码,主要应用于基本字符串类型、结构类型、从基本字符串类 型和结构类型通过隐式派生得到的类型、从任意类型通过显式派生得到的类型,不需要知 道编码值的长度。各个部分的BER编码规则如下:
  • 标识串编码,与结构类型定长模式编码中的标识串编码规则完全相同。
  • 长度串编码,只有 1 个字节,赋值为0x80,表示该数据编码是非定长模式编码。
  • 内容串编码,与结构类型定长模式编码中内容串编码规则完全相同。
  • 内容结构串编码,由2个字节组成,赋值为0x0000

3、DER编码格式

重要的 DER 编码约束————与BER的限定区别

  • 长度编码必须使用定型
  • 必须使用尽可能短的长度编码
  • 位串、八位组串和受限字符串必须使用原始编码
  • Set 的元素根据它们的标签值按排序顺序编码
    image
DER编码格式类型:使用TAG来标识数据类型,每种数据类型都有唯一的TAG号来标识,其编码如下
  • 0x01:布尔类型(true/false)
  • 0x02:整型
  • 0x03:位字符串
  • 0x04:字节字符串
  • 0x05:Null类型
  • 0x06:对象标识符
  • 0x13:UTF8 字符串

DER是 BER 的一个子集,提供了一种编码 ASN.1 值的方法。这种编码方式DER 适用于需要唯一编码的情况,例如在密码学中,并确保需要数字签名的数据结构产生唯一的序列化表示。因此,DER 可以被认为是BER的规范形式。

BER编码格式允许接收器从不完整的流中解码 ASN.1 信息,而无需预先了解数据的大小、内容或语义含义。例如,在 BER 中,布尔值 true 可以编码为 255 个非零字节值中的任何一个,而在 DER 中,只有一种方法可以对布尔值 true 进行编码。

数据长度编码:对于所有非固定长度的数据类型(如字符串、二进制等),需要先编码数据的长度,LEN编码方法如下:

  • 如果长度在0 ~ 127个字节之间,则采用一个字节表示长度。
  • 如果长度超过127个字节,则采用多个字节表示长度。第一个字节第7位为0,后续字节的第8位为0,其它7位为数据长度的二进制位(即称为长形式编码)。

DER数据内容编码:将数据的实际值编码到字节流中。

  • 将TAG、LEN和DATA的编码按照顺序拼接在一起,便是这个数据的DER编码。

注:DER编码一般用于二进制数据中,使用DER编码的数据具有唯一性,可以用于数字签名和加密操作。

  • 对于简单类型,如布尔类型或整数类型等,直接按照ASN.1定义的格式编码。例如,布尔类型的值为TRUE,则编码为0x01 0x01 0xFF。
  • 对于结构类型,如SEQUENCE或SET等,需要先按照类型域中定义的顺序对其中的每个成员进行编码,然后再将所有成员的编码序列按照类型域中定义的顺序使用相应的标识符和长度信息进行包装。
  • 对于任意类型,其长度信息需要使用BER长度编码法进行编码,即对于小于等于127字节的长度信息,直接使用一个字节表示;对于大于127字节的长度信息,则使用多个字节来表示,其中最高位为1,其余7位为表示长度所需的字节数。
  • 对于SEQUENCE OF或SET OF等集合类型,需要先对其中的每个成员进行编码,然后再使用相应的标识符和长度信息进行包装,并且这些成员需要按照顺序排列。

综上所述,DER编码是一种严格的ASN.1数据结构的编码方式,它的主要特点是编码后的长度可以预知,且具有唯一性和可逆性。

4、DER编码示例

对象位串:01000100111011是ASN.1定义的BIT STRING类型的对象,其编码的步骤如下:

  1. 对位串使用"0"进行填补,使其长度为8的整数倍(如果已经是整数倍,则不需要进行填补)————补齐两个0在后面,成为8的整数倍,得到’0100010011101100’;
  2. 计算填补的位数并写下来,成为数据内容的第一个字节————'02’作为第一个数据内容的字节;
  3. 写入填补后的位串,高位字节优先。这些数据跟前面的一个字节组成数据内容的全部字节————'44 ec’作为其余的数据内容的字节;
  4. 在这些数据前面加上一个头字节————'03’作为前面的对象标识字节,因为BIT STRING的tag值3<=127,所以只有一个字节的长度域’03’;
  5. 那么得到的这个位串的DER编码就是03 03 02 44 ec,其中,第一个字节是对象标识域,第二个字节是数据长度域,其他为数据域。

二、base64与PEM

1、base64是什么

Base64是一种将二进制数据编码成ASCII字符的方法

  • 它将三个字节的数据用四个ASCII字符表示,其中包含64个可打印字符,包括字母、数字和符号。Base64编码后的数据长度通常比原始数据略长。这种编码方法在像电子邮件这样只能传输ASCII字符的通讯协议中很常用,因为它可以让二进制数据在ASCII字符中传输。使用Base64编码的数据可以通过Base64解码恢复成原始数据。

2、base64编码规则

  1. 将需要编码的二进制数据按照8位分组,不足8位的,在末尾补零;
  2. 将8位的二进制数据再次分成6组,每组6位,因此共分成了n组;
  3. 对每组6位的二进制数据,将其转换成十进制数,即对这个6位二进制数求出它的十进制数值;
  4. 将十进制数值按照Base64编码表中的对应关系转换成对应的字符;
  5. 对于编码时因二进制数据位数不足3的倍数而在结尾处不足6位的情况,根据Base64编码表的填充字符规定,在结尾处增加1个或2个字符“=”;
  6. 最终得到的就是Base64编码后的字符串

3、PEM和base64的关系

PEM(Privacy Enhanced Mail)编码是一种用于在文本格式下表达二进制数据,如密钥、数字证书等的编码方式

  • PEM编码被广泛应用于公开密钥基础设施(PKI)中,常见的有PEM格式的数字证书、私钥等。

PEM编码的规则与base64有关:

  1. 将二进制数据进行Base64编码;
  2. 在编码后的Base64字符串中,每72个字符(或者64个字符)就增加一个换行符("\n");
  3. 在编码后的Base64字符串前后添加一些标记,例如对于X.509证书,一般使用如下的标记

-----BEGIN CERTIFICATE----- (Base64编码后的证书内容) -----END CERTIFICATE-----

其中,-----BEGIN CERTIFICATE-----和-----END CERTIFICATE-----为开头和结尾的标记,用于标识PEM格式的证书内容。

类似的标记还有-----BEGIN PRIVATE KEY-----、-----END PRIVATE KEY-----,用于表示PEM格式的私钥内容等。类似的标记还有-----BEGIN PRIVATE KEY-----、-----END PRIVATE KEY-----,用于表示PEM格式的私钥内容等。

PEM编码的好处在于其文本形式,易于传输和存储,且不会被大多数邮件系统和文本处理工具拦截。但也因此,PEM编码的数据经过Base64编码后会变得更大,不如二进制数据文件紧凑,且无法表示所有二进制数据。

三、DER与base64的转换

DER编码转换base64的目的:

将DER编码进行Base64编码是为了方便将二进制数据进行传输或存储。经过Base64编码后,DER编码转换成的数据可以使用文本格式进行传输,不受二进制数据传输时的特殊字符限制,同时也可以通过文本编辑器或浏览器查看和编辑。

  • DER编码和Base64编码是两种不同的数据格式。DER编码是ASN.1数据编码的一种规范,它是一种二进制格式的数据表示方式,使用DER编码可以将复杂的数据结构编码成简单的字节流,而Base64编码是一种将二进制数据转换成ASCII字符的编码方式,它可以将任意二进制数据编码成由64个字符组成的字符串。

DER转换base64的原理

  • Base64编码的原理是将3个字节的二进制数据转换成4个字符的字符串,每个字符使用6个二进制位表示。
  • 如果数据长度不足3的倍数,则在结尾使用一个或两个"="字符进行填充。
  • 经过Base64编码后的数据长度比原数据长度增加了1/3左右。
  • 将DER编码进行Base64编码可以使用各种编程语言的库进行实现,具体实现方式是,将DER编码数据按照3个字节一组进行划分,然后将每一组的3个字节转换为4个Base64字符,将多余的0或填充字符转换成"="字符,并将转换后的Base64编码串拼接起来。

具体方法参考上文base64的转码方式

四、问题实现举例

代码

基于OpenSSL库的对DER编码进行Base64编码

该函数接受两个参数:DER编码数据的指针和长度。返回值为Base64编码后的数据的指针。在函数内部,创建一个包含BIO对象的管道,将DER编码数据写入BIO对象中并刷新管道,然后从BIO对象中获取输出的Base64编码数据并保存到内存中,最后释放BIO对象。可以调用该函数进行DER编码的Base64编码。注意,在使用OpenSSL库时需要添加必要的头文件和链接库。
点击查看代码
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/err.h>

// 将DER编码数据进行Base64编码
char* base64_encode_der(const unsigned char* der_data, int der_length) {
    BIO *bio, *b64;
    BUF_MEM *bufferPtr = NULL;
    char *base64_data;

    // 创建一个BIO对象,将输出数据存放到内存中
    b64 = BIO_new(BIO_f_base64());
    bio = BIO_new(BIO_s_mem());
    bio = BIO_push(b64, bio);

    // 在BIO对象中写入DER编码数据
    BIO_write(bio, der_data, der_length);
    BIO_flush(bio);

    // 获取输出的Base64编码数据
    BIO_get_mem_ptr(bio, &bufferPtr);
    base64_data = (char*)malloc(bufferPtr->length + 1);
    memcpy(base64_data, bufferPtr->data, bufferPtr->length);
    base64_data[bufferPtr->length] = '\0';

    // 释放资源
    BIO_free_all(bio);

    return base64_data;
}

获取DER编码数据的方法将根据具体的应用场景而定。在使用OpenSSL证书的API时,一般会提供函数来直接获取证书数据的DER编码表示。以下是一个从PEM格式证书中获取DER编码数据,并使用上述代码进行Base64编码的示例,该函数接受PEM格式证书文件的路径作为参数,返回Base64编码后的DER编码数据。在函数内部,从文件中读取证书数据并解析出X509结构体,然后获取DER编码数据并使用base64_encode_der函数进行Base64编码。最后释放相关资源。注意,在使用OpenSSL库时需要添加必要的头文件和链接库。

点击查看代码
#include <openssl/x509.h>

// 从PEM格式证书文件中获取DER编码数据,并进行Base64编码
char* base64_encode_pem_certificate(const char* pem_file_path) {
    X509* cert;
    BIO* bio;
    char* base64_data;

    // 读取PEM格式证书文件
    bio = BIO_new_file(pem_file_path, "r");
    if (!bio) {
        return NULL;
    }

    // 从BIO对象中读取证书数据
    cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
    if (!cert) {
        BIO_free_all(bio);
        return NULL;
    }

    // 获取DER编码数据,并进行Base64编码
    int der_length = i2d_X509(cert, NULL);
    unsigned char* der = (unsigned char*)malloc(der_length);
    if (!der) {
        X509_free(cert);
        BIO_free_all(bio);
        return NULL;
    }
    unsigned char* p = der;
    i2d_X509(cert, &p);
    base64_data = base64_encode_der(der, der_length);

    // 释放资源
    free(der);
    X509_free(cert);
    BIO_free_all(bio);

    return base64_data;
}

在此示例中,我们创建了一个ASN.1结构体,该结构体包含一个整数值2022。然后,我们使用OpenSSL库的i2d_ASN1_TYPE函数将该结构体编码为DER格式,并获取DER编码数据的指针和长度。最后,我们使用前面介绍过的base64_encode_der函数将DER编码数据进行Base64编码,以便传输和存储

点击查看代码
#include <openssl/asn1.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

// 生成一个ASN.1结构体,其中包含一个整数值
ASN1_INTEGER* create_asn1_integer(int value)
{
    ASN1_INTEGER* integer;

    integer = ASN1_INTEGER_new();
    if (!integer)
    {
        return NULL;
    }

    if (!ASN1_INTEGER_set(integer, value))
    {
        ASN1_INTEGER_free(integer);
        return NULL;
    }

    return integer;
}

// 将ASN.1结构体编码为DER格式,并返回DER编码数据的指针和长度
int encode_asn1_der(ASN1_TYPE* type, unsigned char** out_data)
{
    unsigned char *p, *out;
    int len, out_len;

    len = i2d_ASN1_TYPE(type, NULL);
    out = (unsigned char*)malloc(len);
    if (!out)
    {
        return -1;
    }

    p = out;
    i2d_ASN1_TYPE(type, &p);

    *out_data = out;
    out_len = len;

    return out_len;
}

int main()
{
    ASN1_TYPE* type;
    ASN1_INTEGER* integer;
    unsigned char *der_data;
    int der_len;

    // 创建ASN.1结构体
    type = ASN1_TYPE_new();
    if (!type)
    {
        return -1;
    }

    integer = create_asn1_integer(2022);
    if (!integer)
    {
        ASN1_TYPE_free(type);
        return -1;
    }

    ASN1_TYPE_set_octetstring(type, (unsigned char*)integer, sizeof(ASN1_INTEGER));

    // 将ASN.1结构体编码为DER格式
    der_len = encode_asn1_der(type, &der_data);
    if (der_len < 0)
    {
        ASN1_INTEGER_free(integer);
        ASN1_TYPE_free(type);
        return -1;
    }

    // 对DER编码数据进行Base64编码
    char* base64_data = base64_encode_der(der_data, der_len);

    // 打印Base64编码数据
    printf("Base64-encoded DER data: %s
           ", base64_data);

           // 释放资源
           free(der_data);
           ASN1_INTEGER_free(integer);
           ASN1_TYPE_free(type);
           free(base64_data);

           return 0;
}

运行

五、问题拓展:DER与PEM的转换

综上所述,DER和PEM都是针对ASN.1数据结构的编解码方式,它们之间是有关联的。

DER(Distinguished Encoding Rules)是ASN.1编码规则的一种,它是一种二进制编码方式,编码后具有唯一性和可逆性,且长度可以预知。
PEM(Privacy-enhanced Electronic Mail)是一种基于Base64算法的ASN.1数据结构的文本编码方式,主要应用于加密算法和数字签名领域。它将DER格式的数据使用Base64算法转换成可读的ASCII字符串,通常以"-----BEGIN ..."和"-----END ..."为标识符来 delineate。

例如,证书文件通常以DER格式进行存储,但是在传输或显示时,往往使用PEM格式进行传输或显示。具体来说,在使用PEM格式进行传输或显示时,需要将DER格式的数据进行Base64编码,然后将编码后的数据使用固定的格式进行包装和标识,如"-----BEGIN CERTIFICATE-----"和"-----END CERTIFICATE-----"。因此,可以将PEM格式视为DER格式的一种文本表示形式。

现在我们可以把DER转换base64编码的问题进展到DER编码与PEM编码的转换