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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM