← CCNP Security: núcleo SCOR e operação
21 / 25 · 55 MIN

Criptografia: funções, confiança e migração

Executa primitivas pós-quânticas e distingue segredo partilhado, assinatura, confiança e compatibilidade de serviço.

1. Inventariar a função antes do algoritmo

Uma aplicação fictícia envia relatórios confidenciais e recebe pacotes de atualização assinados. São necessidades diferentes: proteger comunicação e verificar a origem/integridade de uma atualização. Antes de escolher substituições, regista protocolo, biblioteca, chave, certificado, validade, proprietário, parceiros e tempo durante o qual os dados precisam de proteção. A descoberta técnica pode encontrar uma biblioteca sem revelar todos os seus utilizadores; confirma dependências com as equipas. A iniciativa NCCoE organiza descoberta e interoperabilidade como partes da migração. Prioriza dados de longa confidencialidade e dependências difíceis de atualizar, sem inventar uma data para chegada de capacidade quântica relevante nem tratar um upgrade de biblioteca como migração completa.

2. Separar encapsulação de assinatura

O laboratório usa ML-KEM-768 para estabelecer um segredo e ML-DSA-65 para assinar uma mensagem sintética. Na execução registada, o ciphertext KEM tem 1088 bytes e o segredo tem 32; a assinatura tem 3309 bytes. São propriedades destes conjuntos de parâmetros e deste formato, não máximos de pacotes de rede. Encapsular para uma chave pública permite a quem tem a chave privada correspondente obter o segredo. Não comprova quem é o dono legítimo dessa chave pública. O teste substitui-a pela chave de outro interveniente e consegue estabelecer um segredo com ele. A identidade precisa de uma ligação confiável à chave e de um protocolo apropriado.

3. Interpretar falhas e contexto

A decapsulação de um ciphertext com um bit alterado, mas comprimento correto, devolve neste ensaio um segredo de 32 bytes diferente do original. “A função não lançou erro” não significa que o recetor obteve o segredo pretendido. O exemplo deriva chaves com HKDF-SHA256 e compara HMAC sobre dados sintéticos para mostrar a divergência. Alterar a mensagem ou o campo info da derivação também impede a comparação esperada. O salt e os rótulos fixos servem apenas o exercício; não constituem um protocolo autenticado pronto a implementar. Na assinatura ML-DSA, mudar mensagem, chave de verificação, bytes da assinatura ou contexto impede a verificação. Cada falha deve ser diagnosticada na operação que efetivamente a observou.

4. Rodar confiança e preservar verificação

O registo local começa por confiar apenas na chave current. A nova chave consegue produzir uma assinatura válida para si própria, mas isso não a torna autorizada pelo registo. Depois de aprovar e acrescentar next, testa a nova assinatura e mantém a antiga durante a sobreposição acordada. Retirar current do registo altera a política local; não torna matematicamente inválidas as assinaturas antigas se alguém ainda possuir a chave pública. Planeia como verificar artefactos históricos, tratar compromisso e identificar a versão de chave usada. Nunca distribuas a chave privada como solução para um verificador desatualizado. O teste mantém todas as chaves em memória e não publica segredos nem altera um trust store do sistema.

5. Aceitar a migração por evidência

Executa node run.mjs com o runtime registado e compara as 36 verificações com o manifesto. O ensaio não mede latência de produção, TLS, compatibilidade de parceiros, HSM ou resistência a canais laterais. Node 25.8.0 marca as APIs KEM como release candidate; o runtime fixado permite reproduzir o exercício e não recomenda produção. As páginas FIPS 203/204 ligam notas de possíveis correções; este trabalho não certifica conformidade da implementação nem valida um módulo FIPS. Para a migração real, ensaia mensagens, certificados, limites, recuperação, observabilidade e rollback com parceiros. Resumo: seleciona a função correta, autentica a associação de chaves e exige resultado do serviço antes de declarar a migração concluída.

