首页
社区
课程
招聘
[原创]TLS协议全解析
发表于: 2025-4-27 17:53 1626

[原创]TLS协议全解析

2025-4-27 17:53
1626

本文全程干货,但稍微苦涩难懂,假如能耐心看完,我相信您一定能得到想要的TLS细节。如果有什么疑问,请留言,必定知无不言。


一.  概述


mbedtls完整实现了tls/https协议,大量使用于嵌入式设备。使用mbedtls过程中,一些细节需记录和深入学习,出于学习目的,记录了mbedtls、https/tls的学习心得。

首先是一些基础概念。

MAC:  Message Authentication Code

SHA3:跟AES一样,该名词是一个标准,其实现算法为keccak算法。

HMAC:  Hash-based Message Authentication Code

DSA:Digital Signature Algorithm,一种离散对数的数字签名算法。注意,它不是加密方法。

Elgamal:有限域上的离散对数加密方案。

ECC:椭圆曲线离散对数加解密方案。椭圆曲线加密算法的强度比RSA、Elgamal都要高,也就是说,同样的位数,破解难度更大。

椭圆曲线方程:

该方程是求椭圆弧长的椭圆积分的反函数。该方程因变量是,因此该函数的图像必然关于轴对称。????2=????????3+????????2+????????+????该方程是求椭圆弧长的椭圆积分的反函数。该方程因变量是????2,因此该函数的图像必然关于????轴对称。

椭圆曲线上的加法



椭圆曲线上的2倍运算

椭圆曲线的取反运算


椭圆曲线算法一般使用有限域的椭圆曲线算法,具体方程为:

下图是离散域椭圆方程

添加图片注释,不超过 140 字(可选)

Diffie-Hellman:一种密钥交换算法。适用于离散对数问题。

Sbox:或者写为S-box,即subsitution box,汉语表示替换盒,即将数据替换为盒子中的值。

Rijndael:AES加密算法使用的加密标砖。AES只是代表一个加密代号,他的具体实现算法由Rijndael完成。

TLS和SSL:tls1.0就相当于ssl3.1



tls版本字段定义:

tls版本

tls版本号最小为0x0300,也就是ssl3.0;最大为0x304,也就是tls1.3


二. 数据包

ssl/tls协议有两层。第一层长度5字节分为3个字段,主要用于封装第二层的记录协议;第二层记录协议下有多个子协议,但是长度、格式不固定。


TLS协议层次图


TLS协议号

类型

标志

ClientHello

1

ServerHello

2

Certificate

11

ClientKeyExchange

16

MsgAlert

21

EncryptedHandshakeMessage

22

ServerHelloDone

14

ApplicationData

23

ChangeCipherSpec

20


tls命令号


mbedtls中的定义:

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)


关于msgAlert消息,有如下定义:

  struct{
      AlertLevel level;             //警告错误级别
      AlertDescription description; //警告协议的详细描述信息
  } Alert;

  enum{ 
      warning(1), 
      fatal(2), 
      (255)
  } AlertLevel;

  enum { 
      close_notify(0),                     //正常关闭通知,表示会话正常结束。
      unexpected_message(10) ,  //表示收到了不适当或未预期的消息类型
      bad_record_mac(20),          // 记录层的消息认证码(MAC)验证失败
      decryption_failed_RESERVED(21), //解密接收到的数据时出现了问题
      record_overflow(22), //接收到的数据记录长度超过了协议所规定的最大长度,违反了协议的格式要求
      decompression_failure(30), //解压缩操作失败
      handshake_failure(40),   // 握手过程中发生错误,无法完成握手
      no_certificate_RESERVED(41),  //未接收到有效证书
      bad_certificate(42),   //证书无效或格式错误
      unsupported_certificate(43), //对端提供的证书不受本地信任存储支持
      certificate_revoked(44),  //证书已被吊销
      certificate_expired(45), //证书已过期
      certificate_unknown(46), //无法验证对端提供的证书的有效性
      illegal_parameter(47), //在握手消息中遇到了非法或不支持的参数
      unknown_ca(48), //验证过程中使用的证书颁发机构未知
      access_denied(49) , //访问被拒绝
      decode_error(50), //解码消息时出错
      decrypt_error(51) , //解密消息时出错
      export_restriction_RESERVED(60), 
      protocol_version(70), //不支持的协议版本
      insufficient_security(71), //安全度低于最低要求
      internal_error(80), //发生了内部错误
      user_canceled(90) , // 用户主动取消了相关操作
      no_renegotiation(100) ,  //客户端或服务器拒绝重新协商请求时产生
      unsupported_extension(110) , //一方接收到的SSL/TLS扩展不被另一方支持或理解
      (255) 
  } AlertDescription;


第一层握手协议截图

添加图片注释,不超过 140 字(可选)


以下几点需要注意:

  1. 记录协议中的长度字段,表示从本字段往后到数据包结尾的长度。这点比较奇怪,因为本字段之后,还有协议版本字段。也就是说,此值等于第一层中的长度值-4。此外,若本字段第一个字节不为0,则丢弃该数据包,也就是说,虽然本层的长度字段为3字节,但是表示的长度不得大于64kb。

  2. 加密套件是由服务器端决定的。客户端只是列出本机支持的加密套件,但是决定使用哪一种,取决于服务端。

  3. 加密套件选用ECDHE等,tls握手协议中才会有Sever Key Exchange消息

  4. 会话密钥就由 "客户端随机数" 、 "服务端随机数 "、密钥交换算法算出的 "PreMasterKey"三者结合生成的。

  5. CryptedHandshakeMessage是使用对称秘钥加密的第一个报文,并且包含MAC校验。该数据包若是可以正确解密、并且校验正确,在wireshark中会被显示为Client/Server Finished包。

  6. session key的作用。当客户端再次请求Client Hello,服务器端从客户端携带的SessionID,找到该客户端上次和服务端SSL加密通信的上下文信息,直接复原对称加密信道,而不用再进行SSL握手密钥协商。

sesson key重用连接

7.  加解密函数中,若使用_mm_load_si128等simd并行计算加速指令,需使用calloc函数分配内存,即将内存分配地址值清0!

tls握手过程数据包

三. X509证书

主要是sig字段。该字段的计算方式如下:

  1. 对证书做sha计算,并用私钥加密。

  2. 客户端计算本证书中除了此字段外的sha值,并用公钥解密该字段,比较两者是否相同。


RSA和对称加密是网络安全的核心支柱理论,在作者的一篇文章中详细描述了RSA为例的非对称加密的数学原理和64位密钥的最小化实现:08aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6*7K9s2g2S2L8X3I4S2L8W2)9J5k6i4A6Z5K9h3S2#2i4K6u0W2j5$3!0E0i4K6u0r3M7q4)9J5c8U0j5%4y4U0b7J5y4e0M7@1y4b7`.`.

还有另一种更为流行的非对称加密算法即DSA,此算法的数学原理跟RSA类似,利用了大质数分解难题,但是另一种数论模型:寻找大质数的原根。作者将会继续写一篇DSA数学原理和实现的文章。

本人还有一篇关于RC4加密的说明文章:a22K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6*7K9s2g2S2L8X3I4S2L8W2)9J5k6i4A6Z5K9h3S2#2i4K6u0W2j5$3!0E0i4K6u0r3M7q4)9J5c8U0j5%4y4e0V1@1y4U0x3H3y4b7`.`.


四. 加解密套件

每个加密套件有四个功能:认证算法(身份验证)、密钥交换算法KeyExchange(密钥协商)、对称加密算法Enc(信息加密)和信息摘要Mac(完整性校验)。

以TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384为例:


添加图片注释,不超过 140 字(可选)

     本问例子使用tls_rsa_with_rc4_128_md5

五. 加解密过程

