Desenvolvimento

9 set, 2014

Descriptografia de web sites HTTPS – Parte 03: Cálculo de chaves

Publicidade

Esta é a terceira parte da série de artigos sobre descriptografia de sites HTTPS. Estamos abordando como é feita a conexão do Cliente e Servidor para chegar a um estado seguro.

No artigo anterior, falamos sobre o envio do código secreto, e entendemos porque o protocolo é seguro e porque, mesmo quando essas informações são interceptadas, elas não podem ser decifradas. Hoje vamos continuar falando sobre Cálculo de Chaves.

Para garantir a segurança do protocolo, o valor da chave Master Secret, não é enviado do servidor para o cliente e nem do cliente para o servidor. Ela será calculada nos dois lados.

A integridade da chave, ou seja, a garantia de que ambos irão chegar ao mesmo resultado, é o envio da Pre Master Key pelo cliente criptografada com a Public Key. Lembrando que esta só pode ser descriptografada com a Private Key do servidor, pois ambas são relacionadas matematicamente. Também será usado os valores do Client Random e do Server Random. Que foi trocada nos pacotes anteriormente. O algoritmo para o cálculo é conhecido como PRF (Pseudo Random Function). Este pode variar conforme a versão negociada no inicio da conexão do protocolo TLS.

Após obter o valor da Master Secret, um algoritmo é utilizado para gerar o valor do Key Block, também conhecido como Key Material. Esse valor será dividido para ser utilizado como chave de criptografia e descriptografia do servidor e do cliente.

Mensagem de confirmação

fs01

fs02

As próximas mensagens tendem a vir no mesmo pacote, confirmando a versão do protocolo utilizado e uma mensagem criptografada para testar o cálculo do servidor e confirmar que ambos estão se comunicando com o mesmo segredo.

Implementação

Será implementado o métodos PRF e a P_SHA256 da classe SessionTLS, que irá calcular o valor da Master Secret e da Key Block.

Esses métodos seguem os algoritmos da norma RFC 5246, da IETF. O nome utilizado para o método P_SHA256 se deve ao fato de conter na norma o método P_<hash> e também conter que o TLS 1.2 deve utilizar o hash SHA 256.

A função PRF é composta do algoritmo da RFC 5246:

PRF(secret, label, seed) = P_<hash>(secret, label + seed)

Segue a implementação do método em PHP:

public function PRF($secret, $label, $seed, $size) 
{
	return self::P_SHA256($secret, $label . $seed, $size);
}

Sendo que a função P_<hash> é composta pelo algoritmo da RFC 5246:

P_<hash>(secret, seed) = HMAC_hash(secret, A(1) + seed) + HMAC_hash(secret, A(2) + seed) + HMAC_hash(secret, A(3) + seed) + ...

Onde <hash> é SHA 256.

O final das iterações termina com o tamanho necessário da string concatenada, que é passado por parâmetro. Segue a implementação do método em PHP:

public function P_SHA256($secret, $seed, $size) 
{
	$output = "";
	$a = $seed;	

	while (strlen($output) < $size) 
	{
		$a = hash_hmac("sha256", $a, $secret, true);
		$output .= hash_hmac("sha256", $a . $seed, $secret, true);
	}
	return substr($output, 0, $size);
}

Com os dois métodos acima podemos agora implementar o método generateMasterSecret, que serve para calcular a Master Secret. Conforme a RFC 5246, o cálculo da Master Secret é composto por:

master_secret = PRF(pre_master_secret, "master secret", ClientHello.random + ServerHello.random) [0..47];

O valor da pre_master_secret, ClientHello.random e ServerHello.random são os valores que temos nos atributos na classe SessionTLS. O valor “master secret” é uma string representando o label da função PRF. Já o valor [0..47] indica que o tamanho da master_secret, deve ser de 0 à 47 bytes, ou seja, 48 bytes. Portanto, iremos chamar a PRF atribuindo no parâmetro $size o valor 48.

Segue a implementação do método em PHP:

public function generateMasterSecret() 
{		
	$this->master_secret = self::PRF($this->pre_master_secret, 'master secret', $this->client_random . $this->server_random, 48);
}

Após obter a Master Secret, iremos para o cálculo da Key Block. Conforme a RFC 5246, o valor da Key Block é composto por:

key_block = PRF(SecurityParameters.master_secret, “key expansion”, SecurityParameters . server_random + SecurityParameters.client_random);

Os valores SecurityParameters.master_secret, SecurityParameters.server_random e SecurityParameters.client_random são os valores que temos nos atributos da classe SessionTLS.

Você deve ter percebido que desta vez a RFC 4256 não mostrou o tamanho de retorno, consequentemente as iterações não teriam fim. Isso se deve ao fato de que cada cifra possui um determinado tamanho para a geração do Key Block.

Na mesma RFC encontramos uma tabela indicando o tamanho das principais cifras e que cada MAC key deverá ter, lembrando que o MAC Key é utilizado para confirmar a autenticidade da mensagem.

