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
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.