rsa+rc4128套件加解密过程:bd1K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2X3M7X3g2W2j5Y4g2X3i4K6u0W2j5$3!0E0i4K6u0r3j5i4u0@1K9h3y4D9k6i4y4Q4x3V1k6F1k6i4c8%4L8%4u0C8i4K6u0r3x3e0V1J5x3K6x3H3i4K6u0W2K9s2c8E0L8l9`.`.

mbedtls中使用rsa,发现解密过程跟上文描述一致。上文用到的工具链接:4a8K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6z5k6i4N6n7k6h3f1I4x3e0W2Q4x3V1k6Z5N6s2c8H3M7#2)9#2k6Y4m8S2j5$3E0W2N6s2y4Q4y4h3k6V1k6h3y4J5P5i4m8@1

PremasterKey生成过程。PremasterKey即客户端 Client Key Exchange 消息内容。解密后为48字节的随机数。其中前两个字节为TLS版本,后面46字节为随机数。

Client Key Exchange 数据包

MasterKey计算。即tls1_prf函数。其中secret参数为PremasterKey,label为字符串"master secret",random为64字节的random_c和random_s


tls1_prf函数定义


对称密钥的计算。还是调用tls1_prf函数,使用masterKey完成计算,randbytes还是64字节的random_c和random_s

tls_prf函数参数


tls_prf实现过程:

  1. 构造如下结构体:

typedef struct key
char sha1[20];
char tag[15] or char tag[13]; //"server finished" or "client finished" or "master key" or "key expansion"
char random_s[32];
char random_c[32];
};

2. 计算masterkey前24字节的md5

3. 计算16轮md5

4. 计算masterkey后24字节的md5

5. 计算16轮md5


static int tls1_prf( const unsigned char *secret, size_t slen,
                     const char *label,
                     const unsigned char *random, size_t rlen,
                     unsigned char *dstbuf, size_t dlen )
{
    size_t nb, hs;
    size_t i, j, k;
    const unsigned char *S1, *S2;
    unsigned char *tmp;
    size_t tmp_len = 0;
    unsigned char h_i[20];
    const mbedtls_md_info_t *md_info;
    mbedtls_md_context_t md_ctx;
    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;

    mbedtls_md_init( &md_ctx );

    tmp_len = 20 + strlen( label ) + rlen;
    tmp = mbedtls_calloc( 1, tmp_len );
    if( tmp == NULL )
    {
        ret = MBEDTLS_ERR_SSL_ALLOC_FAILED;
        goto exit;
    }

    hs = ( slen + 1 ) / 2;
    S1 = secret;
    S2 = secret + slen - hs;

    nb = strlen( label );
    memcpy( tmp + 20, label, nb );
    memcpy( tmp + 20 + nb, random, rlen );
    nb += rlen;

    /*
     * First compute P_md5(secret,label+random)[0..dlen]
     */
    if( ( md_info = mbedtls_md_info_from_type( MBEDTLS_MD_MD5 ) ) == NULL )
    {
        ret = MBEDTLS_ERR_SSL_INTERNAL_ERROR;
        goto exit;
    }

    if( ( ret = mbedtls_md_setup( &md_ctx, md_info, 1 ) ) != 0 )
    {
        goto exit;
    }

    mbedtls_md_hmac_starts( &md_ctx, S1, hs );
    mbedtls_md_hmac_update( &md_ctx, tmp + 20, nb );
    mbedtls_md_hmac_finish( &md_ctx, 4 + tmp );

    for( i = 0; i < dlen; i += 16 )
    {
        mbedtls_md_hmac_reset ( &md_ctx );
        mbedtls_md_hmac_update( &md_ctx, 4 + tmp, 16 + nb );
        mbedtls_md_hmac_finish( &md_ctx, h_i );

        mbedtls_md_hmac_reset ( &md_ctx );
        mbedtls_md_hmac_update( &md_ctx, 4 + tmp, 16 );
        mbedtls_md_hmac_finish( &md_ctx, 4 + tmp );

        k = ( i + 16 > dlen ) ? dlen % 16 : 16;

        for( j = 0; j < k; j++ )
            dstbuf[i + j]  = h_i[j];
    }

    mbedtls_md_free( &md_ctx );

    /*
     * XOR out with P_sha1(secret,label+random)[0..dlen]
     */
    if( ( md_info = mbedtls_md_info_from_type( MBEDTLS_MD_SHA1 ) ) == NULL )
    {
        ret = MBEDTLS_ERR_SSL_INTERNAL_ERROR;
        goto exit;
    }

    if( ( ret = mbedtls_md_setup( &md_ctx, md_info, 1 ) ) != 0 )
    {
        goto exit;
    }

    mbedtls_md_hmac_starts( &md_ctx, S2, hs );
    mbedtls_md_hmac_update( &md_ctx, tmp + 20, nb );
    mbedtls_md_hmac_finish( &md_ctx, tmp );

    for( i = 0; i < dlen; i += 20 )
    {
        mbedtls_md_hmac_reset ( &md_ctx );
        mbedtls_md_hmac_update( &md_ctx, tmp, 20 + nb );
        mbedtls_md_hmac_finish( &md_ctx, h_i );

        mbedtls_md_hmac_reset ( &md_ctx );
        mbedtls_md_hmac_update( &md_ctx, tmp, 20 );
        mbedtls_md_hmac_finish( &md_ctx, tmp );

        k = ( i + 20 > dlen ) ? dlen % 20 : 20;

        for( j = 0; j < k; j++ )
            dstbuf[i + j] = (unsigned char)( dstbuf[i + j] ^ h_i[j] );
    }

exit:
    mbedtls_md_free( &md_ctx );

    mbedtls_platform_zeroize( tmp, tmp_len );
    mbedtls_platform_zeroize( h_i, sizeof( h_i ) );

    mbedtls_free( tmp );
    return( ret );
}