/** Original primitive-level experiment; not a deployable key exchange protocol. */
import * as c from 'node:crypto';
import fs from 'node:fs';
import assert from 'node:assert/strict';
const checks=[];const check=(label,ok)=>{assert(ok,label);checks.push(label);};
function rejected(label,fn){let did=false;try{fn();}catch{did=true;}check(label,did);}
const receiver=c.generateKeyPairSync('ml-kem-768'),other=c.generateKeyPairSync('ml-kem-768');
const first=c.encapsulate(receiver.publicKey),received=c.decapsulate(receiver.privateKey,first.ciphertext);
const second=c.encapsulate(receiver.publicKey),receivedSecond=c.decapsulate(receiver.privateKey,second.ciphertext);
const salt=Buffer.from('synthetic-course-salt'),info=Buffer.from('dr.pt primitive-lab confirmation v1');
const derive=(secret,label=info)=>Buffer.from(c.hkdfSync('sha256',secret,salt,label,32));
const mac=(key,msg)=>c.createHmac('sha256',key).update(msg).digest();
const equal=(a,b)=>a.length===b.length&&c.timingSafeEqual(a,b);
const message=Buffer.from('synthetic transaction R-84; amount=100');
const senderKey=derive(first.sharedKey),receiverKey=derive(received),tag=mac(senderKey,message);
check('ML-KEM key type is explicit',receiver.publicKey.asymmetricKeyType==='ml-kem-768');
check('ML-KEM-768 ciphertext is 1088 bytes',first.ciphertext.length===1088);
check('encapsulation returns 32-byte secret',first.sharedKey.length===32);
check('decapsulation agrees for the intended key',equal(first.sharedKey,received));
check('independent encapsulation changes ciphertext',!equal(first.ciphertext,second.ciphertext));
check('independent encapsulation changes shared secret',!equal(first.sharedKey,second.sharedKey));
check('second valid ciphertext decapsulates correctly',equal(second.sharedKey,receivedSecond));
check('HKDF derives equal confirmation keys',equal(senderKey,receiverKey));
check('matching message and key confirm MAC',equal(tag,mac(receiverKey,message)));
check('changed message fails MAC confirmation',!equal(tag,mac(receiverKey,Buffer.from('synthetic transaction R-84; amount=900'))));
check('different HKDF label separates derived keys',!equal(senderKey,derive(received,Buffer.from('different purpose'))));
check('different label fails confirmation',!equal(tag,mac(derive(received,Buffer.from('different purpose')),message)));
const altered=Buffer.from(first.ciphertext);altered[0]^=1;const alteredSecret=c.decapsulate(receiver.privateKey,altered);
check('same-length modified ciphertext can return a secret',alteredSecret.length===32);
check('modified ciphertext does not reproduce original secret',!equal(first.sharedKey,alteredSecret));
check('modified ciphertext fails original MAC confirmation',!equal(tag,mac(derive(alteredSecret),message)));
const wrongSecret=c.decapsulate(other.privateKey,first.ciphertext);
check('wrong private key does not reproduce original secret',!equal(first.sharedKey,wrongSecret));
check('wrong private key fails original MAC confirmation',!equal(tag,mac(derive(wrongSecret),message)));
rejected('truncated ML-KEM ciphertext rejected',()=>c.decapsulate(receiver.privateKey,first.ciphertext.subarray(0,-1)));
const substituted=c.encapsulate(other.publicKey),substitutedSecret=c.decapsulate(other.privateKey,substituted.ciphertext);
check('substituted public key establishes a secret with that other key',equal(substituted.sharedKey,substitutedSecret));
check('substitution does not authenticate intended receiver',!equal(substituted.sharedKey,c.decapsulate(receiver.privateKey,substituted.ciphertext)));
const signing=c.generateKeyPairSync('ml-dsa-65'),replacement=c.generateKeyPairSync('ml-dsa-65'),context=Buffer.from('dr-course-release-v1');
const signature=c.sign(null,message,{key:signing.privateKey,context});
const verifies=(key=signing.publicKey,m=message,s=signature,ctx=context)=>c.verify(null,m,{key,context:ctx},s);
check('ML-DSA key type is explicit',signing.publicKey.asymmetricKeyType==='ml-dsa-65');
check('ML-DSA-65 signature is 3309 bytes',signature.length===3309);
check('original signature verifies with trusted key and context',verifies());
check('changed message fails signature',!verifies(signing.publicKey,Buffer.from('synthetic transaction R-84; amount=900')));
const damaged=Buffer.from(signature);damaged[0]^=1;
check('changed signature bytes fail verification',!verifies(signing.publicKey,message,damaged));
check('unrelated public key fails verification',!verifies(replacement.publicKey));
check('different signature context fails verification',!verifies(signing.publicKey,message,signature,Buffer.from('dr-course-other-v1')));
check('omitted signature context fails verification',!c.verify(null,message,signing.publicKey,signature));
const nextSignature=c.sign(null,message,{key:replacement.privateKey,context});
check('replacement key produces a valid signature for itself',c.verify(null,message,{key:replacement.publicKey,context},nextSignature));
check('replacement signature does not verify under original trusted key',!verifies(signing.publicKey,message,nextSignature));
const trust=new Map([['current',signing.publicKey]]);
check('replacement signer initially outside local trust registry',!trust.has('next'));
trust.set('next',replacement.publicKey);
check('registered replacement key verifies its signature',c.verify(null,message,{key:trust.get('next'),context},nextSignature));
check('old signer still verifies during overlap',c.verify(null,message,{key:trust.get('current'),context},signature));
trust.delete('current');
check('local removal changes trust registry but not cryptographic validity',!trust.has('current')&&verifies());
rejected('ML-KEM key is not accepted for signing',()=>c.sign(null,message,receiver.privateKey));
rejected('ML-DSA key is not accepted for encapsulation',()=>c.encapsulate(signing.publicKey));
console.log(JSON.stringify({passed:checks.length,failed:0,checks,node:process.version,openssl:process.versions.openssl,fipsMode:c.getFips(),scriptSHA256:c.createHash('sha256').update(fs.readFileSync(new URL(import.meta.url))).digest('hex'),actualMLKEM:true,actualMLDSA:true,actualHKDFHMAC:true,networkUsed:false,actualTLS:false,actualCiscoDevice:false,actualAAA:false,productionProtocolClaimed:false,fipsModuleValidationClaimed:false,observations:{kem:'ml-kem-768',sharedSecretBytes:32,ciphertextBytes:1088,signature:'ml-dsa-65',signatureBytes:3309,kdf:'HKDF-SHA256',confirmation:'HMAC-SHA256',modifiedCiphertext:'same length; different secret; original MAC fails',publicKeySubstitution:'other key agrees; intended receiver does not',trustRemoval:'local registry changed; original signature still mathematically valid',privateKeysWritten:false,secretsPrinted:false},scope:'Ephemeral in-memory primitive tests. Fixed instructional HKDF salt/labels and MAC comparison are not a complete authenticated protocol, key-confirmation standard, TLS/PQC migration, Cisco configuration or FIPS module validation.'},null,2));
NA PRÁTICA

Um ciphertext alterado mantém 1088 bytes e devolve um segredo; o HMAC esperado falha. O retorno da API não prova acordo nem identidade.

Armadilhas comuns

KEM como assinatura; ausência de erro como acordo; chave pública arbitrária como identidade; algoritmo normalizado como módulo certificado.

Tópicos relacionados: PQC e inventário · PKI e rotação · Interoperabilidade e rollback

Leva esta ideia contigo

Primitivas corretas são parte de uma solução; confiança, protocolo e operação precisam de evidência própria.

Criar conta

Referência: Migration to Post-Quantum Cryptography · 350-701 SCOR v2.0, effective 2026-08-27; core component of CCNP Security

CCNP® e Cisco® são marcas registadas da Cisco Systems, Inc. e/ou das suas afiliadas. A dr.pt é uma plataforma de preparação independente e não está afiliada, associada, patrocinada, autorizada nem aprovada por Cisco. Os conteúdos e as perguntas são originais, não são perguntas oficiais de exame, e concluir os nossos testes não atribui nem garante qualquer certificação. Os nomes são usados apenas para identificar o tema. Todas as outras marcas pertencem aos respetivos titulares.