Como construir um verificador de integridade de arquivos baseado em navegador com JavaScript


Quando os usuários baixam contratos, faturas, relatórios ou documentos assinados, muitas vezes precisam de uma forma confiável de confirmar que o arquivo não foi alterado. Um arquivo pode parecer idêntico quando aberto, mas um único byte modificado significa que não é mais o mesmo arquivo.

Um hash criptográfico oferece aos desenvolvedores uma maneira prática de detectar essas alterações. Ele converte um arquivo em uma impressão digital digital de comprimento fixo. Se o arquivo for alterado, sua impressão digital também será alterada.

Neste tutorial, construiremos um verificador de integridade de arquivos baseado em navegador usando a API Web Crypto. A ferramenta calculará um hash SHA-256 localmente, comparará-o com um hash confiável e permitirá ao usuário copiar o resultado. Nenhum servidor ou upload de arquivo é necessário.

A ferramenta finalizada irá:

  • Permitir que o usuário selecione um arquivo.
  • Leia o arquivo diretamente no navegador.
  • Gere seu hash SHA-256.
  • Compare o resultado com um hash esperado.
  • Mostra se o arquivo corresponde.
  • Deixe o usuário copiar o hash calculado.

Essa abordagem é útil para verificar downloads de software, relatórios exportados, cópias de documentos assinados e outros arquivos que devem permanecer inalterados.

Uma comparação de hash não prova quem criou ou assinou um arquivo. Ele apenas confirma se o arquivo atual corresponde à versão a partir da qual o hash confiável foi gerado.

Crie a interface HTML

Comece com uma entrada de arquivo, um campo para o hash confiável, um botão de verificação e uma área de saída.

<main class="checker">
  <h1>File Integrity Checker</h1>

  <label for="file">Choose a file</label>
  <input id="file" type="file">

  <label for="expected-hash">Expected SHA-256 hash</label>
  <input
    id="expected-hash"
    type="text"
    placeholder="Paste the trusted hash"
    autocomplete="off"
  >

  <button id="verify" type="button">Verify file</button>

  <section id="result" aria-live="polite" hidden>
    <p id="status"></p>
    <code id="calculated-hash"></code>
    <button id="copy" type="button">Copy hash</button>
  </section>
</main>

O arquivo nunca precisa sair do navegador. O JavaScript lê o dispositivo do usuário e passa seus bytes para a API Web Crypto.

Se você precisar se atualizar sobre os controles de arquivos do navegador, o guia do SitePoint para Tipos de entrada HTML explica como as entradas de arquivo funcionam e como o accept atributo pode restringir os formatos mostrados no seletor.

Evite confiar em accept como um controle de segurança. Melhora a experiência de seleção, mas às vezes os usuários podem escolher arquivos por meio de outros métodos. Valide o tipo e o tamanho do arquivo em JavaScript quando essas restrições forem importantes.

Gere um hash SHA-256

A API Web Crypto está disponível através window.crypto.subtle. Isso é digest() método aceita um algoritmo e um ArrayBufferentão retorna uma promessa contendo o resumo calculado. O Documentação MDN para SubtleCrypto.digest() fornece detalhes sobre algoritmos de hash suportados, compatibilidade de navegador, requisitos de HTTPS, valores de retorno e limitações de memória.

Adicione o seguinte JavaScript:

const fileInput = document.querySelector("#file");
const expectedHashInput = document.querySelector("#expected-hash");
const verifyButton = document.querySelector("#verify");
const resultSection = document.querySelector("#result");
const statusElement = document.querySelector("#status");
const hashElement = document.querySelector("#calculated-hash");
const copyButton = document.querySelector("#copy");

async function calculateSHA256(file) {
  const fileBuffer = await file.arrayBuffer();

  const hashBuffer = await crypto.subtle.digest(
    "SHA-256",
    fileBuffer
  );

  return Array.from(new Uint8Array(hashBuffer))
    .map(byte => byte.toString(16).padStart(2, "0"))
    .join("");
}

O arrayBuffer() O método lê o arquivo selecionado como dados binários brutos. O navegador então calcula o resumo SHA-256.

digest() retorna outro ArrayBufferentão o código o converte em uma matriz de bytes. Cada byte se torna um valor hexadecimal de dois caracteres. A união desses valores produz a familiar string SHA-256 de 64 caracteres.

Compare os hashes

Os usuários podem colar hashes contendo letras maiúsculas ou espaços em branco extras. A normalização de ambos os valores evita que diferenças de formatação inofensivas causem uma falha na comparação.

function normalizeHash(value) {
  return value.trim().toLowerCase().replace(/\s+/g, "");
}

