HTTPS加密流程超详解(二)

2.进入正题

上篇文章介绍了如何简单搭建一个环境帮助我们分析,今天我们就进入正题,开始在这个环境下分析。

我们使用IE浏览器访问Web服务器根目录的test.txt文件并抓包,可以抓到如下6个包(前面的TCP三次握手在此略过):

使用服务器私钥解密后的包是这个样子的:

接下来我们就结合这6个包来分析一下一个完整的HTTPS加解密流程。

第1包

Client Hello是TLS握手的第一步,客户端会将一个随机数、支持的加密套件、压缩算法等信息发送给服务器。

第2包

  1. Server Hello,用来响应客户端的Client Hello,里面同样包含一个32字节的随机数,以及服务端选择的加密套件和压缩算法。
  2. Certificate,服务端会把自己的证书发送给客户端,用来证明自己的身份,证书里面包含一个公钥,供后面的密钥交换使用。(客户端也可以发送证书证明身份,但是比较少见,我们这里就没有客户端证书)。
  3. Server Hello Done,用以表示服务端的密钥交换过程已经结束。

第3包

  1. Client Key Exchange,包含一个使用服务器公钥加密的预主密钥PreMasterSecret,解密后可以用来生成密钥。
  2. Change Cipher Spec,表明握手协议已经完成。
  3. Finished,表示握手结束,这条消息已经被协商好的密钥加密,也可以起到确认密钥的作用。

解密后的Finish消息如下,这里面包含一个Verify Data,是利用PRF函数算出来的,这个函数接下来会介绍,这里我们只需要知道函数的输入参数有:(1)两个hash值,是之前所有握手消息的MD5和SHA1;(2)MasterSecret,由PreMasterSecret生成;(3)finished_label,服务端使用“server finished”,客户端使用“client finished”。

接下来重点介绍下密钥的生成(下面代码中的加解密函数使用了OpenSSL库):

解密Encrypted PreMasterSecret

刚才说到PreMasterSecret被服务器的公钥加密了,所以需要使用服务器的私钥解密,直接上代码:

FILE * priv_fp = fopen("C:\\Users\\hello\\Desktop\\server.key","r");//server.key为之前生成的服务器私钥文件
if (priv_fp == NULL)
{
    printf("read key error\n");
    return -1;
}

RSA *rsa = PEM_read_RSAPrivateKey(priv_fp, NULL, NULL, NULL);
if (rsa == NULL)
{
    printf("read key error\n");
    return -1;
}

len = RSA_private_decrypt(128, encrypted_premaster, premaster, rsa, RSA_PKCS1_PADDING);

可以解密出来48字节的PreMasterSecret:

密钥生成

密钥生成要使用一个很重要的伪随机函数,Pseudo-random Fuction(PRF),PRF函数原理如下:

该函数有3个输入,其中Secret相当于密钥;Label是一个标识符,不同场合会使用不同的字符串,比如“server finished”、"master secret"等;Seed是一个种子值,比如客户端和服务器的随机数。

该函数的代码实现如下:

static int tls_prf(Data *secret,char *usage,Data *rnd1,Data *rnd2,Data *out)
{
    int r,_status;
    Data *md5_out=0,*sha_out=0;
    Data *seed;
    UCHAR *ptr;
    Data *S1=0,*S2=0;
    int i,S_l;

    if(r=r_data_alloc(&md5_out,MAX(out->len,16)))
        return -1;
    if(r=r_data_alloc(&sha_out,MAX(out->len,20)))
        return -1;
    if(r=r_data_alloc(&seed,strlen(usage)+rnd1->len+rnd2->len))
        return -1;
    ptr=seed->data;
    memcpy(ptr,usage,strlen(usage)); ptr+=strlen(usage);
    memcpy(ptr,rnd1->data,rnd1->len); ptr+=rnd1->len;
    memcpy(ptr,rnd2->data,rnd2->len); ptr+=rnd2->len;    

    S_l=secret->len/2 + secret->len%2;

    if(r=r_data_alloc(&S1,S_l))
        return -1;
    if(r=r_data_alloc(&S2,S_l))
        return -1;

    memcpy(S1->data,secret->data,S_l);
    memcpy(S2->data,secret->data + (secret->len - S_l),S_l);

    if(r=tls_P_hash
        (S1,seed,EVP_get_digestbyname("MD5"),md5_out))
        return -1;
    if(r=tls_P_hash(S2,seed,EVP_get_digestbyname("SHA1"),sha_out))
        return -1;

    for(i=0;i<out->len;i++)
        out->data[i]=md5_out->data[i] ^ sha_out->data[i];

    _status=0;
abort:
    r_data_destroy(&md5_out);
    r_data_destroy(&sha_out);
    r_data_destroy(&seed);
    r_data_destroy(&S1);
    r_data_destroy(&S2);
    return(_status);
}

