Skip to content

Tudo para WordPress, desenvolvimento web — e não só

🔐 Esteganografia de imagem em PHP: esconder texto em pixels com LSB

🔐 Esteganografia de imagem em PHP: esconder texto em pixels com LSB

Clique, e o seu encoded.png parece idêntico ao container.jpg. Ninguém vai suspeitar que há uma mensagem escondida lá dentro. A esteganografia não torna os dados ilegíveis como a encriptação faz; ela esconde o próprio facto da transmissão. Para um programador PHP, esta é uma daquelas técnicas que vale a pena compreender ao nível dos bits e dos píxeis.

A esteganografia LSB em PHP com o módulo GD não é uma ferramenta de produção, mas sim um campo de treino. Um campo de treino que o ensina a trabalhar com representação binária, máscaras de bits e formatos sem perdas melhor do que uma dúzia de tarefas CRUD. Vai passar de «como é que uma imagem é estruturada por dentro» à linha php decrypt.php que extrai hello dos píxeis, e vai perceber porque é que o JPEG não é adequado para isto.

💡 Visão geral rápida:

  • Explicamos a teoria: como o bit menos significativo do canal azul esconde uma mensagem e porque é que o olho não o vê
  • Escrevemos três ficheiros PHP: functions.php (utilitários), encrypt.php (codificação), decrypt.php (extração)
  • Executamo-lo num contentor JPEG real e obtemos um resultado PNG visualmente indistinguível
  • Analisamos as limitações: compressão, detetabilidade, capacidade e a combinação com encriptação AES
  • Analisamos bibliotecas PHP prontas a usar e decidimos quando o GD é suficiente e quando é altura de usar Python

Como funciona a esteganografia LSB

LSB (Least Significant Bit) é um método em que a mensagem secreta é escrita nos bits menos significativos dos píxeis. O nome é literal: pegamos no bit menos significativo de um canal de cor e substituímo-lo por um bit da mensagem.

Uma imagem digital é uma grelha de píxeis. Cada píxel é codificado com três bytes: vermelho (R), verde (G) e azul (B). A combinação de três valores de 0 a 255 determina a cor final. Alterar o bit menos significativo muda o valor do canal no máximo em 1; por exemplo, RGB(100, 50, 200) passa a RGB(100, 50, 201). O olho humano não consegue distinguir a diferença.

Vejamos um píxel laranja:

  • Vermelho: 11001001 (201)
  • Verde: 11111000 (248)
  • Azul: 00000011 (3)

O bit menos significativo do canal azul é um. Substitua-o por um bit da mensagem secreta e a cor permanecerá visualmente a mesma. Ao processar os píxeis sequencialmente, escrevemos a mensagem completa bit a bit.

Suponha que precisamos de esconder a sequência 1101. Pegamos em 4 píxeis e alteramos o bit menos significativo do canal azul:

Píxel

R

G

B original

LSB substituído

Novo B

1

11001100

10010001

00101011

1

00101011

2

00011000

11110000

11111110

1

11111111

3

11100010

00100101

01010101

0

01010100

4

11111101

00001010

01000011

1

01000011

A descodificação é o processo inverso: recolhemos os bits menos significativos do canal azul dos mesmos quatro píxeis e obtemos 1101. Nada de magia, apenas operações bit a bit.

Na prática, não está limitado apenas ao canal azul: pode usar os três (R, G, B), triplicando o volume de dados escondidos. Neste tutorial, trabalhamos apenas com o azul por simplicidade, para ser mais fácil ver o que acontece com cada bit.

Implementação passo a passo em PHP

Vai precisar do PHP com o módulo GD, que está pré-instalado em praticamente todos os fornecedores de alojamento. O código está escrito para PHP 8.0+ e testado até à versão 8.4. Crie três ficheiros numa pasta.

Passo 1: Funções auxiliares, functions.php

Dois utilitários: converter uma string para representação binária e vice-versa.

1<?php
2
3function toBin(string $str): string
4{
5 $result = '';
6 $len = strlen($str);
7
8 while ($len--) {
9 $result = str_pad(
10 decbin(ord($str[$len])),
11 8,
12 '0',
13 STR_PAD_LEFT
14 ) . $result;
15 }
16
17 return $result;
18}
19
20function toString(string $binary): string
21{
22 $result = '';
23 foreach (str_split($binary, 8) as $byte) {
24 $result .= chr((int) base_convert($byte, 2, 10));
25 }
26
27 return $result;
28}

toBin() converte cada caractere da string para uma representação de 8 bits através de decbin(ord(...)) e preenche com zeros à esquerda para formar um byte completo. toString() faz o oposto: fatia a string binária em pedaços de 8 bits, converte cada pedaço para um número decimal e obtém o caractere através de chr().