verifyButton.addEventListener("click", async () => {
  const file = fileInput.files(0);
  const expectedHash = normalizeHash(expectedHashInput.value);

  resultSection.hidden = false;
  hashElement.textContent = "";

  if (!file) {
    statusElement.textContent = "Choose a file first.";
    return;
  }

  if (!/^(a-f0-9){64}$/.test(expectedHash)) {
    statusElement.textContent =
      "Enter a valid 64-character SHA-256 hash.";
    return;
  }

  try {
    verifyButton.disabled = true;
    statusElement.textContent = "Calculating hash…";

    const calculatedHash = await calculateSHA256(file);
    hashElement.textContent = calculatedHash;

    if (calculatedHash === expectedHash) {
      statusElement.textContent =
        "Match: the file has the expected fingerprint.";
    } else {
      statusElement.textContent =
        "No match: this file differs from the trusted version.";
    }
  } catch (error) {
    console.error(error);
    statusElement.textContent =
      "The browser could not process this file.";
  } finally {
    verifyButton.disabled = false;
  }
});

A expressão regular requer exatamente 64 caracteres hexadecimais, que é a representação esperada de um resumo SHA-256.

O botão fica temporariamente desativado durante o processamento para evitar solicitações duplicadas. UM try...catch...finally block garante que erros inesperados de leitura ou de navegador produzam uma mensagem útil e que o botão fique disponível novamente.

O aria-live atributo no HTML faz com que as tecnologias assistivas anunciem alterações de status sem mover o foco do teclado.

Adicionar um botão de cópia

Hashes longos são inconvenientes para selecionar manualmente. A API Clipboard permite que os usuários copiem o valor calculado com uma ação.

copyButton.addEventListener("click", async () => {
  const hash = hashElement.textContent;

  if (!hash) {
    statusElement.textContent =
      "Calculate a hash before copying it.";
    return;
  }

  try {
    await navigator.clipboard.writeText(hash);
    statusElement.textContent =
      "The calculated hash was copied.";
  } catch (error) {
    console.error(error);
    statusElement.textContent =
      "Copy failed. Select and copy the hash manually.";
  }
});

O acesso à área de transferência normalmente requer uma página HTTPS segura e uma ação iniciada pelo usuário, como um clique. Tutorial do SitePoint sobre o API da área de transferência explica suas permissões, suporte ao navegador e padrões de implementação comuns.

Sempre mantenha um substituto manual. As permissões da área de transferência podem ser bloqueadas pelas configurações do navegador, por um quadro incorporado ou pela política de segurança de uma organização.

Manipulação de arquivos grandes

Chamando file.arrayBuffer() carrega o arquivo inteiro na memória. Isso é aceitável para PDFs comuns, documentos e downloads pequenos, mas pode criar problemas de desempenho com arquivos de vários gigabytes.

Defina um limite razoável para seu caso de uso:

const MAX_FILE_SIZE = 100 * 1024 * 1024;

if (file.size > MAX_FILE_SIZE) {
  statusElement.textContent =
    "Choose a file smaller than 100 MB.";
  return;
}

A API Web Crypto não fornece entrada de streaming para digest() em todos os navegadores. Os aplicativos que precisam processar arquivos muito grandes podem precisar de uma biblioteca de hashing incremental confiável, de um Web Worker ou de processamento no lado do servidor.

Um Web Worker pode evitar que o trabalho pesado faça com que a página pare de responder, embora os dados do arquivo ainda consumam memória.

Limitações de segurança

Um hash correspondente só é significativo quando o hash esperado vem de uma fonte confiável. Se um invasor puder substituir o arquivo e o hash exibido, a comparação ainda será aprovada.

Publique hashes confiáveis ​​por meio de um canal protegido separado, uma API autenticada ou um manifesto assinado. Não armazene o hash confiável ao lado de um arquivo para download se ambos puderem ser modificados através do mesmo sistema comprometido.

A verificação de hash também difere da validação de assinatura digital. Uma assinatura digital pode conectar um documento a um certificado e identificar modificações não autorizadas cobertas pela assinatura. Uma comparação básica do SHA-256 responde apenas a uma pergunta mais restrita: este arquivo corresponde exatamente ao original confiável?

Considerações finais

A API Web Crypto torna a verificação de integridade do lado do cliente surpreendentemente pequena. Com uma entrada de arquivo, crypto.subtle.digest()e um valor SHA-256 confiável, os desenvolvedores podem detectar alterações nos arquivos sem fazer upload de documentos confidenciais.

Para uso em produção, adicione limites de tamanho de arquivo, mensagens de status acessíveis, estados de erro claros e um método confiável para distribuição de hashes esperados. Esses detalhes transformam um exemplo de código curto em um recurso de verificação confiável.



Source link