Criptografia Simétrica

A criptografia simétrica usa a mesma chave para criptografar e descriptografar. A ideia mais importante desta página é a diferença entre uma cifra simples e uma cifra AEAD (Authenticated Encryption with Associated Data). AES-CBC ou AES-CTR sozinhos oferecem confidencialidade e nada mais: um atacante que não consegue ler o texto plano ainda assim pode inverter bits no texto cifrado, e o destinatário vai descriptografar o resultado adulterado sem nenhuma indicação de que algo está errado. AES-GCM e ChaCha20-Poly1305 autenticam além de criptografar, de modo que a adulteração é detectada e a descriptografia falha completamente. Prefira uma cifra AEAD a menos que algo específico force um modo simples.

 

Todas as funções vivem em sgcCrypto_AES, sgcCrypto_Modes, sgcCrypto_ChaCha e sgcCrypto_Poly1305. Nunca reutilize um IV ou um nonce com a mesma chave: para o CTR e as cifras AEAD, a reutilização não apenas enfraquece a confidencialidade, no caso específico do GCM ela quebra completamente a garantia de autenticação. Gere um novo a cada mensagem com sgcRandomBytes.

 

AES-GCM (recomendado)

 

O modo AEAD que a maioria dos protocolos utiliza por padrão. O nonce tem 12 bytes; gere-o aleatoriamente a cada mensagem. aAAD é autenticado mas não criptografado, e é assim que um texto cifrado fica vinculado ao seu contexto, por exemplo um id de mensagem ou uma versão de protocolo, sem esconder esse contexto de quem consegue ver o texto cifrado.

 


function sgcAES_GCM_Encrypt(const aKey, aIV, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcAES_GCM_Decrypt(const aKey, aIV, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;

var
  oKey, oIV, oCipher, oTag, oPlain, oAAD: TBytes;
begin
  oIV := sgcRandomBytes(12);
  oCipher := sgcAES_GCM_Encrypt(oKey, oIV, oPlain, oAAD, oTag);
  { send oIV, oCipher and oTag to the recipient }
  if not sgcAES_GCM_Decrypt(oKey, oIV, oCipher, oAAD, oTag, oPlain) then
    { the ciphertext, the tag or the AAD was altered: reject it };
end;

 

ChaCha20-Poly1305 e XChaCha20-Poly1305

 

O par AEAD da RFC 8439. Enquanto o AES-GCM se apoia na aceleração de hardware do AES, o ChaCha20-Poly1305 roda em velocidade constante em software puro, e é por isso que o TLS e o WireGuard usam-no por padrão em dispositivos móveis e embarcados sem AES-NI. O nonce tem 12 bytes, mesma restrição do GCM. O XChaCha20-Poly1305 estende o nonce para 24 bytes, grande o suficiente para ser escolhido aleatoriamente durante toda a vida útil de uma chave sem controlar um contador, o que importa quando muitos escritores independentes compartilham uma chave e não conseguem coordenar um contador entre si.

 


function sgcChaCha20Poly1305_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcChaCha20Poly1305_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;
function sgcXChaCha20Poly1305_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcXChaCha20Poly1305_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;

 

AES-CCM

Counter with CBC-MAC, RFC 3610 e NIST SP 800-38C. Esta é a cifra AEAD que pilhas restritas e de IoT preferem, porque exige apenas a própria cifra de bloco e nenhuma tabela GHASH: o Zigbee, o 802.15.4, o Bluetooth e as suítes TLS CCM especificam-na. Onde houver espaço, o GCM continua sendo a escolha melhor, já que o CCM faz duas passagens sobre os dados e não pode ser processado em pipeline.

O nonce tem de 7 a 13 bytes e seu comprimento não é uma escolha livre: 15 menos o comprimento do nonce é o tamanho do campo de comprimento, o que fixa o tamanho máximo da mensagem, e um nonce de 13 bytes limita o texto plano a 65535 bytes. Um nonce nunca deve se repetir sob a mesma chave, exatamente como no GCM. aTagLen é 4, 6, 8, 10, 12, 14 ou 16 bytes.


function sgcAES_CCM_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; aTagLen: Integer; out aTag: TBytes): TBytes;
function sgcAES_CCM_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;

var
  oKey, oNonce, oCipher, oTag, oPlain, oAAD: TBytes;
begin
  oNonce := sgcRandomBytes(12);
  oCipher := sgcAES_CCM_Encrypt(oKey, oNonce, oPlain, oAAD, 16, oTag);
  { send oNonce, oCipher and oTag to the recipient }
  if not sgcAES_CCM_Decrypt(oKey, oNonce, oCipher, oAAD, oTag, oPlain) then
    { the ciphertext, the tag or the AAD was altered: reject it };
end;

AES-GMAC

GMAC é exatamente o AES-GCM executado com um texto plano vazio e a mensagem inteira passada como dados adicionais, que é como o padrão o define. Por isso ele herda todas as regras de nonce do GCM: aIV normalmente tem 12 bytes e nunca deve se repetir sob a mesma chave. Reutilizar um IV sob o GMAC não apenas o enfraquece, ele vaza a subchave de hash e permite que um atacante forje tags livremente, de modo que a falha é total, e não gradual. O AES-CMAC na página Hashing & Autenticação de Mensagens não precisa de nenhum IV e é a escolha mais segura a menos que o GCM já esteja no protocolo. A tag tem 16 bytes.


function sgcAES_GMAC(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_GMAC_Verify(const aKey, aIV, aData, aTag: TBytes): Boolean;

Modos de bloco do AES

 

Eles oferecem apenas confidencialidade, sem nenhuma autenticação: combine com um MAC de Hashing & Autenticação de Mensagens se o texto plano precisar de integridade, ou use o AES-GCM acima em vez disso.

 


function sgcAES_CBC_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CBC_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;
function sgcAES_CTR(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_CFB_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CFB_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;
function sgcAES_OFB(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_ECB_Encrypt(const aKey, aPlain: TBytes): TBytes;
function sgcAES_ECB_Decrypt(const aKey, aCipher: TBytes): TBytes;

 

sgcAES_CTR e sgcAES_OFB são suas próprias inversas: chame a mesma função novamente com a mesma chave e o mesmo IV para descriptografar. O ECB está incluído apenas para interoperar com formatos antigos que o especificam; blocos de texto plano idênticos produzem blocos de texto cifrado idênticos, o que vaza a estrutura dos dados, de modo que não deve ser escolhido para nada novo.

 

O CBC também está disponível sem nenhum padding, o modo que o Java grava como AES/CBC/NoPadding. O comprimento da entrada precisa ser um múltiplo exato de 16, e uma exceção é gerada quando isso não ocorre, em vez de a chamada aplicar padding silenciosamente e produzir um texto cifrado que o outro lado não consegue ler. Isso só é seguro onde o protocolo já garante dados alinhados ao bloco.


function sgcAES_CBC_Encrypt_NoPad(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CBC_Decrypt_NoPad(const aKey, aIV, aCipher: TBytes): TBytes;

O ciphertext stealing evita o overhead de padding do CBC para dados que não são um múltiplo do tamanho do bloco:

 


function sgcAES_CTS_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CTS_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;

 

Esquemas de padding

Um modo de bloco exige que o texto plano seja um múltiplo de 16 bytes, e o esquema de padding é como se chega lá. O PKCS#7 é a única escolha certa para algo novo; os outros seis existem para ler e gravar dados de um sistema que já escolheu de outra forma. padPKCS5 não está na lista porque, para um bloco de 16 bytes, ele é o PKCS#7; os dois nomes diferem apenas nos tamanhos de bloco para os quais foram definidos.


type
  TsgcPadding = (padNone, padPKCS7, padZero, padANSIX923, padISO7816_4,
    padISO10126_2, padTBC);

function sgcPad_Add(const aData: TBytes; aBlockSize: Integer; aPadding: TsgcPadding): TBytes;
function sgcPad_Remove(const aData: TBytes; aBlockSize: Integer; aPadding: TsgcPadding; out aPlain: TBytes): Boolean;

function sgcAES_CBC_EncryptPad(const aKey, aIV, aPlain: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_CBC_DecryptPad(const aKey, aIV, aCipher: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_ECB_EncryptPad(const aKey, aPlain: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_ECB_DecryptPad(const aKey, aCipher: TBytes; aPadding: TsgcPadding): TBytes;

var
  oKey, oIV, oPlain, oCipher: TBytes;
begin
  oCipher := sgcAES_CBC_EncryptPad(oKey, oIV, oPlain, padANSIX923);
end;

sgcAES_CBC_Encrypt e sgcAES_ECB_Encrypt, vistas mais acima, são o atalho em PKCS#7 para estas. sgcPad_Remove retorna False para um pad malformado em vez de gerar uma exceção, e segue o mesmo caminho para todo tipo de falha, porque distingui-las é exatamente o que um oráculo de padding precisa. Tenha cuidado especial com padZero: ele não consegue distinguir um byte de padding de um zero final legítimo do texto plano, então remove todo zero à direita, e dados que legitimamente terminam em zero voltam truncados.

Aceleração de hardware do AES (AES-NI)

Em builds Win32 e Win64 do Windows feitos com Delphi, sgcCrypto_AES verifica a CPU em tempo de execução e, quando ela informa AES-NI, PCLMULQDQ e SSSE3, executa a cifragem de bloco único, AES-CTR, AES-GCM (o modo contador e o GHASH) e a cifragem AES-CBC sobre essas instruções, que são de tempo constante por design. Sem elas, e no Linux64, macOS, iOS e Android, é usado o núcleo em software de tempo constante, como antes. A decifragem CBC sempre usa o núcleo em software. O mecanismo TLS nativo se beneficia disso automaticamente.

sgcAES_HardwareAvailable retorna True quando este build traz o caminho de hardware e a CPU o suporta. A variável global sgcAES_UseHardware, True por padrão, definida como False força o núcleo em software, por exemplo para comparar os dois. Defina-a uma única vez na inicialização.

var sgcAES_UseHardware: Boolean = True;
function sgcAES_HardwareAvailable: Boolean;

if sgcAES_HardwareAvailable then
  Memo1.Lines.Add('AES-NI in use');

AES Key Wrap

 

O key wrapping protege uma chave, não uma mensagem: não precisa de IV, é determinístico, e autentica a chave envolvida. Esta é a forma padrão de armazenar uma chave de criptografia de dados sob uma chave mestra, por exemplo uma chave mestra de HSM ou KMS envolvendo uma chave por arquivo. A RFC 3394 exige que a chave sendo envolvida seja um múltiplo de 8 bytes; a RFC 5649 acrescenta padding para chaves de qualquer tamanho.

 


function sgcAES_KeyWrap(const aKEK, aPlainKey: TBytes): TBytes;
function sgcAES_KeyUnwrap(const aKEK, aWrapped: TBytes; out aPlainKey: TBytes): Boolean;
function sgcAES_KeyWrapPad(const aKEK, aPlainKey: TBytes): TBytes;
function sgcAES_KeyUnwrapPad(const aKEK, aWrapped: TBytes; out aPlainKey: TBytes): Boolean;

 

ChaCha20 e Salsa20 (cifras de fluxo brutas)

 

As cifras de fluxo por trás das construções AEAD acima, disponibilizadas diretamente para protocolos que especificam a cifra bruta. Nenhuma das duas autentica: quase sempre as versões AEAD com Poly1305 mais acima nesta página são o que você quer usar. O Salsa20 é o predecessor do ChaCha20, mantido para compatibilidade com formatos da era NaCl; escolha o ChaCha20 para qualquer coisa nova.

 


function sgcChaCha20(const aKey, aNonce, aData: TBytes; aCounter: Cardinal): TBytes;
function sgcXChaCha20(const aKey, aNonce, aData: TBytes; aCounter: Cardinal): TBytes;
function sgcSalsa20(const aKey, aNonce, aData: TBytes; aCounter: Int64): TBytes;
function sgcXSalsa20(const aKey, aNonce, aData: TBytes; aCounter: Int64): TBytes;

 

Legado: DES e NTLM

 

sgcCrypto_Legacy também fornece DES simples em modo ECB e a derivação de chave NTLM v1 baseada em DES. O DES tem uma chave efetiva de 56 bits, forçável por força bruta em hardware moderno em questão de horas; ele existe apenas para interoperar com protocolos que o especificam pelo nome (a autenticação NTLM é o motivo mais comum). Não o escolha para nada novo; use AES em vez disso.

 


function sgcDES_EncryptECB(const aKey, aData: TBytes): TBytes;
function sgcDES_DecryptECB(const aKey, aData: TBytes): TBytes;
function sgcDES_NTLM7to8(const aKey7: TBytes): TBytes;