Cipher        Type    Key Material      IV Size Block Size
NULL Stream 0 0 N/A
RC4_128 Stream 16 0 N/A
3DES_EDE_CBC Block 24 8 8
AES_128_CBC Block 16 16 16
AES_256_CBC Block 32 16 16

 

MAC       Algorithm    mac_length  mac_key_length
NULL N/A 0 0
MD5 HMAC-MD5 16 16
SHA HMAC-SHA1 20 20
SHA256 HMAC-SHA256 32 32

Observação: existem além dessa, outras Cipher Suites, com outros diferentes tamanho. Aqui estamos demonstrando a tabela que está na RFC 5246.

O cálculo para o tamanho do key_block deve ser dobrado, pois iremos dividir o bloco para a leitura e escrita.

Como a nossa Cipher Suite é”TLS_RSA_WITH_AES_128_CBC_SHA”, então pegamos na tabela primeiramente o valor de AES_128_CBC equivalente ao Key Material de 16 bytes, mais o valor do MAC Length igual a 20 bytes, e o valor do IV Size igual a 16. Portanto temos 20 + 16 + 16 = 52, dobrando o valor temos 104 bytes.

Para facilitar o cálculo, criamos os métodos parserCipherMac, getKeyMaterialBytes, getIVSizeBytes e getMacLengthBytes para a classe SessionTLS, que criamos na primeira parte do artigo, segue a implementação em PHP:

public function parserCipherMac()
{
	$posWith = strpos($this->cipher_suite, "WITH_");
	$arrayCipherSuite = explode("_", substr($this->cipher_suite, $posWith + 5));
	$this->mac = array_reverse($arrayCipherSuite)[0];
	unset($arrayCipherSuite[count($arrayCipherSuite)-1]);
	$this->cipher = implode("_",$arrayCipherSuite);		
}
public function getKeyMaterialBytes()
{	
	switch($this->cipher)
	{
		default: throw new Exception("Cipher não encontrada!");
		case "RC4_128": return 16;
		case "3DES_EDE_CBC": return 24; 
		case "AES_128_CBC": return 16;
		case "AES_256_CBC": return 32;
	}	
}

public function getIVSizeBytes()
{
	switch($this->cipher)
	{
		default: throw new Exception("Cipher não encontrada");
		case "RC4_128": return 0;
		case "3DES_EDE_CBC": return 8; 
		case "AES_128_CBC": return 16; 
		case "AES_256_CBC": return 16; 
	}
}
public function getMacLengthBytes()
{
	switch($this->mac)
	{
		default: throw new Exception("Cipher não encontrada");
		case "MD5": return 16;
		case "SHA": return 20;
		case "SHA256": return 32;
	}
}

O Key Block, conforme a RFC 5246 é dividido em três parâmetros para escrita e três para leitura com o tamanho da tabela descrita acima. Ou seja, são três para a mensagem que é enviada e três para a mensagem que é recebida do servidor. Os parâmetros são:

client_write_MAC_key[SecurityParameters.mac_key_length]
server_write_MAC_key[SecurityParameters.mac_key_length]
client_write_key[SecurityParameters.enc_key_length]
server_write_key[SecurityParameters.enc_key_length]
client_write_IV[SecurityParameters.fixed_iv_length]
server_write_IV[SecurityParameters.fixed_iv_length]

Agora que temos o tamanho do Key Block, segue implementação em PHP:

public function generateKeyBlock() 
{		
	self::parserCipherMac();
	$key_block_size = (self::getKeyMaterialBytes() + self::getIVSizeBytes() + self::getMacLengthBytes()) * 2;		
	$key_block = self::PRF($this-> master_secret, 'key expansion', $this->server_random . $this->client_random , $key_block_size);

	//A variável $pointer, indica a parte da string que está sendo cortada
	$pointer = 0;

	$this->client_write_MAC_key = substr($key_block, $pointer, 	self::getMacLengthBytes());		
	$pointer += self::getMacLengthBytes();
	$this->server_write_MAC_key = substr($key_block, $pointer, 	self::getMacLengthBytes());		
	$pointer += self::getMacLengthBytes();
	$this->client_write_key = substr($key_block, $pointer, 	self::getKeyMaterialBytes());	
	$pointer += self::getKeyMaterialBytes();
	$this->server_write_key = substr($key_block, $pointer, 	self::getKeyMaterialBytes());	
	$pointer += self::getKeyMaterialBytes();	
	$this->client_write_IV = substr($key_block, $pointer, self::getIVSizeBytes());		
	$pointer += self::getIVSizeBytes();
	$this->server_write_IV = substr($key_block, $pointer, self::getIVSizeBytes());	
	$pointer += self::getIVSizeBytes();	
}

Por fim, chamamos os dois métodos que irão gerar a Master Secret e o Key Block, considerando que temos a instância $TLS da classe SessionTLS na implementação da primeira parte do artigo:

$TLS->generateMasterSecret();
$TLS->generateKeyBlock();

No próximo artigo iremos utilizar os valores das chaves acima para decodificar as mensagens trocadas entre cliente e servidor.