{"id":154,"date":"2026-06-19T17:03:43","date_gmt":"2026-06-19T17:03:43","guid":{"rendered":"https:\/\/shattered.io\/pt\/2026\/06\/19\/ecdh-nodejs-troca-de-chaves\/"},"modified":"2026-06-19T17:05:14","modified_gmt":"2026-06-19T17:05:14","slug":"ecdh-nodejs-troca-de-chaves","status":"publish","type":"post","link":"https:\/\/shattered.io\/pt\/ecdh-nodejs-troca-de-chaves\/","title":{"rendered":"ECDH em Node.js: Troca de Chaves em 11 Passos [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">O ECDH (Elliptic Curve Diffie-Hellman) \u00e9 a base criptogr\u00e1fica do TLS 1.3, do Signal Protocol e de praticamente toda a troca de chaves segura moderna. Em 2026, com 70,1% dos sites a usar TLS 1.3 segundo a Qualys SSL Labs, compreender ECDH deixou de ser opcional para qualquer programador que trabalhe com seguran\u00e7a. Este tutorial mostra como implementar ECDH em Node.js, do zero ao projeto completo, em 11 passos concretos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"o-que-e-ecdh-e-porque-importa-em-2026\">O que \u00e9 ECDH e Porqu\u00ea Importa em 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O ECDH \u00e9 um protocolo de troca de chaves que permite a duas partes, sem comunica\u00e7\u00e3o pr\u00e9via, chegar ao mesmo segredo partilhado atrav\u00e9s de uma rede insegura. Baseia-se no problema do logaritmo discreto em curvas el\u00edpticas, um problema matematicamente muito mais dif\u00edcil de resolver do que a fatoriza\u00e7\u00e3o de inteiros usada pelo RSA cl\u00e1ssico.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A vantagem pr\u00e1tica \u00e9 enorme. Uma chave P-256 de 256 bits oferece seguran\u00e7a equivalente a uma chave RSA de 3072 bits, com opera\u00e7\u00f5es criptogr\u00e1ficas significativamente mais r\u00e1pidas. Em benchmarks de produ\u00e7\u00e3o publicados em 2025, a migra\u00e7\u00e3o para TLS 1.3 com ECDHE reduziu a lat\u00eancia p95 de 318 ms para 194 ms, uma melhoria de 40%, e o consumo de CPU do balanceador de carga caiu 28%. A taxa de falhas no handshake caiu de 1,2% com TLS 1.2 para 0,4% com TLS 1.3.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">No TLS 1.3, o RSA como mecanismo de troca de chaves foi completamente removido. O protocolo usa exclusivamente (EC)DHE, garantindo <strong>forward secrecy<\/strong> por defeito. Mesmo que um atacante grave todo o tr\u00e1fego cifrado e depois obtenha a chave privada do servidor, n\u00e3o consegue desencriptar as sess\u00f5es passadas. Cada sess\u00e3o gera um par de chaves ef\u00e9mero que \u00e9 destru\u00eddo ap\u00f3s a troca.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O Node.js exp\u00f5e ECDH atrav\u00e9s do m\u00f3dulo <code>node:crypto<\/code>, com suporte nativo para curvas NIST (P-256, P-384, P-521) e curvas modernas de alta performance (X25519, X448). N\u00e3o \u00e9 necess\u00e1ria nenhuma depend\u00eancia externa para o n\u00facleo do protocolo. Para projetos onde a seguran\u00e7a da cadeia de fornecimento \u00e9 cr\u00edtica, eliminar pacotes npm \u00e9 um ganho real: menos vetores de ataque, menos atualiza\u00e7\u00f5es de seguran\u00e7a urgentes, menos risco de compromisso via supply chain.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O Signal Protocol, o WireGuard e o Noise Protocol Framework usam X25519 (uma variante de ECDH sobre Curve25519) como base de toda a criptografia de sess\u00e3o. A ado\u00e7\u00e3o de 58% de 0-RTT em TLS 1.3 (dado de 2025) demonstra que as sess\u00f5es de retorno beneficiam ainda mais, com lat\u00eancia zero no handshake para utilizadores recorrentes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"pre-requisitos-versoes-e-ferramentas-necessarias\">Pr\u00e9-requisitos: Vers\u00f5es e Ferramentas Necess\u00e1rias<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Antes de come\u00e7ar, verifica se tens o ambiente correto. A tabela seguinte lista o que \u00e9 obrigat\u00f3rio e as vers\u00f5es m\u00ednimas recomendadas para 2026.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Componente<\/th><th>Vers\u00e3o M\u00ednima<\/th><th>Vers\u00e3o Recomendada<\/th><th>Motivo<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>18.0.0 LTS<\/td><td>22.x LTS<\/td><td>HKDF s\u00edncrono, KeyObject API<\/td><\/tr><tr><td>OpenSSL (inclu\u00eddo)<\/td><td>3.0<\/td><td>3.3+<\/td><td>X25519, X448, curvas modernas<\/td><\/tr><tr><td>npm<\/td><td>9.x<\/td><td>10.x<\/td><td>lockfile v3, audit melhorado<\/td><\/tr><tr><td>Sistema Operativo<\/td><td>Linux \/ macOS \/ Windows<\/td><td>Linux (Ubuntu 22.04+)<\/td><td>Melhor suporte OpenSSL nativo<\/td><\/tr><tr><td>Editor<\/td><td>Qualquer<\/td><td>VS Code com ESLint<\/td><td>Autocompletion para API crypto<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Verifica a vers\u00e3o do Node.js com <code>node --version<\/code>. Para confirmar as curvas dispon\u00edveis no teu build de OpenSSL, executa o seguinte comando:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node -e \"\nconst c = require('crypto');\nconst nist = c.getCurves().filter(x => ['prime256v1','secp384r1','secp521r1'].includes(x));\nconst modernas = c.getCurves().filter(x => ['X25519','X448'].includes(x));\nconsole.log('Curvas NIST dispon\u00edveis:', nist);\nconsole.log('Curvas modernas dispon\u00edveis:', modernas);\nconsole.log('Node.js:', process.version);\n\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Se aparecerem <code>prime256v1<\/code>, <code>secp384r1<\/code>, <code>secp521r1<\/code> e <code>X25519<\/code>, o ambiente est\u00e1 pronto. Caso o X25519 n\u00e3o apare\u00e7a, atualiza o Node.js para a vers\u00e3o 20.x LTS ou superior. N\u00e3o s\u00e3o necess\u00e1rios pacotes npm externos para este tutorial: o m\u00f3dulo <code>node:crypto<\/code> cobre tudo, desde a gera\u00e7\u00e3o de chaves at\u00e9 \u00e0 encripta\u00e7\u00e3o AES-256-GCM final.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"como-funciona-a-criptografia-de-curva-eliptica\">Como Funciona a Criptografia de Curva El\u00edptica<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Uma curva el\u00edptica \u00e9 definida pela equa\u00e7\u00e3o <code>y\u00b2 = x\u00b3 + ax + b<\/code> sobre um corpo finito. Os pontos que satisfazem esta equa\u00e7\u00e3o formam um grupo matem\u00e1tico com uma opera\u00e7\u00e3o de adi\u00e7\u00e3o geom\u00e9trica. A partir de um ponto base p\u00fablico <code>G<\/code>, \u00e9 f\u00e1cil calcular <code>nG<\/code> (multiplicar o ponto n vezes), mas, dado <code>nG<\/code> e <code>G<\/code>, descobrir <code>n<\/code> \u00e9 computacionalmente invi\u00e1vel para curvas bem escolhidas. A este problema chama-se Problema do Logaritmo Discreto em Curvas El\u00edpticas (ECDLP).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">No ECDH, a Alice escolhe um n\u00famero privado aleat\u00f3rio <code>a<\/code> e publica <code>A = aG<\/code>. O Bob escolhe <code>b<\/code> e publica <code>B = bG<\/code>. A Alice calcula <code>aB = a(bG) = abG<\/code>. O Bob calcula <code>bA = b(aG) = abG<\/code>. Ambos chegam ao mesmo ponto <code>abG<\/code> sem nunca terem transmitido os seus n\u00fameros privados. Um atacante que veja apenas <code>A<\/code> e <code>B<\/code> teria de resolver o ECDLP para descobrir <code>abG<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A coordenada x do ponto resultante \u00e9 usada como segredo partilhado. No entanto, este valor n\u00e3o deve ser usado diretamente como chave de encripta\u00e7\u00e3o. As coordenadas de pontos de curvas el\u00edpticas n\u00e3o s\u00e3o uniformemente distribu\u00eddas como sequ\u00eancias aleat\u00f3rias. Por isso, o passo seguinte \u00e9 sempre passar o segredo por uma fun\u00e7\u00e3o de deriva\u00e7\u00e3o de chaves (KDF), tipicamente HKDF conforme o <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc5869\" target=\"_blank\" rel=\"noopener\">RFC 5869<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A diferen\u00e7a fundamental em rela\u00e7\u00e3o ao Diffie-Hellman cl\u00e1ssico (sobre grupos multiplicativos de inteiros) \u00e9 o tamanho das chaves. Para atingir 128 bits de seguran\u00e7a, o DH cl\u00e1ssico precisa de chaves de 3072 bits. O ECDH atinge o mesmo n\u00edvel com apenas 256 bits. Esta diferen\u00e7a de 12x no tamanho da chave traduz-se em handshakes mais r\u00e1pidos, menos dados transmitidos e menos CPU consumida, como mostram os benchmarks de 2025 citados acima.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"curvas-elipticas-em-node-js-guia-de-selecao-para-2026\">Curvas El\u00edpticas em Node.js: Guia de Sele\u00e7\u00e3o para 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O Node.js suporta m\u00faltiplas curvas com diferentes caracter\u00edsticas de seguran\u00e7a, performance e compatibilidade. Escolher a curva errada pode comprometer a seguran\u00e7a ou criar problemas de interoperabilidade. A tabela seguinte resume as op\u00e7\u00f5es relevantes para 2026.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Curva<\/th><th>Nome em Node.js<\/th><th>Seguran\u00e7a<\/th><th>Chave P\u00fablica<\/th><th>Melhor Para<\/th><\/tr><\/thead><tbody><tr><td>P-256 (secp256r1)<\/td><td><code>prime256v1<\/code><\/td><td>128 bits<\/td><td>33 bytes (comprimida)<\/td><td>TLS 1.3, compatibilidade m\u00e1xima<\/td><\/tr><tr><td>P-384 (secp384r1)<\/td><td><code>secp384r1<\/code><\/td><td>192 bits<\/td><td>49 bytes (comprimida)<\/td><td>Requisitos governamentais NSA Suite B<\/td><\/tr><tr><td>P-521 (secp521r1)<\/td><td><code>secp521r1<\/code><\/td><td>256 bits<\/td><td>67 bytes (comprimida)<\/td><td>Dados ultra-sens\u00edveis, longo prazo<\/td><\/tr><tr><td>X25519<\/td><td><code>X25519<\/code><\/td><td>128 bits<\/td><td>32 bytes<\/td><td>Performance, Signal\/WireGuard<\/td><\/tr><tr><td>X448<\/td><td><code>X448<\/code><\/td><td>224 bits<\/td><td>56 bytes<\/td><td>Seguran\u00e7a extra, projetos novos<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Para a maioria dos projetos web em 2026, a escolha \u00e9 entre <strong>P-256<\/strong> e <strong>X25519<\/strong>. O P-256 oferece compatibilidade m\u00e1xima com sistemas legados e hardware HSM. O X25519 foi desenhado explicitamente para evitar as vulnerabilidades de implementa\u00e7\u00e3o das curvas NIST e \u00e9 mais r\u00e1pido em CPU sem hardware AES-NI. Em termos pr\u00e1ticos: usa P-256 se precisas de certificados X.509 ou integra\u00e7\u00e3o com HSM; usa X25519 se constr\u00f3is um sistema novo sem requisitos de compatibilidade legada.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As curvas P-384 e P-521 s\u00e3o necess\u00e1rias apenas em contextos com requisitos regulat\u00f3rios espec\u00edficos, como sistemas governamentais sujeitos \u00e0 Suite B da NSA ou infraestruturas cr\u00edticas com pol\u00edticas que exigem n\u00edveis de seguran\u00e7a acima de 128 bits. Para a maioria das aplica\u00e7\u00f5es comerciais, P-256 ou X25519 s\u00e3o mais do que suficientes. A situa\u00e7\u00e3o mudar\u00e1 com a migra\u00e7\u00e3o para algoritmos p\u00f3s-qu\u00e2nticos como o ML-KEM (Kyber), padronizado pelo NIST em 2024, mas o ECDH continuar\u00e1 relevante em sistemas h\u00edbridos durante a transi\u00e7\u00e3o.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-1-configurar-o-projeto-node-js\">Passo 1: Configurar o Projeto Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cria uma pasta para o projeto e inicializa o ambiente. Este projeto n\u00e3o requer depend\u00eancias externas, o que simplifica a auditoria de seguran\u00e7a e elimina riscos de supply chain.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Criar e entrar na pasta do projeto\nmkdir ecdh-nodejs && cd ecdh-nodejs\nnpm init -y\n\n# Confirmar vers\u00e3o do Node.js (m\u00ednimo 18.x)\nnode --version\n\n# Verificar curvas dispon\u00edveis no OpenSSL inclu\u00eddo\nnode -e \"\nconst c = require('crypto');\nconsole.log('Curvas NIST:', c.getCurves().filter(x => ['prime256v1','secp384r1','secp521r1'].includes(x)));\nconsole.log('Curvas modernas:', c.getCurves().filter(x => ['X25519','X448'].includes(x)));\n\"\n\n# Sa\u00edda esperada:\n# Curvas NIST: [ 'prime256v1', 'secp384r1', 'secp521r1' ]\n# Curvas modernas: [ 'X25519', 'X448' ]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Adiciona <code>\"type\": \"module\"<\/code> ao <code>package.json<\/code> para usar ES Modules modernos. Em alternativa, podes usar CommonJS com <code>require()<\/code>; a API <code>node:crypto<\/code> funciona da mesma forma em ambos os sistemas de m\u00f3dulos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cria a seguinte estrutura de ficheiros para organizar o projeto:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir -p src\n\n# Estrutura do projeto\n# ecdh-nodejs\/\n# \u251c\u2500\u2500 package.json\n# \u2514\u2500\u2500 src\/\n#     \u251c\u2500\u2500 utils.js        (fun\u00e7\u00f5es ECDH reutiliz\u00e1veis)\n#     \u251c\u2500\u2500 exchange.js     (demonstra\u00e7\u00e3o da troca de chaves)\n#     \u251c\u2500\u2500 crypto.js       (encripta\u00e7\u00e3o AES-256-GCM)\n#     \u2514\u2500\u2500 chat-seguro.js  (projeto completo)\n\ntouch src\/utils.js src\/exchange.js src\/crypto.js src\/chat-seguro.js<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-2-e-3-gerar-chaves-ecdh-e-trocar-chaves-publicas\">Passo 2 e 3: Gerar Chaves ECDH e Trocar Chaves P\u00fablicas<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O passo mais cr\u00edtico do protocolo \u00e9 a gera\u00e7\u00e3o correta do par de chaves. Cada parte deve gerar um par independente e fresco para cada sess\u00e3o (chaves ef\u00e9meras). Reutilizar a mesma chave privada em m\u00faltiplas sess\u00f5es elimina a propriedade de forward secrecy, um dos principais benef\u00edcios do ECDH sobre sistemas de chave p\u00fablica est\u00e1tica.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/utils.js\nimport { createECDH, hkdfSync, randomBytes } from 'node:crypto';\n\n\/\/ Gerar par de chaves ECDH ef\u00e9mero para P-256\nexport function gerarParDeChaves(curva = 'prime256v1') {\n  const ecdh = createECDH(curva);\n  ecdh.generateKeys();\n\n  return {\n    \/\/ Chave privada: NUNCA transmitir ou persistir sem prote\u00e7\u00e3o\n    chavePrivada: ecdh.getPrivateKey('hex'),\n    \/\/ Chave p\u00fablica comprimida: 33 bytes para P-256 (prefixo 0x02 ou 0x03 + coord. x)\n    chavePublica: ecdh.getPublicKey('hex', 'compressed'),\n    instancia: ecdh,\n  };\n}\n\n\/\/ Exportar chave p\u00fablica em base64 para transmiss\u00e3o em JSON\/HTTP\nexport function exportarChavePublica(instancia) {\n  return instancia.getPublicKey('base64', 'compressed');\n}\n\n\/\/ Demonstra\u00e7\u00e3o\nconst exemplo = gerarParDeChaves();\nconsole.log('Chave p\u00fablica (hex, comprimida):', exemplo.chavePublica);\nconsole.log(\n  'Comprimento:',\n  Buffer.from(exemplo.chavePublica, 'hex').length,\n  'bytes'  \/\/ 33 bytes para P-256\n);\n\/\/ Sa\u00edda:\n\/\/ Chave p\u00fablica (hex, comprimida): 03a1b2c3...\n\/\/ Comprimento: 33 bytes<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">O formato comprimido \u00e9 prefer\u00edvel para transmiss\u00e3o em rede. Para P-256, a chave p\u00fablica comprimida ocupa apenas 33 bytes em vez dos 65 bytes do formato n\u00e3o comprimido. O prefixo <code>0x02<\/code> ou <code>0x03<\/code> indica se a coordenada y \u00e9 par ou \u00edmpar, permitindo reconstru\u00e7\u00e3o completa do ponto na rece\u00e7\u00e3o.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A troca de chaves p\u00fablicas deve ser feita sobre um canal autenticado. O ECDH por si s\u00f3 n\u00e3o protege contra ataques man-in-the-middle. Se um atacante substituir a chave p\u00fablica de Alice pela sua pr\u00f3pria antes de Bob a receber, pode estabelecer dois canais ECDH separados e interceptar tudo em claro. Em produ\u00e7\u00e3o, as chaves p\u00fablicas s\u00e3o assinadas com chaves de longo prazo (ECDSA, Ed25519) ou transmitidas atrav\u00e9s de um canal j\u00e1 autenticado como TLS. O artigo sobre <a href=\"\/pt\/assinaturas-digitais-nodejs-ecdsa-ed25519\/\">Assinaturas Digitais em Node.js com ECDSA e Ed25519<\/a> cobre exatamente este passo complementar.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-4-e-5-calcular-o-segredo-e-derivar-chave-com-hkdf\">Passo 4 e 5: Calcular o Segredo e Derivar Chave com HKDF<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Depois da troca de chaves p\u00fablicas, cada parte calcula o segredo partilhado usando a sua chave privada e a chave p\u00fablica do parceiro. O resultado, a coordenada x do ponto da curva resultante, n\u00e3o \u00e9 uma chave de encripta\u00e7\u00e3o direta. \u00c9 necess\u00e1rio passar por HKDF (HMAC-based Key Derivation Function, <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc5869\" target=\"_blank\" rel=\"noopener\">RFC 5869<\/a>) para derivar uma chave sim\u00e9trica com propriedades criptogr\u00e1ficas adequadas.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/exchange.js\nimport { createECDH, hkdfSync, randomBytes } from 'node:crypto';\n\nconst CURVA = 'prime256v1';\nconst ALGORITMO_HASH = 'sha256';\nconst COMPRIMENTO_CHAVE = 32; \/\/ 256 bits para AES-256\n\n\/\/ --- Lado Alice ---\nconst alice = createECDH(CURVA);\nalice.generateKeys();\nconst chavePublicaAlice = alice.getPublicKey(); \/\/ Buffer\n\n\/\/ --- Lado Bob ---\nconst bob = createECDH(CURVA);\nbob.generateKeys();\nconst chavePublicaBob = bob.getPublicKey(); \/\/ Buffer\n\n\/\/ Simula transmiss\u00e3o de chaves p\u00fablicas pela rede\n\/\/ (em produ\u00e7\u00e3o: serializar, assinar e enviar por canal autenticado)\n\n\/\/ --- C\u00e1lculo do segredo partilhado ---\nconst segredoAlice = alice.computeSecret(chavePublicaBob);\nconst segredoBob = bob.computeSecret(chavePublicaAlice);\n\n\/\/ Verifica\u00e7\u00e3o: ambos chegam ao mesmo segredo\nconsole.log('Segredos iguais:', segredoAlice.equals(segredoBob)); \/\/ true\nconsole.log('Segredo bruto (hex):', segredoAlice.toString('hex'));\nconsole.log('Comprimento segredo:', segredoAlice.length, 'bytes'); \/\/ 32 bytes para P-256\n\n\/\/ --- Deriva\u00e7\u00e3o de chave com HKDF ---\n\/\/ Salt: aleat\u00f3rio, transmitido em claro junto com a chave p\u00fablica\nconst salt = randomBytes(32);\n\n\/\/ Info: contexto da deriva\u00e7\u00e3o; chaves distintas para usos distintos\nconst info = Buffer.from('ecdh-aes256gcm-v1', 'utf8');\n\n\/\/ Derivar chave AES-256 a partir do segredo bruto\n\/\/ ATEN\u00c7\u00c3O: hkdfSync() devolve ArrayBuffer; converter para Buffer antes de usar\nconst chaveDerivada = Buffer.from(\n  hkdfSync(\n    ALGORITMO_HASH,\n    segredoAlice,    \/\/ ikm: input key material\n    salt,            \/\/ salt aleat\u00f3rio\n    info,            \/\/ contexto de uso\n    COMPRIMENTO_CHAVE\n  )\n);\n\nconsole.log('\\nChave derivada (AES-256):');\nconsole.log(chaveDerivada.toString('hex'));\nconsole.log('Pronta para AES-256-GCM:', chaveDerivada.length, 'bytes');<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">O <strong>salt<\/strong> do HKDF n\u00e3o precisa de ser secreto, mas deve ser diferente em cada sess\u00e3o. O padr\u00e3o mais comum \u00e9 incluir o salt na mensagem inicial junto com a chave p\u00fablica, em claro. O campo <strong>info<\/strong> permite derivar m\u00faltiplas chaves independentes do mesmo segredo: <code>'encryption-key-v1'<\/code> e <code>'mac-key-v1'<\/code> derivam chaves distintas para encripta\u00e7\u00e3o e autentica\u00e7\u00e3o, mesmo com o mesmo segredo ECDH.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O resultado do HKDF \u00e9 determin\u00edstico: dados os mesmos inputs (segredo, salt, info, comprimento), produz sempre a mesma chave. Isto \u00e9 intencional. Tanto Alice como Bob, depois de calcular o mesmo segredo partilhado ECDH, podem derivar de forma independente a mesma chave AES-256 sem mais comunica\u00e7\u00e3o. A coordena\u00e7\u00e3o \u00e9 impl\u00edcita na matem\u00e1tica do protocolo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-6-e-7-encriptar-e-desencriptar-com-aes-256-gcm\">Passo 6 e 7: Encriptar e Desencriptar com AES-256-GCM<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Com a chave AES-256 derivada, o pr\u00f3ximo passo \u00e9 encriptar e autenticar os dados. O modo GCM (Galois\/Counter Mode) \u00e9 obrigat\u00f3rio para qualquer implementa\u00e7\u00e3o nova em 2026. Ao contr\u00e1rio do CBC, o GCM autentica o texto cifrado com um c\u00f3digo de autentica\u00e7\u00e3o (authentication tag de 128 bits), tornando imposs\u00edvel modificar dados cifrados sem que a desencripta\u00e7\u00e3o falhe. Usar AES-256-CBC em novos projetos \u00e9 um erro de arquitetura em 2026.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/crypto.js\nimport { createCipheriv, createDecipheriv, randomBytes, createECDH, hkdfSync } from 'node:crypto';\n\nconst ALGORITMO = 'aes-256-gcm';\nconst IV_COMPRIMENTO = 12;   \/\/ 96 bits: recomendado para GCM pelo NIST SP 800-38D\nconst TAG_COMPRIMENTO = 16;  \/\/ 128 bits: authentication tag m\u00e1ximo\n\n\/**\n * Encripta com AES-256-GCM\n * @param {Buffer} chave - 32 bytes derivados por HKDF\n * @param {string|Buffer} mensagem - dados em claro\n * @param {Buffer} [aad] - dados adicionais autenticados (opcional mas recomendado)\n *\/\nexport function encriptar(chave, mensagem, aad = null) {\n  const iv = randomBytes(IV_COMPRIMENTO);\n  const cipher = createCipheriv(ALGORITMO, chave, iv, { authTagLength: TAG_COMPRIMENTO });\n\n  if (aad) cipher.setAAD(aad);\n\n  const textoCifrado = Buffer.concat([cipher.update(mensagem), cipher.final()]);\n  const tag = cipher.getAuthTag();\n\n  return { iv, textoCifrado, tag };\n}\n\n\/**\n * Desencripta e verifica autenticidade\n * Lan\u00e7a erro se a tag de autentica\u00e7\u00e3o falhar (dados modificados ou chave errada)\n *\/\nexport function desencriptar(chave, iv, textoCifrado, tag, aad = null) {\n  const decipher = createDecipheriv(ALGORITMO, chave, iv, { authTagLength: TAG_COMPRIMENTO });\n  decipher.setAuthTag(tag);\n\n  if (aad) decipher.setAAD(aad);\n\n  return Buffer.concat([decipher.update(textoCifrado), decipher.final()]);\n}\n\n\/\/ --- Demonstra\u00e7\u00e3o completa ---\nconst CURVA = 'prime256v1';\nconst alice = createECDH(CURVA);\nalice.generateKeys();\nconst bob = createECDH(CURVA);\nbob.generateKeys();\n\nconst segredo = alice.computeSecret(bob.getPublicKey());\nconst salt = randomBytes(32);\nconst info = Buffer.from('chat-v1', 'utf8');\nconst chave = Buffer.from(hkdfSync('sha256', segredo, salt, info, 32));\n\nconst mensagem = 'Ol\u00e1 Bob, esta mensagem est\u00e1 encriptada com ECDH + AES-256-GCM';\n\/\/ AAD: metadados autenticados mas n\u00e3o encriptados (ex: remetente, n\u00famero de sequ\u00eancia)\nconst aad = Buffer.from('alice->bob:seq:1', 'utf8');\n\nconst { iv, textoCifrado, tag } = encriptar(chave, mensagem, aad);\nconsole.log('Texto cifrado (base64):', textoCifrado.toString('base64'));\nconsole.log('IV (hex):', iv.toString('hex'));\nconsole.log('Auth tag (hex):', tag.toString('hex'));\n\n\/\/ Bob desencripta com a mesma chave derivada\nconst chaveBob = Buffer.from(\n  hkdfSync('sha256', bob.computeSecret(alice.getPublicKey()), salt, info, 32)\n);\nconst decifrado = desencriptar(chaveBob, iv, textoCifrado, tag, aad);\nconsole.log('\\nMensagem decifrada:', decifrado.toString('utf8'));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Os <strong>dados adicionais autenticados<\/strong> (AAD) s\u00e3o uma funcionalidade do GCM frequentemente ignorada mas muito \u00fatil. Permitem incluir metadados como identificadores de remetente\/destinat\u00e1rio, n\u00fameros de sequ\u00eancia ou vers\u00f5es de protocolo na autentica\u00e7\u00e3o sem os encriptar. Se um atacante modificar o AAD em tr\u00e2nsito, a desencripta\u00e7\u00e3o falha com erro de autentica\u00e7\u00e3o. Usa AAD sempre que precisas de autenticar contexto que n\u00e3o precisa de confidencialidade.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-8-implementar-x25519-como-alternativa-moderna\">Passo 8: Implementar X25519 como Alternativa Moderna<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O X25519 usa uma abordagem diferente das curvas P-NIST. Foi desenhado por Daniel J. Bernstein especificamente para resistir a erros de implementa\u00e7\u00e3o comuns. As curvas Curve25519 t\u00eam cofator 8 (em vez de 1 nas curvas NIST), o que simplifica a prote\u00e7\u00e3o contra ataques de pequeno subgrupo sem necessitar de valida\u00e7\u00e3o expl\u00edcita do ponto. Em Node.js, X25519 usa a API <code>generateKeyPairSync<\/code> e <code>diffieHellman()<\/code> em vez de <code>createECDH()<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/x25519.js\nimport { generateKeyPairSync, diffieHellman, hkdfSync, randomBytes } from 'node:crypto';\n\n\/\/ Gerar par de chaves X25519 usando a API KeyObject (moderna)\nfunction gerarChavesX25519() {\n  const { privateKey, publicKey } = generateKeyPairSync('x25519');\n  return { privateKey, publicKey }; \/\/ KeyObject: mais flex\u00edvel que Buffer\n}\n\n\/\/ Troca ECDH com X25519 via diffieHellman()\nfunction calcularSegredoX25519(chavePrivadaLocal, chavePublicaPar) {\n  return diffieHellman({\n    privateKey: chavePrivadaLocal,\n    publicKey: chavePublicaPar,\n  });\n}\n\n\/\/ Exemplo completo X25519\nconst alice = gerarChavesX25519();\nconst bob = gerarChavesX25519();\n\n\/\/ Alice calcula com a chave p\u00fablica de Bob\nconst segredoAlice = calcularSegredoX25519(alice.privateKey, bob.publicKey);\n\n\/\/ Bob calcula com a chave p\u00fablica de Alice\nconst segredoBob = calcularSegredoX25519(bob.privateKey, alice.publicKey);\n\nconsole.log('Segredos X25519 iguais:', segredoAlice.equals(segredoBob)); \/\/ true\nconsole.log('Comprimento:', segredoAlice.length, 'bytes'); \/\/ 32 bytes\n\n\/\/ Deriva\u00e7\u00e3o HKDF: id\u00eantica \u00e0 de P-256\nconst salt = randomBytes(32);\nconst chave = Buffer.from(\n  hkdfSync('sha256', segredoAlice, salt, Buffer.from('x25519-v1'), 32)\n);\nconsole.log('Chave AES-256 derivada de X25519:', chave.toString('hex'));\n\n\/\/ Exportar chave p\u00fablica X25519 para transmiss\u00e3o\nconst chavePublicaBob = bob.publicKey.export({ type: 'spki', format: 'der' });\nconsole.log('Chave p\u00fablica Bob (DER, base64):', chavePublicaBob.toString('base64'));\nconsole.log('Comprimento chave p\u00fablica X25519:', chavePublicaBob.length, 'bytes'); \/\/ ~44 bytes (DER)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A principal diferen\u00e7a de API entre P-256 e X25519 est\u00e1 no facto de X25519 usar <code>generateKeyPairSync('x25519')<\/code> e <code>diffieHellman()<\/code>, enquanto P-256 usa <code>createECDH('prime256v1')<\/code> e <code>computeSecret()<\/code>. O resultado em ambos os casos \u00e9 um segredo de 32 bytes. Ap\u00f3s a deriva\u00e7\u00e3o HKDF, o processo de encripta\u00e7\u00e3o AES-256-GCM \u00e9 exatamente o mesmo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-9-e-10-validacao-de-chaves-e-gestao-de-erros\">Passo 9 e 10: Valida\u00e7\u00e3o de Chaves e Gest\u00e3o de Erros<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A aus\u00eancia de valida\u00e7\u00e3o de chaves \u00e9 um dos erros mais graves em implementa\u00e7\u00f5es ECDH. Se n\u00e3o validares que a chave p\u00fablica recebida pertence \u00e0 curva correta, podes ser vulner\u00e1vel a ataques de pequeno subgrupo ou ataques de chave inv\u00e1lida, onde um atacante envia uma chave constru\u00edda para revelar bits da tua chave privada atrav\u00e9s de m\u00faltiplas sess\u00f5es.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/validacao.js\nimport { createECDH } from 'node:crypto';\n\n\/\/ Comprimentos esperados em bytes por curva e formato\nconst COMPRIMENTOS_ESPERADOS = {\n  'prime256v1': { comprimida: 33, naoComprimida: 65 },\n  'secp384r1':  { comprimida: 49, naoComprimida: 97 },\n  'secp521r1':  { comprimida: 67, naoComprimida: 133 },\n};\n\n\/**\n * Verificar formato da chave p\u00fablica antes de processar\n * @throws {Error} se formato inv\u00e1lido\n *\/\nexport function verificarFormatoChave(chaveBuffer, curva = 'prime256v1') {\n  const esperado = COMPRIMENTOS_ESPERADOS[curva];\n\n  if (!esperado) {\n    throw new Error(`Curva n\u00e3o suportada na valida\u00e7\u00e3o: ${curva}`);\n  }\n\n  const ehComprimida = chaveBuffer.length === esperado.comprimida;\n  const ehNaoComprimida = chaveBuffer.length === esperado.naoComprimida;\n\n  if (!ehComprimida && !ehNaoComprimida) {\n    throw new Error(\n      `Comprimento inv\u00e1lido para ${curva}: recebeu ${chaveBuffer.length} bytes, ` +\n      `esperava ${esperado.comprimida} (comprimida) ou ${esperado.naoComprimida} (n\u00e3o comprimida)`\n    );\n  }\n\n  const prefixo = chaveBuffer[0];\n  if (ehComprimida && prefixo !== 0x02 && prefixo !== 0x03) {\n    throw new Error(`Prefixo inv\u00e1lido para chave comprimida: 0x${prefixo.toString(16)}`);\n  }\n  if (ehNaoComprimida && prefixo !== 0x04) {\n    throw new Error(`Prefixo inv\u00e1lido para chave n\u00e3o comprimida: 0x${prefixo.toString(16)}`);\n  }\n\n  return { valida: true, formato: ehComprimida ? 'comprimida' : 'n\u00e3o-comprimida' };\n}\n\n\/**\n * Valida\u00e7\u00e3o criptogr\u00e1fica completa: verifica que o ponto pertence \u00e0 curva\n * @throws {Error} com c\u00f3digo ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY se inv\u00e1lida\n *\/\nexport function validarPontoNaCurva(chaveBuffer, curva = 'prime256v1') {\n  \/\/ Primeiro verifica formato (r\u00e1pido, sem opera\u00e7\u00f5es de curva)\n  verificarFormatoChave(chaveBuffer, curva);\n\n  \/\/ Depois usa o OpenSSL para validar o ponto (mais lento mas completo)\n  const local = createECDH(curva);\n  local.generateKeys();\n\n  try {\n    const segredo = local.computeSecret(chaveBuffer);\n    if (!segredo || segredo.length === 0) {\n      throw new Error('Segredo vazio ap\u00f3s computeSecret');\n    }\n  } catch (err) {\n    if (err.code === 'ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY') {\n      throw new Error(`Chave p\u00fablica inv\u00e1lida (fora da curva ${curva}): ${err.message}`);\n    }\n    throw err;\n  }\n\n  return true;\n}\n\n\/\/ Uso em produ\u00e7\u00e3o: validar ANTES de calcular o segredo real\nimport { createECDH as createECDHTest } from 'node:crypto';\nconst ecdh = createECDHTest('prime256v1');\necdh.generateKeys();\nconst chaveValida = ecdh.getPublicKey();\n\ntry {\n  verificarFormatoChave(chaveValida, 'prime256v1');\n  console.log('Chave v\u00e1lida:', chaveValida.toString('hex').slice(0, 16) + '...');\n} catch (err) {\n  console.error('Chave rejeitada:', err.message);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Em sistemas de produ\u00e7\u00e3o, a valida\u00e7\u00e3o da chave deve acontecer antes de qualquer c\u00e1lculo criptogr\u00e1fico. Nunca confies em chaves p\u00fablicas recebidas de fontes externas sem valida\u00e7\u00e3o. O Node.js lan\u00e7a <code>ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY<\/code> quando a chave \u00e9 inv\u00e1lida, mas apenas na altura em que tentas calcular o segredo. Validar o formato antes (prefixo e comprimento) \u00e9 mais r\u00e1pido e evita trabalho desnecess\u00e1rio de CPU. Usa os c\u00f3digos de erro para dar feedback ao cliente sem expor detalhes internos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-11-projeto-completo-de-chat-encriptado\">Passo 11: Projeto Completo de Chat Encriptado<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O passo final junta tudo num projeto funcional que simula uma troca de mensagens encriptadas ponta-a-ponta usando ECDH. Este \u00e9 o padr\u00e3o base usado pelo Signal Protocol e pelo WhatsApp no seu sistema de encripta\u00e7\u00e3o de mensagens.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/chat-seguro.js\n\/\/ Projeto completo: chat E2EE com ECDH + HKDF + AES-256-GCM\n\nimport {\n  createECDH,\n  hkdfSync,\n  randomBytes,\n  createCipheriv,\n  createDecipheriv,\n  createHash,\n} from 'node:crypto';\n\nconst CURVA = 'prime256v1';\nconst HASH = 'sha256';\nconst TAMANHO_CHAVE = 32;\nconst TAMANHO_IV = 12;\nconst TAMANHO_TAG = 16;\n\nclass ParticipanteECDH {\n  #instanciaECDH;\n  #nome;\n\n  constructor(nome) {\n    this.#nome = nome;\n    this.#instanciaECDH = createECDH(CURVA);\n    this.#instanciaECDH.generateKeys();\n  }\n\n  get nome() { return this.#nome; }\n\n  \/\/ Chave p\u00fablica comprimida para transmitir ao parceiro\n  get chavePublica() {\n    return this.#instanciaECDH.getPublicKey(null, 'compressed');\n  }\n\n  \/\/ Fingerprint de 8 bytes para verifica\u00e7\u00e3o fora-de-banda (m\u00e9todo Signal)\n  get fingerprint() {\n    return createHash('sha256')\n      .update(this.chavePublica)\n      .digest('hex')\n      .slice(0, 16)\n      .match(\/.{4}\/g)\n      .join(':');\n  }\n\n  \/\/ Derivar chave de sess\u00e3o sim\u00e9trica a partir da chave p\u00fablica do parceiro\n  derivarChaveSessao(chavePublicaParceiro, salt, contexto = 'v1') {\n    const segredo = this.#instanciaECDH.computeSecret(chavePublicaParceiro);\n    const info = Buffer.from(`ecdh-chat:${CURVA}:${contexto}`, 'utf8');\n    return Buffer.from(hkdfSync(HASH, segredo, salt, info, TAMANHO_CHAVE));\n  }\n\n  \/\/ Encriptar mensagem com n\u00famero de sequ\u00eancia para prevenir replay\n  encriptar(chaveSessao, mensagem, seq) {\n    const iv = randomBytes(TAMANHO_IV);\n    const aad = Buffer.from(`seq:${seq}:de:${this.#nome}`, 'utf8');\n\n    const cipher = createCipheriv('aes-256-gcm', chaveSessao, iv, {\n      authTagLength: TAMANHO_TAG,\n    });\n    cipher.setAAD(aad);\n\n    const cifrado = Buffer.concat([cipher.update(mensagem, 'utf8'), cipher.final()]);\n    const tag = cipher.getAuthTag();\n\n    return {\n      iv: iv.toString('base64'),\n      cifrado: cifrado.toString('base64'),\n      tag: tag.toString('base64'),\n      seq,\n      de: this.#nome,\n    };\n  }\n\n  \/\/ Desencriptar e verificar autenticidade\n  desencriptar(chaveSessao, pacote) {\n    const iv = Buffer.from(pacote.iv, 'base64');\n    const cifrado = Buffer.from(pacote.cifrado, 'base64');\n    const tag = Buffer.from(pacote.tag, 'base64');\n    const aad = Buffer.from(`seq:${pacote.seq}:de:${pacote.de}`, 'utf8');\n\n    const decipher = createDecipheriv('aes-256-gcm', chaveSessao, iv, {\n      authTagLength: TAMANHO_TAG,\n    });\n    decipher.setAuthTag(tag);\n    decipher.setAAD(aad);\n\n    return Buffer.concat([decipher.update(cifrado), decipher.final()]).toString('utf8');\n  }\n}\n\n\/\/ --- Simula\u00e7\u00e3o da Troca Completa ---\nconst alice = new ParticipanteECDH('Alice');\nconst bob = new ParticipanteECDH('Bob');\n\nconsole.log('=== Troca de Chaves ECDH P-256 ===');\nconsole.log(`Alice fingerprint: ${alice.fingerprint}`);\nconsole.log(`Bob fingerprint:   ${bob.fingerprint}`);\nconsole.log('(Comparar estes valores por voz\/pessoalmente para confirmar aus\u00eancia de MITM)');\n\n\/\/ Salt partilhado: gerado por Alice, transmitido junto com a chave p\u00fablica\nconst saltSessao = randomBytes(32);\n\n\/\/ Ambos derivam a mesma chave de sess\u00e3o de forma independente\nconst chaveAlice = alice.derivarChaveSessao(bob.chavePublica, saltSessao);\nconst chaveBob = bob.derivarChaveSessao(alice.chavePublica, saltSessao);\n\nconsole.log('\\nChaves de sess\u00e3o iguais:', chaveAlice.equals(chaveBob)); \/\/ true\nconsole.log('Chave de sess\u00e3o (hex):', chaveAlice.toString('hex'));\n\n\/\/ Troca de mensagens\nconsole.log('\\n=== Mensagens Encriptadas ===');\nconst p1 = alice.encriptar(chaveAlice, 'Ol\u00e1 Bob! Mensagem protegida com ECDH.', 1);\nconsole.log('\\nAlice envia (cifrado):', p1.cifrado.slice(0, 20) + '...');\n\nconst d1 = bob.desencriptar(chaveBob, p1);\nconsole.log('Bob decifrou:', d1);\n\nconst p2 = bob.encriptar(chaveBob, 'Recebi com sucesso, Alice!', 2);\nconst d2 = alice.desencriptar(chaveAlice, p2);\nconsole.log('Alice decifrou:', d2);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Executa o projeto com <code>node src\/chat-seguro.js<\/code>. A sa\u00edda mostrar\u00e1 os fingerprints das chaves, a confirma\u00e7\u00e3o de que as chaves de sess\u00e3o derivadas de forma independente s\u00e3o iguais, e as mensagens decifradas com sucesso. O <strong>fingerprint<\/strong> de 8 bytes (format <code>xxxx:xxxx:xxxx:xxxx<\/code>) \u00e9 o mecanismo que o Signal usa para verifica\u00e7\u00e3o fora-de-banda: os utilizadores comparam estes c\u00f3digos por chamada de voz ou presencialmente para confirmar a aus\u00eancia de um atacante intermedi\u00e1rio.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"ecdh-vs-rsa-comparacao-tecnica-para-2026\">ECDH vs RSA: Compara\u00e7\u00e3o T\u00e9cnica para 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A escolha entre ECDH e RSA n\u00e3o \u00e9 apenas t\u00e9cnica. Afeta performance, compatibilidade, tamanho dos dados transmitidos e manuten\u00e7\u00e3o a longo prazo. A tabela seguinte compara os dois mecanismos para os casos de uso mais comuns em 2026.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Crit\u00e9rio<\/th><th>ECDH (P-256)<\/th><th>RSA-2048<\/th><th>RSA-3072<\/th><\/tr><\/thead><tbody><tr><td>N\u00edvel de seguran\u00e7a<\/td><td>128 bits<\/td><td>112 bits<\/td><td>128 bits<\/td><\/tr><tr><td>Tamanho da chave p\u00fablica<\/td><td>33 bytes (comprimida)<\/td><td>256 bytes<\/td><td>384 bytes<\/td><\/tr><tr><td>Forward secrecy nativa<\/td><td>Sim (chaves ef\u00e9meras)<\/td><td>N\u00e3o<\/td><td>N\u00e3o<\/td><\/tr><tr><td>Suporte em TLS 1.3<\/td><td>Sim (preferencial)<\/td><td>N\u00e3o (removido)<\/td><td>N\u00e3o (removido)<\/td><\/tr><tr><td>Lat\u00eancia p95 (caso 2025)<\/td><td>194 ms<\/td><td>318 ms (TLS 1.2)<\/td><td>Mais lento<\/td><\/tr><tr><td>CPU balanceador de carga<\/td><td>28% menos<\/td><td>Refer\u00eancia<\/td><td>Refer\u00eancia<\/td><\/tr><tr><td>Falhas de handshake<\/td><td>0,4%<\/td><td>1,2% (TLS 1.2)<\/td><td>N\u00e3o aplic\u00e1vel<\/td><\/tr><tr><td>Resist\u00eancia p\u00f3s-qu\u00e2ntica<\/td><td>Nenhuma<\/td><td>Nenhuma<\/td><td>Nenhuma<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">O RSA continua v\u00e1lido para assinatura digital em certificados X.509 e para autentica\u00e7\u00e3o, mas como mecanismo de troca de chaves foi substitu\u00eddo pelo ECDH no TLS 1.3. Para novos sistemas que n\u00e3o precisam de compatibilidade com TLS 1.2, ECDH (P-256 ou X25519) \u00e9 sempre a escolha correta. O artigo sobre <a href=\"\/pt\/aes-256-vs-chacha20\/\">AES-256 vs ChaCha20<\/a> aborda a escolha do algoritmo sim\u00e9trico a usar ap\u00f3s a deriva\u00e7\u00e3o da chave ECDH.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"5-erros-comuns-que-comprometem-a-seguranca-do-ecdh\">5 Erros Comuns que Comprometem a Seguran\u00e7a do ECDH<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A maioria das vulnerabilidades em implementa\u00e7\u00f5es ECDH n\u00e3o vem de falhas matem\u00e1ticas mas de erros de implementa\u00e7\u00e3o. Estes s\u00e3o os cinco mais frequentes em projetos Node.js auditados.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Erro 1: Usar o segredo bruto como chave de encripta\u00e7\u00e3o.<\/strong> O segredo calculado por <code>computeSecret()<\/code> n\u00e3o \u00e9 uma chave AES direta. As coordenadas de pontos de curvas el\u00edpticas t\u00eam distribui\u00e7\u00e3o n\u00e3o uniforme que pode ser explorada em ataques com m\u00faltiplas observa\u00e7\u00f5es. \u00c9 sempre obrigat\u00f3rio passar o segredo por HKDF antes de o usar como chave sim\u00e9trica. O padr\u00e3o errado <code>const chave = ecdh.computeSecret(parPublico)<\/code> seguido diretamente de <code>createCipheriv('aes-256-gcm', chave, iv)<\/code> est\u00e1 incorreto mesmo que a chave tenha 32 bytes de comprimento correto.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Erro 2: Reutilizar o par de chaves ECDH entre sess\u00f5es.<\/strong> O ECDH ef\u00e9mero garante forward secrecy porque as chaves s\u00e3o destru\u00eddas ap\u00f3s cada sess\u00e3o. Guardar a chave privada ECDH em disco e reutiliz\u00e1-la nas sess\u00f5es seguintes transforma o protocolo num sistema est\u00e1tico que perde toda a prote\u00e7\u00e3o de forward secrecy. As chaves ECDH devem ser geradas de novo a cada sess\u00e3o ou negocia\u00e7\u00e3o de chaves.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Erro 3: N\u00e3o validar a chave p\u00fablica recebida antes de usar.<\/strong> Um atacante pode enviar um ponto inv\u00e1lido (fora da curva) ou um ponto de pequeno subgrupo, reduzindo drasticamente o espa\u00e7o do segredo poss\u00edvel. Valida sempre o formato (prefixo e comprimento) antes de passar ao <code>computeSecret()<\/code>, e captura o erro <code>ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY<\/code> com mensagem adequada ao cliente.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Erro 4: Salt fixo ou ausente no HKDF.<\/strong> Um salt fixo no HKDF reduz a deriva\u00e7\u00e3o a uma fun\u00e7\u00e3o determin\u00edstica apenas no segredo ECDH. Se dois pares distintos produzirem o mesmo segredo (improv\u00e1vel mas poss\u00edvel em teoria), derivar\u00e3o a mesma chave AES. O salt deve ser aleat\u00f3rio, gerado com <code>randomBytes(32)<\/code> a cada sess\u00e3o, e transmitido em claro junto com a chave p\u00fablica. Sem salt aleat\u00f3rio, perdes uma camada de prote\u00e7\u00e3o importante.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Erro 5: Esquecer de converter <code>hkdfSync()<\/code> para Buffer.<\/strong> Este \u00e9 o erro de API mais frequente em Node.js com HKDF. O <code>hkdfSync()<\/code> devolve um <code>ArrayBuffer<\/code>, n\u00e3o um <code>Buffer<\/code>. O <code>createCipheriv()<\/code> espera um <code>Buffer<\/code> e lan\u00e7a um <code>TypeError<\/code> se receber <code>ArrayBuffer<\/code> diretamente. A solu\u00e7\u00e3o \u00e9 sempre <code>Buffer.from(hkdfSync(...))<\/code> antes de usar o resultado em opera\u00e7\u00f5es criptogr\u00e1ficas.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"troubleshooting-8-problemas-e-solucoes\">Troubleshooting: 8 Problemas e Solu\u00e7\u00f5es<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 1: <code>ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY<\/code><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A chave p\u00fablica recebida n\u00e3o pertence \u00e0 curva especificada, tem comprimento errado, ou o prefixo \u00e9 inv\u00e1lido. Solu\u00e7\u00e3o: verifica o comprimento antes de usar (33 bytes para P-256 comprimida, 65 bytes n\u00e3o comprimida). Confirma que ambas as partes usam a mesma curva. Testa o prefixo com <code>Buffer.from(chave, 'base64')[0]<\/code>: deve ser <code>0x02<\/code>, <code>0x03<\/code> (comprimida) ou <code>0x04<\/code> (n\u00e3o comprimida).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 2: Segredos diferentes entre Alice e Bob<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Uma das partes usou a sua pr\u00f3pria chave p\u00fablica em vez da do parceiro, ou as curvas s\u00e3o diferentes. Solu\u00e7\u00e3o: adiciona logs com <code>chavePublica.toString('hex')<\/code> nos dois lados antes de calcular o segredo. O debug mais \u00fatil \u00e9 verificar se o hash SHA-256 da chave p\u00fablica recebida em hex \u00e9 o mesmo em ambos os lados do canal.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 3: <code>Error: Invalid IV length<\/code> em AES-256-GCM<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O IV para GCM deve ter exatamente 12 bytes (96 bits). O OpenSSL 3.x rejeita outros comprimentos. Solu\u00e7\u00e3o: usa sempre <code>randomBytes(12)<\/code> para gerar o IV. Verifica se n\u00e3o est\u00e1s a truncar ou a codificar incorretamente o IV em base64 durante a transmiss\u00e3o.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 4: <code>Unsupported state or unable to authenticate data<\/code><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A authentication tag GCM n\u00e3o corresponde: os dados foram modificados em tr\u00e2nsito, a chave est\u00e1 errada, ou o AAD \u00e9 diferente entre encripta\u00e7\u00e3o e desencripta\u00e7\u00e3o. Solu\u00e7\u00e3o: verifica que o AAD \u00e9 byte-a-byte id\u00eantico nos dois lados. Confirma que a chave derivada por HKDF usa exatamente o mesmo salt, info e comprimento. Compara o hex da chave nos dois processos antes da encripta\u00e7\u00e3o.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 5: X25519 n\u00e3o dispon\u00edvel<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Erro: <code>Invalid EC curve name<\/code> ou <code>crypto.generateKeyPairSync is not a function<\/code>. Causa: Node.js vers\u00e3o anterior a 10.x para X25519, ou OpenSSL compilado sem suporte. Solu\u00e7\u00e3o: atualiza para Node.js 18.x LTS ou superior. Testa com <code>node -e \"require('crypto').generateKeyPairSync('x25519')\"<\/code>. Usa nvm para gerir vers\u00f5es de Node.js em paralelo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 6: Performance lenta com P-521<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O P-521 usa aritm\u00e9tica de 521 bits que n\u00e3o se alinha com arquiteturas de 64 bits e \u00e9 consideravelmente mais lento que P-256 ou X25519. Para a maioria das aplica\u00e7\u00f5es, 128 bits de seguran\u00e7a (P-256) \u00e9 adequado at\u00e9 \u00e0 migra\u00e7\u00e3o para algoritmos p\u00f3s-qu\u00e2nticos. Usa P-521 apenas se tiveres requisitos regulat\u00f3rios espec\u00edficos que exijam 256 bits de seguran\u00e7a cl\u00e1ssica.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 7: <code>TypeError: The \"key\" argument must be...<\/code><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O resultado de <code>hkdfSync()<\/code> devolve um <code>ArrayBuffer<\/code>, n\u00e3o um <code>Buffer<\/code>. O <code>createCipheriv<\/code> espera um tipo espec\u00edfico. Solu\u00e7\u00e3o: converte sempre com <code>Buffer.from(hkdfSync(...))<\/code> antes de usar em <code>createCipheriv<\/code>. Este \u00e9 um dos erros de API mais comuns ao usar HKDF s\u00edncrono em Node.js 18 e vers\u00f5es posteriores.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema 8: Encoding inconsistente causa chaves diferentes<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>getPublicKey('hex', 'compressed')<\/code> e <code>getPublicKey(null, 'compressed')<\/code> retornam o mesmo valor em tipos diferentes (string hex vs Buffer). Se um lado converte de hex para Buffer e o outro passa diretamente, podem surgir diferen\u00e7as nas opera\u00e7\u00f5es seguintes. Solu\u00e7\u00e3o: usa sempre <code>Buffer<\/code> internamente e converte para string apenas na serializa\u00e7\u00e3o final. O encoding base64 \u00e9 recomendado para transmiss\u00e3o em JSON ou HTTP headers.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"dicas-avancadas-e-casos-de-uso-reais\">Dicas Avan\u00e7adas e Casos de Uso Reais<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"derivar-multiplas-chaves-do-mesmo-segredo-ecdh\">Derivar M\u00faltiplas Chaves do Mesmo Segredo ECDH<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Um \u00fanico segredo ECDH pode gerar m\u00faltiplas chaves independentes alterando o campo <code>info<\/code> do HKDF. Num protocolo bidirecional, podes derivar chaves separadas para encripta\u00e7\u00e3o de Alice para Bob e de Bob para Alice, eliminando a possibilidade de um lado usar inadvertidamente as chaves do outro.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Derivar m\u00faltiplas chaves independentes do mesmo segredo\nconst segredo = ecdh.computeSecret(chavePublicaPar);\nconst salt = randomBytes(32);\n\n\/\/ Cada campo 'info' diferente produz uma chave criptograficamente independente\nconst chaveEncAliceBob = Buffer.from(hkdfSync('sha256', segredo, salt, Buffer.from('enc:alice->bob:v1'), 32));\nconst chaveEncBobAlice = Buffer.from(hkdfSync('sha256', segredo, salt, Buffer.from('enc:bob->alice:v1'), 32));\nconst chaveMAC         = Buffer.from(hkdfSync('sha256', segredo, salt, Buffer.from('mac:v1'), 32));\n\n\/\/ Todas as tr\u00eas chaves s\u00e3o criptograficamente independentes entre si\nconsole.log('Chaves iguais?', chaveEncAliceBob.equals(chaveEncBobAlice)); \/\/ false<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"ecdh-em-apis-rest-e-microservicos\">ECDH em APIs REST e Microservi\u00e7os<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Em arquiteturas de microservi\u00e7os, o ECDH \u00e9 usado para encripta\u00e7\u00e3o ponta-a-ponta de dados sens\u00edveis entre servi\u00e7os, mesmo quando a comunica\u00e7\u00e3o j\u00e1 corre sobre TLS. O servi\u00e7o A inclui a chave p\u00fablica ECDH no header HTTP (<code>X-ECDH-Public-Key<\/code>), o servi\u00e7o B responde com a sua, e ambos estabelecem um segredo partilhado para encriptar o payload sens\u00edvel. Isto protege os dados mesmo contra um atacante que tenha comprometido o balanceador de carga TLS ou tenha acesso aos logs do gateway. O artigo sobre <a href=\"\/pt\/mtls-node-js-tls-13\/\">mTLS em Node.js: TLS 1.3<\/a> cobre a camada de transporte que complementa este padr\u00e3o.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Rota\u00e7\u00e3o de chaves e Double Ratchet:<\/strong> Em sistemas de mensagens seguras como o Signal, o ECDH b\u00e1sico \u00e9 estendido com um mecanismo de Double Ratchet. A cada mensagem, uma nova troca ECDH ef\u00e9mera \u00e9 combinada com a chave de sess\u00e3o anterior via HKDF para derivar uma nova chave. Isto garante que comprometer uma chave de sess\u00e3o n\u00e3o exp\u00f5e mensagens passadas (backward secrecy) nem futuras (forward secrecy em granularidade por mensagem). O padr\u00e3o base constru\u00eddo neste tutorial \u00e9 o bloco fundamental de qualquer implementa\u00e7\u00e3o de ratchet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para conformidade com as <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Cryptographic_Storage_Cheat_Sheet.html\" target=\"_blank\" rel=\"noopener\">recomenda\u00e7\u00f5es da OWASP para armazenamento criptogr\u00e1fico<\/a>, usa sempre chaves ef\u00e9meras ECDH, deriva chaves de sess\u00e3o com HKDF, e cifra com AES-256-GCM ou ChaCha20-Poly1305. As diretrizes do <a href=\"https:\/\/csrc.nist.gov\/projects\/cryptographic-standards-and-guidelines\" target=\"_blank\" rel=\"noopener\">NIST para padr\u00f5es criptogr\u00e1ficos<\/a> recomendam P-256 ou P-384 para sistemas que precisem de certifica\u00e7\u00e3o governamental. A especifica\u00e7\u00e3o t\u00e9cnica de X25519 e X448 est\u00e1 no <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc7748\" target=\"_blank\" rel=\"noopener\">RFC 7748 do IETF<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"faq-perguntas-frequentes-sobre-ecdh-em-node-js\">FAQ: Perguntas Frequentes sobre ECDH em Node.js<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"qual-a-diferenca-entre-ecdh-e-ecdsa-em-node-js\">Qual a diferen\u00e7a entre ECDH e ECDSA em Node.js?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">O ECDH \u00e9 um protocolo de <strong>troca de chaves<\/strong>: permite a duas partes chegarem ao mesmo segredo sem nunca o transmitir. O ECDSA \u00e9 um protocolo de <strong>assinatura digital<\/strong>: permite assinar dados de forma que qualquer um com a chave p\u00fablica possa verificar a autenticidade. S\u00e3o opera\u00e7\u00f5es complementares mas distintas. Em TLS 1.3, o ECDH estabelece o segredo da sess\u00e3o enquanto o ECDSA (ou Ed25519) autentica o servidor. Para implementar ECDSA em Node.js, o artigo sobre <a href=\"\/pt\/assinaturas-digitais-nodejs-ecdsa-ed25519\/\">Assinaturas Digitais em Node.js<\/a> cobre o processo completo.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"posso-usar-o-segredo-ecdh-diretamente-como-chave-aes-sem-hkdf\">Posso usar o segredo ECDH diretamente como chave AES sem HKDF?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">N\u00e3o. O segredo partilhado ECDH \u00e9 a coordenada x de um ponto de curva el\u00edptica. Estes valores n\u00e3o t\u00eam distribui\u00e7\u00e3o uniforme como bytes aleat\u00f3rios. Usar o segredo bruto como chave AES pode introduzir vulnerabilidades subtis explor\u00e1veis com m\u00faltiplas observa\u00e7\u00f5es de sess\u00f5es com o mesmo segredo est\u00e1tico. O HKDF garante que a chave derivada tem distribui\u00e7\u00e3o uniforme independentemente das propriedades estat\u00edsticas do segredo ECDH. Esta obrigatoriedade est\u00e1 documentada nas especifica\u00e7\u00f5es do NIST e do IETF.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"o-ecdh-e-resistente-a-computadores-quanticos\">O ECDH \u00e9 resistente a computadores qu\u00e2nticos?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">N\u00e3o. O algoritmo de Shor, executado num computador qu\u00e2ntico suficientemente grande, consegue resolver o ECDLP e quebrar o ECDH em tempo polinomial. O NIST padronizou em 2024 os primeiros algoritmos p\u00f3s-qu\u00e2nticos (ML-KEM, ML-DSA, SLH-DSA) que substituir\u00e3o o ECDH para troca de chaves. A transi\u00e7\u00e3o est\u00e1 em curso, com sistemas h\u00edbridos que combinam ECDH e ML-KEM a serem adotados em TLS 1.3 via extens\u00f5es de negocia\u00e7\u00e3o de grupo. Para o contexto atual de 2026, o ECDH continua seguro contra todos os ataques cl\u00e1ssicos conhecidos.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"como-serializar-as-chaves-ecdh-para-transmissao-em-json\">Como serializar as chaves ECDH para transmiss\u00e3o em JSON?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A forma mais simples \u00e9 base64. Usa <code>ecdh.getPublicKey('base64', 'compressed')<\/code> para obter a chave p\u00fablica como string base64. Na rece\u00e7\u00e3o, converte com <code>Buffer.from(chaveBase64, 'base64')<\/code> antes de passar a <code>computeSecret()<\/code>. Inclui tamb\u00e9m o nome da curva, o salt HKDF e o campo info no mesmo objeto JSON para que o receptor tenha toda a informa\u00e7\u00e3o necess\u00e1ria sem comunica\u00e7\u00e3o adicional. Um objeto de handshake t\u00edpico tem a forma <code>{ curva, chavePublica, salt, info, versao }<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"qual-a-diferenca-de-seguranca-entre-x25519-e-p-256\">Qual a diferen\u00e7a de seguran\u00e7a entre X25519 e P-256?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ambos oferecem 128 bits de seguran\u00e7a cl\u00e1ssica, suficiente contra todos os ataques n\u00e3o-qu\u00e2nticos conhecidos. A diferen\u00e7a est\u00e1 no design e na confian\u00e7a. As curvas P-NIST foram padronizadas com par\u00e2metros cuja origem n\u00e3o \u00e9 completamente transparente. O X25519 foi desenhado com par\u00e2metros escolhidos de forma verific\u00e1vel, audit\u00e1vel e resistente a erros de implementa\u00e7\u00e3o comuns como ataques de timing. Na pr\u00e1tica, ambas s\u00e3o consideradas seguras para uso geral em 2026, mas X25519 \u00e9 preferido em novos protocolos onde a transpar\u00eancia e a resist\u00eancia a erros de implementa\u00e7\u00e3o s\u00e3o priorit\u00e1rias.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"como-depurar-erros-de-autenticacao-gcm-em-producao\">Como depurar erros de autentica\u00e7\u00e3o GCM em produ\u00e7\u00e3o?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">O erro <code>Unsupported state or unable to authenticate data<\/code> pode ter v\u00e1rias causas. Segue esta ordem de verifica\u00e7\u00e3o: (1) compara o hex da chave AES nos dois lados com logging antes de encriptar e desencriptar; (2) verifica que o IV transmitido \u00e9 o mesmo comparando hex nos dois processos; (3) confirma que o AAD \u00e9 byte-a-byte id\u00eantico, incluindo encoding e ordena\u00e7\u00e3o; (4) verifica que a authentication tag n\u00e3o foi truncada ou corrompida na transmiss\u00e3o (deve ter exatamente 16 bytes em buffer). Um script de diagn\u00f3stico que logue todos estes valores antes e depois da transmiss\u00e3o \u00e9 a forma mais r\u00e1pida de isolar a causa.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"posso-usar-ecdh-com-autenticacao-jwt\">Posso usar ECDH com autentica\u00e7\u00e3o JWT?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Sim. Um padr\u00e3o comum \u00e9 usar JWT para autentica\u00e7\u00e3o de sess\u00e3o (quem \u00e9s) e ECDH para encripta\u00e7\u00e3o ponta-a-ponta do payload (o que enviaste). O JWT viaja em claro no header HTTP e \u00e9 verificado pelo servidor via HMAC-SHA256 ou RSA. O payload encriptado com ECDH s\u00f3 \u00e9 leg\u00edvel pelas partes com as chaves privadas corretas, mesmo que o JWT seja v\u00e1lido e interceptado. Esta separa\u00e7\u00e3o mant\u00e9m a auditabilidade do JWT enquanto protege o conte\u00fado sens\u00edvel contra acesso por terceiros com acesso ao canal TLS.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"cobertura-relacionada\">Cobertura Relacionada<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Para aprofundar os conceitos deste tutorial, consulta estes artigos do arquivo de <a href=\"\/pt\/cryptography\/\">criptografia<\/a>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/pt\/assinaturas-digitais-nodejs-ecdsa-ed25519\/\">Assinaturas Digitais em Node.js: ECDSA e Ed25519 em 12 Passos [2026]<\/a><\/li>\n<li><a href=\"\/pt\/aes-256-vs-chacha20\/\">AES-256 vs ChaCha20: 3x Mais R\u00e1pido em Servidores, Qual Escolher [2026]<\/a><\/li>\n<li><a href=\"\/pt\/sha-256-vs-sha-3\/\">SHA-256 vs SHA-3: 2373 vs 686 MB\/s, Qual Escolher [2026]<\/a><\/li>\n<li><a href=\"\/pt\/mtls-node-js-tls-13\/\">mTLS em Node.js: TLS 1.3 em 12 Passos [2026]<\/a><\/li>\n<li><a href=\"\/pt\/autenticacao-ssh-chaves\/\">Autentica\u00e7\u00e3o SSH com Chaves Ed25519: 12 Passos [2026]<\/a><\/li>\n<li><a href=\"\/pt\/autenticacao-dois-fatores-nodejs\/\">Autentica\u00e7\u00e3o de Dois Fatores em Node.js: 12 Passos [2026]<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>O ECDH (Elliptic Curve Diffie-Hellman) \u00e9 a base criptogr\u00e1fica do TLS 1.3, do Signal Protocol e de praticamente toda a troca de chaves segura moderna. Em 2026, com 70,1% dos\u2026<\/p>\n","protected":false},"author":8,"featured_media":155,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-154","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cryptography"],"_links":{"self":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts\/154","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/users\/8"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/comments?post=154"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts\/154\/revisions"}],"predecessor-version":[{"id":156,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts\/154\/revisions\/156"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/media\/155"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/media?parent=154"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/categories?post=154"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/tags?post=154"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}