O processamento bloco a bloco em toString() não é um capricho. A abordagem popular em tutoriais que usa pack('H*', base_convert($binary, 2, 16)) falha em mensagens com mais de 31 bits: a função base_convert opera com inteiros e, ao exceder PHP_INT_MAX, devolve lixo. A divisão por bytes resolve o problema para mensagens de qualquer comprimento.

Passo 2: Codificação, encrypt.php

Lê um contentor JPEG, incorpora a mensagem nos bits menos significativos do canal azul e guarda o resultado como PNG. Porquê não JPEG? Um formato sem perdas é obrigatório: a compressão JPEG destrói os bits escondidos e a mensagem transforma-se em ruído.

1<?php
2
3require_once 'functions.php';
4
5$message = 'hello';
6$binary = toBin($message);
7$msgLen = strlen($binary);
8
9$src = 'container.jpg';
10$img = imagecreatefromjpeg($src);
11
12for ($x = 0; $x < $msgLen; $x++) {
13 $y = $x;
14 $rgb = imagecolorat($img, $x, $y);
15
16 $r = ($rgb >> 16) & 0xFF;
17 $g = ($rgb >> 8) & 0xFF;
18 $b = $rgb & 0xFF;
19
20 $blueBits = str_pad(decbin($b), 8, '0', STR_PAD_LEFT);
21 $blueBits[7] = $binary[$x];
22 $newB = bindec($blueBits);
23
24 $newColor = imagecolorallocate($img, $r, $g, $newB);
25 imagesetpixel($img, $x, $y, $newColor);
26}
27
28imagepng($img, 'encoded.png');
29
30echo "Закодировано {$msgLen} бит — результат в encoded.png\n";

imagecolorat() obtém a cor do píxel nas coordenadas (x, y). Os deslocamentos de bits >>16 e >>8 extraem R, G, B. O canal azul é expandido para uma string de 8 bits, o bit menos significativo (índice 7) é substituído pelo próximo bit da mensagem e a nova cor é escrita através de imagesetpixel().

A mensagem é codificada diagonalmente (y = x), o que facilita o acompanhamento da lógica num exemplo pequeno. Num projeto real, itere por todos os píxeis linha a linha e adicione um cabeçalho de 32 bits com o comprimento da mensagem: sem ele, o descodificador não sabe onde parar.

Passo 3: Descodificação, decrypt.php

Extrai a mensagem escondida: percorre os mesmos píxeis e recolhe os bits menos significativos do canal azul.

1<?php
2
3require_once 'functions.php';
4
5$src = 'encoded.png';
6$img = imagecreatefrompng($src);
7
8$bits = '';
9
10for ($x = 0; $x < 40; $x++) {
11 $y = $x;
12 $rgb = imagecolorat($img, $x, $y);
13
14 $b = $rgb & 0xFF;
15 $blueBits = str_pad(decbin($b), 8, '0', STR_PAD_LEFT);
16 $bits .= $blueBits[7];
17}
18
19echo toString($bits) . "\n";

Aqui 40 é um número de bits fixo, exatamente 5 bytes para a palavra hello. Numa versão de produção, substitua este número pelo comprimento do cabeçalho da mensagem que escreveu nos primeiros 32 bits durante a codificação.

Passo 4: Execução e verificação

Coloque container.jpg na pasta com os scripts. O procedimento:

  • php encrypt.php escreve na consola: "Encoded 40 bits, result in encoded.png".
  • php decrypt.php escreve na consola: hello.
  • Abra container.jpg e encoded.png lado a lado; não há diferença. São visualmente indistinguíveis.

Pode comparar byte a byte usando o ImageMagick: magick compare container.jpg encoded.png diff.png irá destacar diferenças nos bits menos significativos, praticamente invisíveis a olho nu. Mas a forma mais fiável de verificar é executar decrypt.php e ler a mensagem.

Limitações do método

