Diga adeus ao OpenSSL e olá para os melhores na prática
Faz mais de 40 anos desde a criação do método RSA e é uma história de onde os métodos de criptografia chegaram e se foram. Novos vetores de ataque também surgiram e os métodos que pareciam invencíveis se desintegraram. Agora, temos um trabalho de correção de métodos criptográficos e que geralmente não protegem os dados adequadamente para garantir confidencialidade, integridade e direitos de acesso.
E, portanto, temos DES, 3DES, RC4, RC4, RC5, MD5, SHA-1, SHA-2, SHA-3, RSA, … e muitos outros. Poucas bibliotecas de software acompanharam o desenvolvimento e, portanto, o OpenSSL tem sido o local de fato para procurar o código que implementa esses métodos. É, portanto, o local em que os desenvolvedores procuraram métodos que foram devidamente testados e conhecidos por funcionar em uma variedade de aplicativos. Mas está rangendo, e quase todo mês uma nova vulnerabilidade é encontrada.
O legado do OpenSSL
O OpenSSL é comumente usado por servidores baseados em Linux para implementar a conexão SSL / TLS, e os bugs são comuns. Foi iniciado com Eric A Young e Tim Hudson, em dezembro de 1998, que criaram a primeira versão do OpenSSL (SSLeay – SSL Eric AYoung), que se tornou a Versão 0.9.1. Eric terminou sua pesquisa e saiu para fazer outras coisas, e se envolveu com a Cryptsoft (www.cryptsoft.com) antes de ingressar na RSA Security, onde atualmente é um engenheiro destacado. Depois que Eric partiu, ficou a cargo de Steve (dos EUA) e Stephen Henson (do Reino Unido) para continuar seu desenvolvimento através da OpenSSL Software Foundation (OSF).
O código continuou a crescer com o suporte a TLS adicionado à 1.0.1a em 14 de março de 2012. Infelizmente, em 1 de janeiro de 2012, apareceu um bug que implementava o protocolo Heartbeat (RFC 6520) e acabou resultando em Heartbleed (Figura 1). O bug foi introduzido pela mesma pessoa que escreveu a RFC – Robin Seggleman, um desenvolvedor alemão:
Toda vulnerabilidade significativa recebe um número CVE. Às vezes, eles são associados a um nome comum como Heartbleed (CVE-2014–0160), BEAST (CVE-2011–3389), FREAK (CVE-2015–0204) e assim por diante, mas alguns, como CVE-2016– 2017, simplesmente não tem um nome comum. Esse bug estava relacionado ao OpenSSL 1.0.1 e 1.0.2 e onde um invasor poderia executar um ataque MITM (Man-in-the-Middle) na criptografia AES.
Qualquer pessoa que observe o código OpenSSL verá que ele não tem um foco real na criação de uma abordagem consistente para a integração do código, e precisava de um novo design, especialmente em um mundo focado em dispositivos móveis. O Google inicialmente adquiriu o código OpenSSL com o BoringSSL, mas agora lançou formalmente o Google Tink.
Google Tink
O Google lançou formalmente a versão 1.2.0 do Tink em agosto de 2018, como uma biblioteca criptográfica multiplataforma (Java, C ++ e Objective C). Seu foco principal é substituir o OpenSSL e onde existem ligações complexas e que geralmente eram focadas em sistemas específicos, como DLLs em sistemas Windows. Embora o Google tenha criado o Tink, ele é de código aberto e tem como objetivo criar APIs simples para funções de criptografia e que devem tornar a infraestrutura mais portátil, segura e estável.
Para o Tink – baseado no BoringSSL e agora na versão 1.2.0 – a adoção foi boa e já está integrada ao AdMob, Google Pay, Google Assistant e Firebase. Ele também integra métodos AEAD (criptografia autenticada com dados associados) e que integra chaves de criptografia, uma função de hash e um código de autenticação de mensagens (MAC). O Google também analisou muitos pontos fracos da criptografia e criou um código que soluciona muitos desses problemas.
Os padrões mínimos para AEAD incluem [RFC5116]:
- O texto sem formatação e os dados associados podem ter qualquer comprimento (de 0 a 2³² bytes).
- Suporta autenticação de 80 bits.
- Segurança CCA2 (ataque adaptável ao texto cifrado escolhido).
Google Tink: chave simétrica
Uma operação básica de criptografia é usar a criptografia de chave simétrica e onde Bob e Alice usam a mesma chave para criptografar e também para descriptografar. Bob cria a chave e a passa com segurança para Alice, ou eles usam um método de troca de chaves para gerar uma chave compartilhada:
package com.helloworld;import com.google.crypto.tink.aead.AeadConfig;
import java.security.GeneralSecurityException;import com.google.crypto.tink.Aead;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.aead.AeadFactory;
import com.google.crypto.tink.aead.AeadKeyTemplates;public final class HelloWorld {
public static void main(String[] args) throws Exception {AeadConfig.register();try {KeysetHandle keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_GCM);Aead aead = AeadFactory.getPrimitive(keysetHandle);String plaintext="napier";String aad="qwerty123";System.out.println("Text:"+plaintext);
byte[] ciphertext = aead.encrypt(plaintext.getBytes(), aad.getBytes());
System.out.println("Cipher:"+ciphertext.toString());byte[] decrypted = aead.decrypt(ciphertext, aad.getBytes());
String s = new String(decrypted);
System.out.println("Text:"+s);} catch (GeneralSecurityException e) {
System.out.println(e);
System.exit(1);
}}
}
Uma execução de amostra prova o processo:
Text: hello123
Password: qwerty
Type: 1
Enc type: 128-bit AES GCMCipher: AQbLoE0ino8ofgrvuSSLOKTaYjdPc/ovwWznuMeYfjP+TO1fc6cn7DE=Cipher: 4151624C6F4530696E6F386F666772767553534C4F4B5461596A6450632F6F7677577A6E754D6559666A502B544F31666336636E3744453DDecrypted: hello123
Nesse caso, usamos AES de 128 bits com GCM (modo Galois / contador). Nosso objeto AEAD é criado com:
KeysetHandle keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_GCM);Aead aead = AeadFactory.getPrimitive(keysetHandle);
e, em seguida, os métodos encrypt () e decrypt () são usados para criar o fluxo de cifra e decifrá-lo. Se visualizarmos a chave (em um formato Json), veremos a chave “GhBruFowER7fvpmnsKYNpAuK”, e que é definida pelo formato AesGcmKey:
{
“primaryKeyId”: 689326870,
“key”: [{
“keyData”: {
“typeUrl”: “type.googleapis.com/google.crypto.tink.AesGcmKey”,
“keyMaterialType”: “SYMMETRIC”,
“value”: “GhBruFowER7fvpmnsKYNpAuK”
},
“outputPrefixType”: “TINK”,
“keyId”: 689326870,
“status”: “ENABLED”
}]
}
Uma demonstração desses métodos está aqui.
O Google pretende focar o setor em métodos de criptografia robustos usando AEAD e com autenticação integrada: AES-EAX (criptografar, depois autenticar e traduzir), AES-GCM, AES-CTR-HMAC (redefinição do contador) e envelope KMS. Para criptografia de streaming, esses métodos são convertidos em: AES-GCM-HKDF-STREAMING e AES-CTR-HMAC-STREAMING.
Este objeto AeadKeyTemplates possui as seguintes propriedades:
- AES128_CTR_HMAC_SHA25. Tamanho da chave AES de 16 bytes. Tamanho IV: 16 bytes. Tamanho da chave HMAC: 32 bytes.Tamanho da tag HMAC: 16 bytes. Função de hash HMAC: SHA256.
- AES128_EAX. Tamanho da chave: 16 bytes. Tamanho IV: 16 bytes.
- AES128_GCM Tamanho da chave: 16 bytes.
- AES256_CTR_HMAC_SHA25. Tamanho da chave AES: 32 bytes. Tamanho AES IV: 16 bytes. Tamanho da chave HMAC: 32 bytes. Tamanho da tag HMAC: 32 bytes. Função de hash HMAC: SHA256.
- AES256_EAX. Tamanho da chave: 32 bytes. Tamanho IV: 16 bytes.
- AES256_GCM. Tamanho da chave: 32 bytes.
- CHACHA20_POLY1305.
Google Tink: MAC
Um dos métodos padrão que usamos em criptografia é assinar uma mensagem. Para isso, geramos uma chave de assinatura, que é mantida em segredo para uma série de mensagens. Isso pode estar relacionado a uma única conversa entre Bob e Alice, ou a comunicações de longo prazo entre eles.
Bob ou Alice (ou os dois) criam uma chave privada compartilhada e a passam com segurança. Apenas Bob e Alice terão essa chave, e Eve não será capaz de descobri-la. Toda vez que Bob envia uma mensagem para Alice, ele envia a mensagem com um MAC (código de autenticação de mensagens), que é a mensagem criptografada com a chave privada e depois produzida como um código de hash (SHA-256). Esse método é conhecido como HMAC (código de autenticação de mensagens com base em hash). Alice pega a chave de assinatura e verifica se ela recebe o mesmo MAC. Se o fizer, verificou se ainda é Bob quem está assinando as mensagens. Se não der certo, ela assume que Eve está fingindo ser Bob e descarta a mensagem.
Agora vamos ver como o Google Tink gera códigos MAC. Para isso, geramos uma nova chave MAC com o método getPrimitive () para MacFactory e, em seguida, usamos o computeMac () para criar e confirmMac () para verificar:
KeysetHandle keysetHandle = KeysetHandle.generateNew(MacKeyTemplates.HMAC_SHA256_128BITTAG); Mac mac = MacFactory.getPrimitive(keysetHandle); byte[] tag = mac.computeMac(plaintext.getBytes()); mac.verifyMac(tag,plaintext.getBytes());
Uma amostra de execução que mostra a assinatura de uma mensagem de “hello123” para os formatos Base-64 e Hex:
Text: hello123MAC: ASJFEIAQEqk9MvGaIsJyKcLiN2iw
MAC: 41534A464549415145716B394D76476149734A794B634C694E326977Valid MAC
A chave também pode ser impressa e mostra a chave simétrica usada (e que estamos usando o HMAC):
Printing out key:MAC: {
"primaryKeyId": 574951552,
"key": [{
"keyData": {
"typeUrl": "type.googleapis.com/google.crypto.tink.HmacKey",
"keyMaterialType": "SYMMETRIC",
"value": "EgQIAxAQGiAU/p0/1SEV+O1WE/fvQufi7z+rxQ0W6cJeRtgHHtqMQg=="
},
"outputPrefixType": "TINK",
"keyId": 574951552,
"status": "ENABLED"
}]
}
Podemos ver que a chave de criptografia usada é “EgQIAxAQGiAU / p0 / 1SEV + O1WE / fvQufi7z + rxQ0W6cJeRtgHHtqMQg ==” e que o formato do HmacKey está definido
Se você quiser tentar isso, tente aqui. Aqui está o código:
package com.helloworld;
import java.util.Base64;
import com.google.crypto.tink.aead.AeadConfig;
import java.security.GeneralSecurityException;
import java.io.ByteArrayOutputStream;import com.google.crypto.tink.Aead;import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.Mac;
import com.google.crypto.tink.mac.MacFactory;
import com.google.crypto.tink.mac.MacKeyTemplates;
import org.apache.commons.codec.binary.Hex;
import com.google.crypto.tink.CleartextKeysetHandle;
import com.google.crypto.tink.JsonKeysetWriter;
public final class HelloWorld { public static void main(String[] args) throws Exception {
AeadConfig.register(); try { String plaintext="napier";
if (args.length>0) plaintext=args[0];
System.out.println("Text:\t"+plaintext);
KeysetHandle keysetHandle = KeysetHandle.generateNew(MacKeyTemplates.HMAC_SHA256_128BITTAG); Mac mac = MacFactory.getPrimitive(keysetHandle);
byte[] tag = mac.computeMac(plaintext.getBytes()); byte[] encoded = Base64.getEncoder().encode(tag);
System.out.println("\nMAC (Base64):\t"+ new String(encoded));
System.out.println("MAC (Hex):\t"+ toHexString(encoded));try {
mac.verifyMac(tag,plaintext.getBytes());
System.out.println("\nValid MAC");
} catch (GeneralSecurityException e) {
System.out.println("In Valid MAC");}
System.out.println("\nPrinting out key:");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); CleartextKeysetHandle.write(keysetHandle, JsonKeysetWriter.withOutputStream(outputStream)); System.out.println("\nMAC:\t"+ new String(outputStream.toByteArray()));
} catch (GeneralSecurityException e) {
System.out.println(e);
System.exit(1);
}
} public static String toHexString( byte[] bytes )
{
StringBuffer sb = new StringBuffer( bytes.length*2 );
for( int i = 0; i < bytes.length; i++ )
{
sb.append( toHex(bytes[i] >> 4) );
sb.append( toHex(bytes[i]) );
} return sb.toString();
}
private static char toHex(int nibble)
{
final char[] hexDigit =
{
'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
};
return hexDigit[nibble & 0xF];
}}
Uma demonstração deste método está aqui.
Google Tink: assinaturas digitais
Um dos métodos padrão que usamos em criptografia é assinar uma mensagem com uma chave privada e provar a assinatura com o público. Portanto, se Bob tiver um par de chaves, ele usará sua chave privada para assinar a mensagem e, em seguida, Alice provará que foi Bob quem a assinou, usando a chave pública de Bob. Também provará que a mensagem não foi alterada por Eva.
Nós usamos este método para muitas aplicações. Um exemplo está nas transferências de Bitcoin e onde Bob assina uma transação para pagar a Alice um determinado número de bitcoins. Ele assina essa transação com sua chave privada (que está na carteira) e a adiciona, com sua chave pública ao blockchain. Quem quiser verificar a transferência verificará a assinatura com a chave pública de Bob.
Agora, vamos ver como o Google Tink gera códigos de assinaturas digitais. O método principal é usar o ECDSA (algoritmo de assinatura digital de curva elíptica). Para isso, geramos um novo par de chaves com o método getPrimitive () para PublicKeySignFactory. Podemos então usar a chave privada do par de chaves para assinar uma mensagem com o método sign ():
KeysetHandle privateKeysetHandle = KeysetHandle.generateNew(SignatureKeyTemplates.ECDSA_P256);PublicKeySign signer = PublicKeySignFactory.getPrimitive(privateKeysetHandle);byte[] signature = signer.sign(plaintext.getBytes());
Um exemplo de execução que mostra a assinatura de uma mensagem de “hello” para os formatos Base-64 e Hex é:
Text: helloMAC (Base64): ATHO2ZIwRAIgd+JV6SOM08i01AFsrGR8JLenLDWtPKzoWUDRh4tBqC8CIHEtnVf8OR9QxLQitKVvwxm7FlrkfLnVMC0G0nql3j5d
MAC (Hex):4154484F325A497752414967642B4A5636534F4D3038693031414673724752384A4C656E4C445774504B7A6F575544526834744271433843494845746E5666384F523951784C5169744B567677786D37466C726B664C6E564D433047306E716C336A3564Valid Signature
Em termos do par de chaves, podemos ver o par de chaves aqui:
Printing out key:
{
“primaryKeyId”: 835639698,
“key”: [{
“keyData”: {
“typeUrl”: “type.googleapis.com/google.crypto.tink.EcdsaPrivateKey”,
“keyMaterialType”: “ASYMMETRIC_PRIVATE”,
“value”: “EkwSBggDEAIYAhogafr30IV05SpchJZvJfQ6ChyWxhNIVSjpmztkJ+wYRusiIHCl4fYPSpuISdC18N9OhWQ9jZvky6B0ytnL97HqafbrGiEAxJzrrM7H3ONlzTRMdoPwWJ/PwkaQ2rCsUK5i3wGZMZQ=”
},
“outputPrefixType”: “TINK”,
“keyId”: 835639698,
“status”: “ENABLED”
}]
}
Podemos ver que a chave de criptografia usada é “EkwSBggDEAIYAho… GZMZQ =” e que o formato da EcdsPrivateKey está definido. O código é:
package com.helloworld;
import java.util.Base64;
import com.google.crypto.tink.aead.AeadConfig;
import java.security.GeneralSecurityException;
import java.io.ByteArrayOutputStream;import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.PublicKeySign;
import com.google.crypto.tink.PublicKeyVerify;
import com.google.crypto.tink.signature.PublicKeySignFactory;
import com.google.crypto.tink.signature.PublicKeyVerifyFactory;
import com.google.crypto.tink.signature.SignatureKeyTemplates;import org.apache.commons.codec.binary.Hex;
import com.google.crypto.tink.CleartextKeysetHandle;
import com.google.crypto.tink.JsonKeysetWriter;import com.google.crypto.tink.config.TinkConfig;public final class HelloWorld {public static void main(String[] args) throws Exception {TinkConfig.register();try {String plaintext="napier";if (args.length>0) plaintext=args[0];System.out.println("Text:\t"+plaintext);
KeysetHandle privateKeysetHandle = KeysetHandle.generateNew(SignatureKeyTemplates.ECDSA_P256);PublicKeySign signer = PublicKeySignFactory.getPrimitive(privateKeysetHandle);byte[] signature = signer.sign(plaintext.getBytes());byte[] encoded = Base64.getEncoder().encode(signature);
System.out.println("\nMAC (Base64):\t"+ new String(encoded));
System.out.println("MAC (Hex):"+ toHexString(encoded));KeysetHandle publicKeysetHandle = privateKeysetHandle.getPublicKeysetHandle();PublicKeyVerify verifier = PublicKeyVerifyFactory.getPrimitive( publicKeysetHandle);try {
verifier.verify(signature, plaintext.getBytes());
System.out.println("\nValid Signature");
} catch (GeneralSecurityException e) {
System.out.println("In Valid Signature");}System.out.println("\nPrinting out key:");ByteArrayOutputStream outputStream = new ByteArrayOutputStream();CleartextKeysetHandle.write( privateKeysetHandle, JsonKeysetWriter.withOutputStream(outputStream));System.out.println(new String(outputStream.toByteArray()));} catch (GeneralSecurityException e) {
System.out.println(e);
System.exit(1);
}}public static String toHexString( byte[] bytes )
{
StringBuffer sb = new StringBuffer( bytes.length*2 );
for( int i = 0; i < bytes.length; i++ )
{
sb.append( toHex(bytes[i] >> 4) );
sb.append( toHex(bytes[i]) );
}return sb.toString();
}
private static char toHex(int nibble)
{
final char[] hexDigit =
{
'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
};
return hexDigit[nibble & 0xF];
}}
Uma demonstração deste método está aqui.
Conclusões
Com o Google Tink, finalmente obtemos um código que foi hackeado ao longo dos anos (como o OpenSSL) e que é consistente em seu uso e focado na integração da API. É um código baseado em um mundo moderno. A assinatura com a chave privada é uma das ações mais fundamentais que temos em nosso mundo moderno e é basicamente a nossa identidade. Portanto, precisamos proteger nossas chaves privadas, caso contrário, teremos nossa identidade digital roubada.
FONTE: https://medium.com/coinmonks/cryptography-with-google-tink-33a70d71918d