paint-brush
Esteganografia: como ocultar texto em imagens usando JavaScriptpor@andriiromasiun
1,031 leituras
1,031 leituras

Esteganografia: como ocultar texto em imagens usando JavaScript

por Andrii Romasiun9m2024/06/26
Read on Terminal Reader

Muito longo; Para ler

A esteganografia é um método de ocultar mensagens secretas em outro arquivo não secreto. Os usuários podem fazer upload de uma imagem para ler a mensagem oculta nela ou para codificar eles próprios uma mensagem em uma imagem. Este artigo descreve como implementar esse mecanismo de codificação usando JavaScript.
featured image - Esteganografia: como ocultar texto em imagens usando JavaScript
Andrii Romasiun HackerNoon profile picture
0-item

O que é esteganografia?

Imagine que você deseja enviar uma mensagem secreta para um amigo, mas o canal que deseja utilizar está comprometido e monitorado. Você poderia usar alguma criptografia, mas isso levantaria suspeitas nas pessoas que monitoram suas conversas, então você teria que usar outra coisa.

Hoje em dia, a esteganografia é um método de esconder mensagens secretas em outro arquivo não secreto (como a foto de um gato), para que, se você enviasse esse arquivo, ele não fosse detectado. A esteganografia não se limita a ocultar texto em imagens e geralmente significa "ocultar informações secretas em outra mensagem não secreta ou objeto físico": você pode ocultar algumas mensagens em áudio, vídeo ou outros textos usando, por exemplo, transposição colunar.


A esteganografia também pode ser extremamente útil em muitos outros casos, por exemplo, pode ser uma boa alternativa à marca d’água em documentos confidenciais para protegê-los contra vazamentos.


Há muitas maneiras de ocultar informações em imagens, desde simplesmente anexar o texto no final do arquivo até ocultá-lo nos metadados. Neste artigo, quero abordar um método mais avançado de esteganografia, indo até o nível binário e ocultando mensagens dentro dos limites da própria imagem.

Construindo um mecanismo esteganográfico

A IU

Para meu exemplo de esteganografia, decidi usar JavaScript porque é uma linguagem de programação poderosa que pode ser executada em um navegador.


Eu criei uma interface simples que permite aos usuários fazer upload de uma imagem para ler a mensagem oculta nela ou codificar eles próprios uma mensagem em uma imagem.


 <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Steganography</title> <style> body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; } textarea { width: 300px; height: 100px; } button { margin: 10px; } #outputImage { margin-top: 20px; max-width: 100%; } </style> </head> <body> <h1>Steganography Example</h1> <input type="file" id="upload" accept="image/*"><br> <canvas id="canvas" style="display:none;"></canvas><br> <textarea id="text" placeholder="Enter text to encode"></textarea><br> <button id="encode">Encode Text</button> <button id="decode">Decode Text</button> <p id="decodedText"></p> <img id="outputImage" alt="Output Image"> <script src="./script.js"></script> </body> </html>


Para usá-lo, os usuários podem simplesmente selecionar uma imagem que desejam manipular e tentar decodificar algum texto dela, ou codificá-lo e baixar a imagem posteriormente.



Processando Imagens

Para trabalhar com imagens em JavaScript, podemos usar a API Canvas . Ele oferece muitas funções diferentes para manipular e desenhar imagens, animações ou até mesmo gráficos e vídeos de jogos.


A API Canvas é usada principalmente para gráficos 2D. Se quiser trabalhar com gráficos 3D acelerados por hardware, você pode usar a API WebGL (que, aliás, também usa o elemento <canvas>).


 const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); const image = new Image();


Para ler o arquivo de imagem do sistema de arquivos e adicioná-lo ao contexto do canvas, podemos usar a API FileReader . Permite-nos ler facilmente o conteúdo de qualquer arquivo armazenado no computador do usuário sem a necessidade de uma biblioteca personalizada.

 function handleFileUpload(event) { const reader = new FileReader(); reader.onload = function (e) { image.src = e.target.result; image.onload = function () { canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); }; }; reader.readAsDataURL(event.target.files[0]); }