虽然tls1_prf也调用了hmac初始化函数mbedtls_md_hmac_starts,但是hmac计算在函数mbedtls_ssl_cf_hmac中。下面会介绍。


本文以rsa密钥交换协议为例,简要描述一下加解密过程。而像更常用的ecdh等密钥交换算法,要双方交换premasterkey,只是比rsa多一个密钥交换的数据包,读者可自行查阅资料。

  1. clientHello指定cipherSuite、随机数random_c(32B), ServerHello选择CipherSuite、随机数random_s(32B)。计算随机数的函数为mbedtls_ctr_drbg_random,而不是prf函数。

  2. server发送证书、客户端收到后,客户端使用prf_tls1函数生成PreMasterKey(48字节,头两个字节为tls版本号,后面46字节为随机数),并使用服务器证书的公钥,加密后封装为Client Key Exchange消息传输。服务端接收到后用私钥解密该PreMasterKey,完成密钥交换。

  3. 客户端、服务器调用ssl_compute_master函数(服务端需要先用私钥解密Client Key Exchange包得到PreMasterKey),各自计算MasterKey(长度48字节)。参数包括三个随机数:ClientHello包中的random_c, ServerHello中的random_s,PreMasterKey。还包括一个字符串参数: "master secret"。

  4. 调用mbedtls_ssl_tls_prf函数产生对称密钥、mac密钥、IV向量,每个向量16字节。参数包括上一步的MasterKey,且字符串参数为:"key expansion"。对称密钥通过mbedtls_ssl_derive_keys函数实现,通过ssl_populate_transform函数安装。其中hmac密钥通过mbedtls_md_hmac_starts安装,hmac填充中opad和ipad填充也不同。对称密钥分为加密和解密两个,即:接收数据使用一个密钥解密,发送数据使用另外一个密钥加密;本例中RC4没有初始化向量,一般像AES、DES需要初始化向量;

  5. 发送Server/Client Finished消息,也即Crypted HandShake Message消息。

添加图片注释,不超过 140 字(可选)

RC4对称密钥的设置

添加图片注释,不超过 140 字(可选)



添加图片注释,不超过 140 字(可选)



六. 校验过程

下面简要描述一下校验过程。

校验是对整个通信中、不包括tls包头5字节之外,双方所有参与握手协议的数据包的校验。