A esteganografia LSB é uma ferramenta de aprendizagem, não uma proteção criptográfica. Eis o que precisa de ter em mente antes de usar esta abordagem num projeto real:

  • A compressão destrói os dados. O JPEG recalcula blocos de 8×8 píxeis com quantização e os bits menos significativos não sobrevivem. É por isso que o encrypt.php guarda o resultado estritamente como PNG (sem perdas). Uma passagem pela compressão JPEG e os bits escondidos transformam-se em ruído. Formatos alternativos: BMP, TIFF sem compressão.

  • Detetabilidade. O preenchimento LSB deixa artefactos estatísticos: o histograma do canal azul muda de uma forma previsível. A análise RS e o teste χ² detetam uma mensagem escondida em segundos. Sem encriptação, o LSB é ocultação, não proteção: a presença de uma mensagem pode ser detetada mesmo que o conteúdo não possa ser lido.

  • Volume de dados. Cada píxel cede um bit no canal azul. Uma imagem de 1000×1000 píxeis pode conter até 125 KB de texto escondido; este é o limite matemático decorrente da definição do método LSB. Na prática, após a compressão PNG, o volume útil é menor, além de que alguns bits vão para o cabeçalho com o comprimento.

  • Não é criptografia. A esteganografia não substitui a encriptação; complementa-a. O pipeline correto: encripte a mensagem (AES-256) e depois esconda o texto cifrado na imagem. Assim, mesmo que o canal escondido seja detetado, o conteúdo não será revelado.

  • O PHP GD é uma ferramenta básica. O GD fornece acesso de baixo nível aos píxeis e, para aprendizagem, isto é mais do que suficiente. Mas o ecossistema Python para esteganografia é visivelmente mais rico: a biblioteca stegano (LSB + encriptação), Stegano (um conjunto de métodos) e o steghide (C++, mas existem bindings Python). Em PHP puro, existem várias bibliotecas: kzykhys/Steganography (uma implementação LSB mínima para PNG), seba/steganography (uma biblioteca moderna com manipulação LSB) e pateon/steganography (PHP 8.5+ com suporte a GD e Imagick). Mas não existem soluções de nível profissional ao nível do steghide em PHP, à data de 2026.

⁉️🤔 Perguntas frequentes

É possível esconder texto diretamente em JPEG?

Não. A compressão JPEG divide a imagem em blocos de 8×8 e aplica quantização; os bits menos significativos não sobrevivem. Para esconder dados em JPEG, usa-se uma abordagem diferente: os dados são escondidos nos coeficientes da transformada discreta de cosseno (DCT), não nos píxeis. Algoritmos: F5, JSteg, OutGuess. Praticamente não existem bibliotecas PHP prontas para isto; é mais fácil usar Python.

Qual é a diferença entre LSB e esteganografia em metadados?

O LSB altera os dados dos píxeis; a mensagem fica escondida «dentro» da imagem. Os metadados (EXIF, XMP, blocos tEXt do PNG) estão localizados nos cabeçalhos do ficheiro e podem ser lidos por qualquer visualizador de propriedades. Os metadados são mais fáceis de extrair, mas sobrevivem à compressão JPEG, ao contrário do LSB.

Como posso verificar se o encoded.png contém realmente uma mensagem escondida?

Visualmente, não pode. Compare os ficheiros byte a byte: magick compare container.jpg encoded.png diff.png (ImageMagick) mostrará diferenças nos bits menos significativos. São praticamente invisíveis a olho nu, mas o utilitário destacará os píxeis alterados. A forma mais fiável é executar php decrypt.php e ler a mensagem.

Quão seguro é isto, no geral?

Sem encriptação, não é seguro. O LSB sem encriptação AES prévia é ocultação, não proteção. O facto da transmissão de dados escondidos é detetado por testes estatísticos em segundos. A combinação «encriptação AES + ocultação LSB» fornece um nível aceitável para projetos pessoais, mas não para dados sensíveis.

Existem bibliotecas PHP prontas para esteganografia?

Sim, mas são todas educativas ou leves. A kzykhys/Steganography é uma implementação LSB mínima que funciona apenas com PNG. A seba/steganography é mais recente, com uma API limpa para codificar/descodificar texto em imagens. A pateon/steganography é para PHP 8.5+ e suporta tanto GD como Imagick. Para tarefas sérias, olhe para Python: stegano (LSB + encriptação) e Stegano (um conjunto de métodos, incluindo um gerador de estego-imagens).

Esteganografia em PHP: uma base de aprendizagem ou uma ferramenta pronta a usar?

A esteganografia LSB em PHP com GD é um excelente campo de treino para compreender como os dados são escondidos ao nível do píxel. Escreveu um ciclo de codificação/descodificação funcional, percebeu as operações bit a bit e viu na prática porque é que uma alteração «invisível» deixa rastos nas estatísticas da imagem.

Para uma demonstração de aprendizagem, um projeto pessoal ou um desafio CTF, este conjunto de ferramentas é mais do que suficiente. Para proteção de dados real, adicione encriptação antes da ocultação e explore as bibliotecas Python, que estão muito acima das equivalentes PHP em capacidades e resistência à deteção.

Se quiser aprofundar, implemente a codificação nos três canais de cor em simultâneo e adicione um cabeçalho de 32 bits com o comprimento da mensagem. Obterá uma mini-ferramenta 3 vezes mais eficiente do que a versão básica e que já se qualifica como um projeto de curso. Publique o código-fonte no GitHub para que outros possam aprender com o seu exemplo.