Ele lê um arquivo e desenha a imagem desse arquivo em nosso contexto de tela 2D previamente definido, após o qual podemos codificar algum texto nessa imagem ou tentar ler o texto da imagem.

Ocultando texto em imagens

As imagens são compostas de pixels e cada pixel contém informações sobre suas cores. Por exemplo, se uma imagem for codificada usando o modelo RGBA, cada pixel conteria 4 bytes de informações sobre quanto vermelho, verde, azul e alfa (opacidade) ele representa.


Para codificar algum texto em uma imagem, poderíamos usar um desses canais (por exemplo, o canal alfa). Como esta informação é representada no sistema binário (como 01001100), poderíamos mudar o último bit para o que precisarmos. É chamado de bit menos significativo (LSB) e alterá-lo causa alterações mínimas na própria imagem, tornando-a indistinguível de um ser humano.


Agora, imagine que temos um texto como “Hello” e queremos codificá-lo em uma imagem. O algoritmo para fazer isso seria


  1. Converta o texto "Olá" em binário.


  2. Itere pelos bytes de dados da imagem e substitua o LSB desses bytes por um bit do texto binário (cada pixel contém 4 bytes de dados para cada uma das cores, no meu exemplo, quero alterar o canal de opacidade da imagem , então eu iteraria a cada 4 bytes).


  3. Adicione um byte nulo no final da mensagem para que, ao decodificar, saibamos quando parar.


  4. Aplique bytes de imagem modificados à própria imagem.


Primeiro, precisamos pegar o texto que queremos codificar do usuário e realizar algumas validações básicas nele.

 const text = document.getElementById("text").value; if (!text) { alert("Please enter some text to encode."); return; }


Então, precisamos converter o texto em binário e criar uma tela da imagem na qual codificaremos esse texto.

 const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imgData.data; let binaryText = ""; for (let i = 0; i < text.length; i++) { let binaryChar = text.charCodeAt(i).toString(2).padStart(8, "0"); binaryText += binaryChar; }


Para fazer isso, podemos simplesmente iterar cada um dos caracteres e obter um índice Unicode usando a função charCodeAt . Este Unicode é então convertido em binário e preenchido para que tenha o mesmo comprimento de qualquer outro caractere.


Por exemplo, a letra “H” é representada como 72 em Unicode; em seguida, convertemos esse número em binário (1001000) e adicionamos 0s no início (01001000) para garantir que todas as letras tenham o mesmo comprimento (8 bits).


Em seguida, precisamos adicionar um byte nulo no final da mensagem para garantir que, ao descriptografá-la, possamos distinguir entre o texto real e os dados de pixel aleatórios da imagem.

 binaryText += "00000000";


Então, precisamos fazer algumas validações básicas para garantir que a imagem tenha pixels suficientes para codificar nossa mensagem e que ela não transborde.

 if (binaryText.length > data.length / 4) { alert("Text is too long to encode in this image."); return; }


E aí vem a parte mais interessante, a codificação da mensagem. A matriz de dados que definimos anteriormente contém informações de pixel na forma de valores RGBA para cada pixel da imagem. Portanto, se a imagem for codificada em RGBA, cada pixel nela contido seria representado por 4 valores da matriz de dados; cada valor representando quanto vermelho, verde e azul aquele pixel possui.

 for (let i = 0; i < binaryText.length; i++) { data[i * 4] = (data[i * 4] & 0b11111110) | parseInt(binaryText[i]); } ctx.putImageData(imgData, 0, 0); const outputImage = document.getElementById("outputImage"); outputImage.src = canvas.toDataURL();


No código acima, iteramos nosso texto codificado em binário. data[i * 4] encontra um byte que precisamos modificar, e como queremos modificar apenas os bytes de um determinado canal, multiplicamos a variável i por 4 para acessá-lo.