PRF要使用一个扩展函数(P_hash),原理图如下:

该函数的代码实现如下:

static int tls_P_hash(Data *secret,Data *seed,const EVP_MD *md,Data *out)
{
    UCHAR *ptr=out->data;
    int left=out->len;
    int tocpy;
    UCHAR *A;
    UCHAR _A[20],tmp[20];
    unsigned int A_l,tmp_l;
    HMAC_CTX hm;

    A=seed->data;
    A_l=seed->len;

    while(left){
        HMAC_Init(&hm,secret->data,secret->len,md);
        HMAC_Update(&hm,A,A_l);
        HMAC_Final(&hm,_A,&A_l);
        A=_A;

        HMAC_Init(&hm,secret->data,secret->len,md);
        HMAC_Update(&hm,A,A_l);
        HMAC_Update(&hm,seed->data,seed->len);
        HMAC_Final(&hm,tmp,&tmp_l);

        tocpy=MIN(left,tmp_l);
        memcpy(ptr,tmp,tocpy);
        ptr+=tocpy;
        left-=tocpy;
    }

    HMAC_cleanup(&hm);
    return 0;
}

了解了PRF函数后,就可以使用它做密钥生成(密钥扩展)了,下图完整阐述了密钥生成过程:

密钥生成代码如下:

tls_prf(&pre_master_secret, "master secret", &random1, &random2, &master_secret);

tls_prf(&master_secret, "key expansion", &random2, &random1, &key_block);

for (int i=0; i<16; i++)
{
    client_write_key[i] = key_block.data[40+i];
}

第一次调用PRF函数,使用PreMasterSecret、"master secret"和两个随机数(上述服务器和客户端各一个)作为输入参数,输出为一个48字节的主密钥MasterSecret:

第二次调用PRF函数,MasterSecret、"key expansion"和两个随机数作为输入参数,输出为一个Key_block,从41字节开始的16个字节为Client Write key,接下来16个字节为Server Write key,这两个就是接下来双方通信使用的RC4密钥:

第4包

  1. Change Cipher Spec,表明握手协议已经完成。
  2. Finished,表示握手结束,这条消息已经被协商好的密钥加密。

第5、6包

接下来就是传输应用层的信息了,这些信息使用之前协商好的密钥(Client Write key、Server Write key)加密,以客户端为例,解密代码如下:

EVP_CIPHER_CTX ctx;
EVP_CIPHER_CTX_init(&ctx);
int rv, outl;
rv = EVP_DecryptInit_ex(&ctx, EVP_rc4(), NULL, client_write_key, iv);//初始向量IV为0
EVP_DecryptUpdate(&ctx, out, &outl, ciphertext, ciphertextlen);

解密后的最后20个字节为MAC校验,这里使用的是SHA1算法。

解密后的客户端数据:

同理,解密后的服务端数据:

至此,一个完整的HTTPS加解密流程就结束了,过程还是比较简单,只是如果自己实现的话一些细节会比较让人头疼,给出代码可以少走一些弯路,至于更复杂的加密套件,这里就不再介绍,流程应该差不太多,有兴趣的朋友可以研究一下。

参考:http://www.360doc.com/content/16/0320/21/30136251_543905971.shtml

原文地址:https://www.cnblogs.com/realwy/p/8185368.html

时间: 2024-11-09 00:53:31

HTTPS加密流程超详解(二)的相关文章

https加密解密过程详解