ChangeCipherSpec总是和CryptedHandshakeMessage包一起发送,且ChangeCipherSpec总是在前面首先发送。

    

1.     第一个包从ClientHello开始,重要的事情说三遍:最后一个是客户端发送的Client Key Exchange包!最后一个包是客户端发送的Client Key Exchange包!最后一个包是客户端发送的Client Key Exchange包!当然,Client Key Exchange包以后的包也会校验,但是不参与CryptedHandshakeMessage包的校验,也就是握手中的mac校验不包含该数据包。并且,双方的CryptedHandshakeMessage包,都是需要解密后去掉sha1和padding计算校验的。

2. 校验既包括自己发送的数据包,也包括接收到的数据包。也就是说,既包括客户端也包括服务器的包,从ClientHello开始,到Client Key Exchange中间的所有数据包。在收到包的同时,计算包的校验值。

3. 客户端与服务器各自计算校验值。客户端先发送CryptedHandshakeMessage消息,服务器后发送CryptedHandshakeMessage消息。收到对方的CryptedHandshakeMessage后,先用对方的解密密钥解密数据包,然后取出对方的校验码(解密后的头部16字节。并且,客户端先向服务端发送CryptedHandshakeMessage,服务端校验成功后,才会向客户端发送CryptedHandshakeMessage,若是CryptedHandshakeMessage校验不通过,双方都会主动断开连接。计算时,客户端和服务端分别使用"client finish"和"server finish"字符串参与随机数的生成,可以防止重放攻击。

包中剩下20字节sha1是finish包内容,跟本地计算的校验比较,看是否相同。


具体实现算法是,得出全部参与校验的数据包的md5+sha1的36字节校验值,使用随机数生成函数tls1_prf生成12字节的随机数,调用时的参数如下:

第一个参数为master key;

第2个参数为长度字段,一般是48;

第3个参数是sender字段,关于sender字段,服务器端验证客户端时为 "client finished",客户端验证服务器端时为"server finished";

第4个参数即padbuf,为上述包计算出的MD5+sha1 36字节拼接值(如果选择sha256校验就为sha256,或者sha512);

第5个参数为padbuf长度,一般为36;

第6个单数buf,为sha256的计算输出;

第7个参数为输出长度,一般为12字节。

mac校验过程,实际就是计算参数校验的ClientHello、ServerHello、Certificate、ServerHelloDone、ClientKeyExchange这几个包的md5+sha1,然后使用prf函数生成12字节校验码,校验值长度一般为12字节。


添加图片注释,不超过 140 字(可选)

上图为对称密钥解密后的EncryptedHandshakeMessage包。

需要计算校验的字段为最前面的16字节(包括类型、长度字段);

最后12字节紫色字段为padding字段,填充方式为:剩余字段大小-1,此处为12-1=11

中间黄色的20字节是sha1,该字段由函数mbedtls_ssl_cf_hmac计算得来。需要的参数是最前面的16字节,并添加额外的13字节add_data字段(包括tls协议版本、tls类型等字段),并依据如下算法: HMAC(msg) = HASH(okey + HASH(ikey + msg)) 计算得出,具体细节看源代码。

因为需要16字节对齐,一般CryptedHandshakeMessage最小为48字节左右。


mac计算关键代码,其中len字段值为12


mac比较函数:

添加图片注释,不超过 140 字(可选)


综上所述,tls1_prf函数是计算PreMasterKey,MasterKey,校验三者时共同使用的函数。

对称加密算法大多是rc4、des、aes等流加密方式,前面的序列加解密会影响到后面序列的加解密,会导致key的变化;因此,每次加解密的长度都是16字节对齐的,而且每种算法的填充方式也不一样,这方面也需要注意。


七. HMAC计算过程


  1. masterkey生成对称密钥后,调用ssl_populate_transform函数初始化对称密钥、iv、hmac密钥时,调用hmac初始化函数mbedtls_md_hmac_starts,密钥为hmac密钥,长度为20字节。

  2. 接下来的计算在mbedtls_ssl_cf_hmac中。首先用key值初始化okey和ikey。如果key长度大于64,则计算key的md5值当作key。

  3. 用0x5c填充okey,0x3c填充ikey

  4. 用key值异或okey和ikey,并计算ikey的md5值。

  5. 计算okey的md5

int mbedtls_md_hmac_starts( mbedtls_md_context_t *ctx, const unsigned char *key, size_t keylen )
{
    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
    unsigned char sum[MBEDTLS_MD_MAX_SIZE];
    unsigned char *ipad, *opad;
    size_t i;

    if( ctx == NULL || ctx->md_info == NULL || ctx->hmac_ctx == NULL )
        return( MBEDTLS_ERR_MD_BAD_INPUT_DATA );

    if( keylen > (size_t) ctx->md_info->block_size )
    {
        if( ( ret = mbedtls_md_starts( ctx ) ) != 0 )
            goto cleanup;
        if( ( ret = mbedtls_md_update( ctx, key, keylen ) ) != 0 )
            goto cleanup;
        if( ( ret = mbedtls_md_finish( ctx, sum ) ) != 0 )
            goto cleanup;

        keylen = ctx->md_info->size;
        key = sum;
    }

    ipad = (unsigned char *) ctx->hmac_ctx;
    opad = (unsigned char *) ctx->hmac_ctx + ctx->md_info->block_size;

    memset( ipad, 0x36, ctx->md_info->block_size );
    memset( opad, 0x5C, ctx->md_info->block_size );

    for( i = 0; i < keylen; i++ )
    {
        ipad[i] = (unsigned char)( ipad[i] ^ key[i] );
        opad[i] = (unsigned char)( opad[i] ^ key[i] );
    }

    if( ( ret = mbedtls_md_starts( ctx ) ) != 0 )
        goto cleanup;
    if( ( ret = mbedtls_md_update( ctx, ipad,
                                   ctx->md_info->block_size ) ) != 0 )
        goto cleanup;

cleanup:
    mbedtls_platform_zeroize( sum, sizeof( sum ) );

    return( ret );
}


添加图片注释,不超过 140 字(可选)


对称密钥的偏移分布:

客户端:

mac加密密钥偏移0;

mac解密密钥偏移20;

key1密钥偏移40;

key2密钥偏移56;

加密iv偏移72;

解密iv偏移88;

服务端:

mac加密密钥偏移0;

mac解密密钥偏移20;

key1密钥偏移40;

key2密钥偏移56;

加密iv偏移72;

解密iv偏移88;


添加图片注释,不超过 140 字(可选)



/*
 * Compute HMAC of variable-length data with constant flow.
 *
 * Only works with MD-5, SHA-1, SHA-256 and SHA-384.
 * (Otherwise, computation of block_size needs to be adapted.)
 */
MBEDTLS_STATIC_TESTABLE int mbedtls_ssl_cf_hmac(
        mbedtls_md_context_t *ctx,
        const unsigned char *add_data, size_t add_data_len,
        const unsigned char *data, size_t data_len_secret,
        size_t min_data_len, size_t max_data_len,
        unsigned char *output )
{
    /*
     * This function breaks the HMAC abstraction and uses the md_clone()
     * extension to the MD API in order to get constant-flow behaviour.
     *
     * HMAC(msg) is defined as HASH(okey + HASH(ikey + msg)) where + means
     * concatenation, and okey/ikey are the XOR of the key with some fixed bit
     * patterns (see RFC 2104, sec. 2), which are stored in ctx->hmac_ctx.
     *
     * We'll first compute inner_hash = HASH(ikey + msg) by hashing up to
     * minlen, then cloning the context, and for each byte up to maxlen
     * finishing up the hash computation, keeping only the correct result.
     *
     * Then we only need to compute HASH(okey + inner_hash) and we're done.
     */
    const mbedtls_md_type_t md_alg = mbedtls_md_get_type( ctx->md_info );
    /* TLS 1.0-1.2 only support SHA-384, SHA-256, SHA-1, MD-5,
     * all of which have the same block size except SHA-384. */
    const size_t block_size = md_alg == MBEDTLS_MD_SHA384 ? 128 : 64;
    const unsigned char * const ikey = ctx->hmac_ctx;
    const unsigned char * const okey = ikey + block_size;
    const size_t hash_size = mbedtls_md_get_size( ctx->md_info );

    unsigned char aux_out[MBEDTLS_MD_MAX_SIZE];
    mbedtls_md_context_t aux;
    size_t offset;
    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;

    mbedtls_md_init( &aux );

#define MD_CHK( func_call ) \
    do {                    \
        ret = (func_call);  \
        if( ret != 0 )      \
            goto cleanup;   \
    } while( 0 )

    MD_CHK( mbedtls_md_setup( &aux, ctx->md_info, 0 ) );

    /* After hmac_start() of hmac_reset(), ikey has already been hashed,
     * so we can start directly with the message */
    MD_CHK( mbedtls_md_update( ctx, add_data, add_data_len ) );
    MD_CHK( mbedtls_md_update( ctx, data, min_data_len ) );

    /* For each possible length, compute the hash up to that point */
    for( offset = min_data_len; offset <= max_data_len; offset++ )
    {
        MD_CHK( mbedtls_md_clone( &aux, ctx ) );
        MD_CHK( mbedtls_md_finish( &aux, aux_out ) );
        /* Keep only the correct inner_hash in the output buffer */
        mbedtls_ssl_cf_memcpy_if_eq( output, aux_out, hash_size,
                                     offset, data_len_secret );

        if( offset < max_data_len )
            MD_CHK( mbedtls_md_update( ctx, data + offset, 1 ) );
    }

    /* Now compute HASH(okey + inner_hash) */
    MD_CHK( mbedtls_md_starts( ctx ) );
    MD_CHK( mbedtls_md_update( ctx, okey, block_size ) );
    MD_CHK( mbedtls_md_update( ctx, output, hash_size ) );
    MD_CHK( mbedtls_md_finish( ctx, output ) );

    /* Done, get ready for next time */
    MD_CHK( mbedtls_md_hmac_reset( ctx ) );

#undef MD_CHK

cleanup:
    mbedtls_md_free( &aux );
    return( ret );
}


上述代码中,可以看到,除了上述的ClientHello、ServerHello、Certificate、ServerHelloDone、ClientKeyExchange几个数据包外,包括CryptedHandshakeMessage头部16字节、extra_data字段的13字节,也参与了mac校验计算。

extra_data字段13字节内容,即8字节的nouce值加server/client finished包头5字节:

添加图片注释,不超过 140 字(可选)

另外,比较疑惑的一点是,为什么CryptedHandshakeMessage消息参加校验的长度是从tls记录层开始的28字节?而没有绕过中间的20字节sha1呢?通过调试mbedtls_ssl_cf_hmac函数,发现计算了16字节校验值和12字节的对端sha1,但是最后12字节的对端sha1被丢弃,所以只有前16字节校验码的下一步计算对比。

除此之外,还要啰嗦两句。除了CryptedHandshakeMessage消息外,其他的消息,比如ApplicationData,校验值都是放在数据包中最后的,比如放在最后的20字节。校验字段也是需要加密和解密的。每个ApplicationData包都需要计算校验,如果失败将会断开连接。

八. 参考链接

  1. HTTPS 温故知新(五) —— TLS 中的密钥计算

  2. 国密(3)- 预主密钥/主密钥计算和Finished消息的加解密_已有客户端或服务端私钥情况下,能够解密或计算出tlcp中的预主密钥、主密钥和工作-CSDN博客

  3. 一文读懂https中密钥交换协议的原理及流程-腾讯云开发者社区-腾讯云

  4. HTTPS网络流量解密方法探索系列(一) - FreeBuf网络安全行业门户




[培训]科锐逆向工程师培训第53期2025年7月8日开班!

最后于 2025-4-28 09:51 被satadrover编辑 ,原因:
收藏
免费 4
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回