A operação data[i * 4] & 0b11111110 define o bit menos significativo como 0 . Por exemplo, se data[i * 4] for 10101101 em binário, a operação 10101101 & 11111110 resultará em 10101100 . Isso garante que o LSB seja definido como 0 antes de fazermos qualquer manipulação adicional com ele.


O parseInt(binaryText[i]) é um bit atual da string codificada em binário; é 1 ou 0 . Podemos então definir este bit para o LSB usando uma operação OR ( | ) bit a bit. Por exemplo, se a parte esquerda do OR bit a bit for 10101100 e o binaryText[i] for 1 , então 10101100 | 00000001 resultaria em 10101101 . Se o bit atual fosse 0 , então o OR resultaria em 10101100 . É por isso que tivemos que excluir o LSB em primeiro lugar.


Depois que a mensagem for codificada, podemos colocá-la na tela atual e renderizá-la em HTML usando o método canvas.toDataURL .



Decodificando mensagens ocultas de imagens

O processo de decodificação de uma imagem é, na verdade, muito mais simples do que a codificação. Como já sabemos que codificamos apenas o canal alfa, podemos simplesmente iterar cada quarto byte, ler o último bit, concatená-lo em nossa string final e converter esses dados de binário em uma string Unicode.


Primeiro, precisamos inicializar as variáveis. Como imgData já está preenchido com as informações da imagem (chamamos ctx.drawImage toda vez que lemos um arquivo do sistema de arquivos), podemos simplesmente extraí-lo para a variável data.


 const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imgData.data; let binaryText = ""; let decodedText = "";


Então precisamos iterar cada quarto byte da imagem, ler o último bit e concatená-lo com a variável binaryText .

 for (let i = 0; i < data.length; i += 4) { binaryText += (data[i] & 1).toString(); }


data[i] é o byte codificado e, para extrair o LSB, podemos usar o operador AND ( & ) bit a bit. Ele recebe dois valores e executa uma operação AND em cada par de bits correspondentes. Ao comparar data[i] com 1 , basicamente isolamos o bit menos significativo das informações do pixel e, se o LSB for 1 , o resultado de tal operação será 1 . Se o LSB for 0 , o resultado também seria 0 .


Depois de ler todos os LSBs e armazená-los na variável binaryText , precisamos convertê-lo de binário para texto simples. Como sabemos que cada caractere consiste em 8 bits (lembra como usamos padStart(8, "0") para fazer com que cada caractere tenha o mesmo comprimento?), podemos iterar em cada 8º caractere do binaryText .


Então podemos usar a operação .slice() para extrair o byte atual do binaryText com base em nossa iteração. A string binária pode ser convertida em um número usando a função parseInt(byte, 2) . Então podemos verificar se o resultado é 0 (um byte nulo) - paramos a conversão e consultamos o resultado. Caso contrário, podemos descobrir qual caractere corresponde ao número Unicode e adicioná-lo à nossa string de resultado.


 for (let i = 0; i < binaryText.length; i += 8) { let byte = binaryText.slice(i, i + 8); if (byte.length < 8) break; // Stop if the byte is incomplete let charCode = parseInt(byte, 2); if (charCode === 0) break; // Stop if we hit a null character decodedText += String.fromCharCode(charCode); }


O texto decodificado pode então ser exibido com segurança para um usuário:

 document.getElementById("decodedText").textContent = decodedText; 



Deixei o código completo usado neste artigo no meu repositório GitHub ; sinta-se à vontade para brincar com isso. Há muitas coisas que poderiam ser melhoradas :)

Pensamentos finais

A esteganografia é uma técnica muito poderosa e pode ser aplicada a vários casos de uso diferentes, desde verificação de documentos, prevenção de vazamentos, verificação de IA de imagem, gerenciamento de DRM de arquivos de música e muito mais. Essa técnica pode até ser aplicada em vídeos, jogos ou até mesmo texto bruto, então acho que tem um potencial enorme.

Na era dos NFTs e blockchains, é ainda mais interessante ver como encontrará seus casos de uso e como essa técnica irá evoluir.