在日常互联网浏览网页时,我们接触到的大多都是 HTTP 协议,这种协议是未加密,即明文的.这使得 HTTP 协议在传输隐私数据时非常不安全.因此,用于对 HTTP 协议传输进行数据加密,即 HTTPS . 那么我们再访问https网站时,大家知道https是安全数据加密传输,但是如果让大家仔细描述从访问打开一个网站.到数据整个加解密的流程,估计有很多朋友(可能哈)很难清晰的表达出来吧. 包括我自己描述的也会模拟两可.在此非常有必要详解下整个流程. 要点: https协议对传输内容进行加密,具有更

HTTPS详解二:SSL / TLS 工作原理和详细握手过程

HTTPS 详解一:附带最精美详尽的 HTTPS 原理图 HTTPS详解二:SSL / TLS 工作原理和详细握手过程 在上篇文章HTTPS详解一中,我已经为大家介绍了 HTTPS 的详细原理和通信流程,但总感觉少了点什么,应该是少了对安全层的针对性介绍,那么这篇文章就算是对HTTPS 详解一的补充吧.还记得这张图吧. HTTPS 和 HTTP的区别 显然,HTTPS 相比 HTTP最大的不同就是多了一层 SSL (Secure Sockets Layer 安全套接层)或 TLS (Transp

CentOS6启动过程超详解分析

CentOS 6 开机流程--linux由kernel和rootfs组成.kernel负责进程管理.内存管理.网络管理.驱动程序.文件系统.安全等;rootfs由程序和glibc组成,完善操作系统的功能.同时linux内核的特点是模块化,通过对模块装载卸载可以对内核功能自定义.linux内核文件:/boot/vmlinuz-2.6.32-696.el6.x86_64 整体的流程 BIOS/开机自检 MBR引导(Boot Loader) 启动内核 启动第一个进程init 一.BIOS/开机自检 1

加密 解密过程详解及openssl自建CA &nbsp;

            加密 解密过程详解及openssl自建CA 为了数据信息能够安全的传输要求数据要有一定的安全性那么数据的安全性包含哪些方面的特性呢?    NIST(美国信息安全署)做了如下的定义:    保密性:       1,数据的保密性 指的是数据或隐私不向非授权者泄漏                   2,隐私性  信息不被随意的收集    完整性:       1,数据的的完整性:信息或程序只能被指定或授权的方式改变不能被随意的             修改        

UINavigationController详解二(转)页面切换和SegmentedController

原文出自:http://blog.csdn.net/totogo2010/article/details/7682433,非常感谢. 1.RootView 跳到SecondView 首先我们需要新一个View.新建SecondView,按住Command键然后按N,弹出新建页面,我们新建SecondView 2.为Button 添加点击事件,实现跳转 在RootViewController.xib中和RootViewController.h文件建立连接 在RootViewController.m

C#实现RSA加密和解密详解

原文:C#实现RSA加密和解密详解 RSA加密解密源码: Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Security.Cryptography;

Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)

[Android布局学习系列]   1.Android 布局学习之——Layout(布局)详解一   2.Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)   3.Android 布局学习之——LinearLayout的layout_weight属性   4.Android 布局学习之——LinearLayout属性baselineAligned的作用及baseline    Layout Parameters(布局参数): 在XML文件中,我们经常看到类似与lay

CSS3中的弹性流体盒模型技术详解(二)

在上一篇文章<CSS3中的弹性流体盒模型技术详解(一)>里,我给大家列出了,从css1到css3各版本中盒子模型的基本元素.本篇我会把余下的属性进行详细讲解. box-pack 作用:用来规定子元素在盒子内的水平空间分配方式 box-pack 语法:box-pack: start | end | center | justify; start 对于正常方向的框,首个子元素的左边缘吸附在盒子的左边框显示 对于相反方向的框,最后子元素的右边缘吸附在盒子的右边框显示 end 对于正常方向的框,最后子

onLayout源码 流程 思路详解(ANDROID自定义视图)

简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量--onMeasure():决定View的大小 2.布局--onLayout():决定View在ViewGroup中的位置 3.绘制--onDraw():如何绘制这个View. 而第3步的onDraw系统已经封装的很好了,基本不用我们来操心,只需要专注到1,2两个步骤就中好了. 第一步的测量,可以参考我之前的文章:(ANDROID自定义视图--onMeasure流程,MeasureSpec详解) 而这篇文章就来谈谈第二步:"