Esteganografia com Arquivos Bitmap em C++

Padrão


Olá, pessoal! Este ano na faculdade tivemos um trabalho sobre esteganografia. Pra quem não conhece, a esteganografia é uma técnica no qual você esconde uma mensagem, de forma que, se ela for interceptada, o intruso nem saberá que ela existe. Combinada com técnicas de encriptação, ela se torna uma ótima forma de se transmitir conteúdo sigiloso.

Este trabalho consistia em ler um arquivo texto, escondê-lo em um arquivo Bitmap (o arquivo Bitmap é um dos mais utilizados na esteganografia pelo fato de não possuir compactação) e depois possibilitar-se extrair a mensagem do arquivo novamente.

Bom, como podem ver, o código está meio “poluído” (confesso que no começo tentei fazer uma coisa bem organizada, mas de acordo que o código foi dando muito problema, acabou “desandando” :D). Então, disponibilizo aqui a classe em C++ que implementa esteganografia em arquivos Bitmap.

// Arquivo esteganografia.h

#ifndef ESTEGANOGRAFIA_H
#define ESTEGANOGRAFIA_H

#include <string>
#include <iostream>
#include <cstdlib>
#include <cstdio>
using namespace std;

struct RGB
{
    char verde;
    char vermelho;
    char azul;
};

struct CabecalhoBitmap
{
    char identificador[2]; // Os símbolos que definem o arquivo como bitmap (0x42 e 0x4D)
    int tamanho; // O tamanho do bitmap em bytes
    short int areaReservada1; // Reservado
    short int areaReservada2; // Reservado
    int enderecoInicial; // Offset
};

struct CabecalhoMapaBits
{
    int tamanho; // O tamanho deste cabeçalho - 40 bytes
    int largura; // Largura do bitmap em pixels
    int altura; // Altura do bitmap em pixels
    short int planosCor; // A quantidade de planos de cor utilizados. Deve ser setado para 1
    short int bpp; // Bits por pixel
    int metodoCompressao; // Método de compressão
    int tamanhoImagem; // O tamanho da imagem. Este é o tamanho bruto dos dados do bitmap. Não deve ser confundido com o tamanho da imagem
    int resolucaoHorizontal; // Resolução horizontal da imagem
    int resolucaoVertical; // Resolução vertical da imagem
    int coresNaPaleta; // O número de cores na paleta de cores
    int coresImportantes; // O número de cores importantes usadas, ou 0 se todas são importantes. Geralmente ignorado.
};

class Esteganografia
{
private:
    FILE *arquivo;
    FILE *mensagem;
    CabecalhoBitmap cabecalho;
    CabecalhoMapaBits cabDados;
    string nomeArquivo;
    char bytesDeAjuste; // 0 a 3 - o número de bytes a serem adicionados para criar uma "linha divisível por 4"

    void esconderMensagem(string mensagem);
    string extrairMensagem(void);
    void salvarBitmap(void);
public:
    RGB *pixels;
    int qtdePixels;
    Esteganografia();
    ~Esteganografia();
    void inserirMensagem(string mensagem, string arquivo);
    string extrairMensagem(string arquivo);
    void imprimirInfoArquivo(void);
};

#endif // ESTEGANOGRAFIA_H
// Arquivo esteganografia.cpp

#include "esteganografia.h"

Esteganografia::Esteganografia()
{

}

Esteganografia::~Esteganografia()
{

}

void Esteganografia::inserirMensagem(string arqmensagem, string arquivo)
{
    this->arquivo = fopen(arquivo.c_str(), "rb");
    if(!this->arquivo)
    {
        cout << endl << " Arquivo nao encontrado...";
        system("pause");
        return;
    }

    string mensagemArquivo;
    char caract;
    this->mensagem = fopen(arqmensagem.c_str(), "r");
    if(!this->mensagem)
    {
        cout << endl << " Arquivo nao encontrado...";
        system("pause");
        return;
    }
    caract = fgetc(this->mensagem);
    while(caract != EOF)
    {
        mensagemArquivo += caract;
        caract = fgetc(this->mensagem);
    }
    fclose(this->mensagem);

    nomeArquivo = arquivo;

    // Preenchimento do cabeçalho do Bitmap
    fread(&cabecalho.identificador, sizeof(cabecalho.identificador), 1, this->arquivo);
    fread(&cabecalho.tamanho, sizeof(cabecalho.tamanho), 1, this->arquivo);
    fread(&cabecalho.areaReservada1, sizeof(cabecalho.areaReservada1), 1, this->arquivo);
    fread(&cabecalho.areaReservada2, sizeof(cabecalho.areaReservada2), 1, this->arquivo);
    fread(&cabecalho.enderecoInicial, sizeof(cabecalho.enderecoInicial), 1, this->arquivo);

    // Preenchimento do Mapa de Bits
    fread(&cabDados.tamanho, sizeof(cabDados.tamanho), 1, this->arquivo);
    fread(&cabDados.largura, sizeof(cabDados.largura), 1, this->arquivo);
    fread(&cabDados.altura, sizeof(cabDados.altura), 1, this->arquivo);
    fread(&cabDados.planosCor, sizeof(cabDados.planosCor), 1, this->arquivo);
    fread(&cabDados.bpp, sizeof(cabDados.bpp), 1, this->arquivo);
    fread(&cabDados.metodoCompressao, sizeof(cabDados.metodoCompressao), 1, this->arquivo);
    fread(&cabDados.tamanhoImagem, sizeof(cabDados.tamanhoImagem), 1, this->arquivo);
    fread(&cabDados.resolucaoHorizontal, sizeof(cabDados.resolucaoHorizontal), 1, this->arquivo);
    fread(&cabDados.resolucaoVertical, sizeof(cabDados.resolucaoVertical), 1, this->arquivo);
    fread(&cabDados.coresNaPaleta, sizeof(cabDados.coresNaPaleta), 1, this->arquivo);
    fread(&cabDados.coresImportantes, sizeof(cabDados.coresImportantes), 1, this->arquivo);

    this->bytesDeAjuste = cabDados.largura % 4;

    // Posiciona no fim do arquivo
    fseek(this->arquivo, 0, SEEK_END);

    // Calcula a quantidade de pixels da imagem
    qtdePixels = cabDados.largura * cabDados.altura;
    pixels = new RGB[qtdePixels];

    // Posiciona no início da área de dados da imagem
    fseek(this->arquivo, cabecalho.enderecoInicial, SEEK_SET);

    // Jogando os pixels da imagem no vetor de RGB
    for (int larg = 0, pix = 0; pix < qtdePixels;)
    {
        // Se larg chegou ao tamanho da largura da imagem
        if (larg == cabDados.largura)
        {
            fseek(this->arquivo, bytesDeAjuste, SEEK_CUR); // "Pula" os bytes faltantes na linha
            larg = 0;
            continue; // Volta no início do for
        }

        fread(&pixels[pix++], sizeof(RGB), 1, this->arquivo); // Lê o pixel e atribui os valores à estrutura RGB
        larg++;
    }

    fclose(this->arquivo);

    unsigned long capacidade = (cabecalho.tamanho - cabecalho.enderecoInicial)/3;
    if(mensagemArquivo.size() > capacidade)
    {
        cout << "Arquivo de texto muito grande!" << endl;
        system("pause");
        return;
    }
    // Agora temos o bitmap estruturado dentro da nossa classe

    if(cabecalho.enderecoInicial != 0x36)
    {
        cout << "Tipo de arquivo invalido!" << endl;
        system("pause");
        return;
    }
    this->esconderMensagem(mensagemArquivo);
    this->salvarBitmap();

    imprimirInfoArquivo();

    delete pixels;
}

string Esteganografia::extrairMensagem(string arquivo)
{
    this->arquivo = fopen(arquivo.c_str(), "rb");
    if(!this->arquivo)
    {
        cout << endl << " Arquivo nao encontrado...";
        system("pause");
        return string("");
    }

    nomeArquivo = arquivo;

    // Preenchimento do cabeçalho do Bitmap
    fread(&cabecalho.identificador, sizeof(cabecalho.identificador), 1, this->arquivo);
    fread(&cabecalho.tamanho, sizeof(cabecalho.tamanho), 1, this->arquivo);
    fread(&cabecalho.areaReservada1, sizeof(cabecalho.areaReservada1), 1, this->arquivo);
    fread(&cabecalho.areaReservada2, sizeof(cabecalho.areaReservada2), 1, this->arquivo);
    fread(&cabecalho.enderecoInicial, sizeof(cabecalho.enderecoInicial), 1, this->arquivo);

    // Preenchimento do Mapa de Bits
    fread(&cabDados.tamanho, sizeof(cabDados.tamanho), 1, this->arquivo);
    fread(&cabDados.largura, sizeof(cabDados.largura), 1, this->arquivo);
    fread(&cabDados.altura, sizeof(cabDados.altura), 1, this->arquivo);
    fread(&cabDados.planosCor, sizeof(cabDados.planosCor), 1, this->arquivo);
    fread(&cabDados.bpp, sizeof(cabDados.bpp), 1, this->arquivo);
    fread(&cabDados.metodoCompressao, sizeof(cabDados.metodoCompressao), 1, this->arquivo);
    fread(&cabDados.tamanhoImagem, sizeof(cabDados.tamanhoImagem), 1, this->arquivo);
    fread(&cabDados.resolucaoHorizontal, sizeof(cabDados.resolucaoHorizontal), 1, this->arquivo);
    fread(&cabDados.resolucaoVertical, sizeof(cabDados.resolucaoVertical), 1, this->arquivo);
    fread(&cabDados.coresNaPaleta, sizeof(cabDados.coresNaPaleta), 1, this->arquivo);
    fread(&cabDados.coresImportantes, sizeof(cabDados.coresImportantes), 1, this->arquivo);

    this->bytesDeAjuste = cabDados.largura % 4;

    // Posiciona no fim do arquivo
    fseek(this->arquivo, 0, SEEK_END);

    // Calcula a quantidade de pixels da imagem
    qtdePixels = cabDados.largura * cabDados.altura;
    pixels = new RGB[qtdePixels];

    // Posiciona no início da área de dados da imagem
    fseek(this->arquivo, cabecalho.enderecoInicial, SEEK_SET);

    // Jogando os pixels da imagem no vetor de RGB
    for (int larg = 0, pix = 0; pix < qtdePixels;)
    {
        // Se larg chegou ao tamanho da largura da imagem
        if (larg == cabDados.largura)
        {
            fseek(this->arquivo, bytesDeAjuste, SEEK_CUR); // "Pula" os bytes faltantes na linha
            larg = 0;
            continue; // Volta no início do for
        }

        fread(&pixels[pix++], sizeof(RGB), 1, this->arquivo); // Lê o pixel e atribui os valores à estrutura RGB
        larg++;
    }

    fclose(this->arquivo);

    // Agora temos o bitmap estruturado dentro da nossa classe
    string mensagem;

    mensagem = this->extrairMensagem();

    return mensagem;
}

void Esteganografia::esconderMensagem(string mensagem)
{
    /*
     * Este método pega 3 pixels da imagem por vez. Dessa forma, teremos 9 bytes.
     * Desses 9 bytes, pegamos o bit menos significativo de 8 deles e repartimos
     * os caracteres da mensagem, letra a letra.
     */

    for(unsigned int numCaracter = 0, indice = 0; numCaracter <= mensagem.size(); numCaracter++)
    {
        this->pixels[indice].vermelho &= 254;
        this->pixels[indice].verde &= 254;
        this->pixels[indice].azul &= 254;

        this->pixels[indice].vermelho |= (mensagem[numCaracter] & 1 ? 1 : 0);
        this->pixels[indice].verde |= (mensagem[numCaracter] & 2 ? 1 : 0);
        this->pixels[indice].azul |= (mensagem[numCaracter] & 4 ? 1 : 0);
        indice++;

        this->pixels[indice].vermelho &= 254;
        this->pixels[indice].verde &= 254;
        this->pixels[indice].azul &= 254;

        this->pixels[indice].vermelho |= (mensagem[numCaracter] & 8 ? 1 : 0);
        this->pixels[indice].verde |= (mensagem[numCaracter] & 16 ? 1 : 0);
        this->pixels[indice].azul |= (mensagem[numCaracter] & 32 ? 1 : 0);
        indice++;

        this->pixels[indice].vermelho &= 254;
        this->pixels[indice].verde &= 254;

        this->pixels[indice].vermelho |= (mensagem[numCaracter] & 64 ? 1 : 0);
        this->pixels[indice].verde |= (mensagem[numCaracter] & 128 ? 1 : 0);
        indice++;
    }
}

void Esteganografia::salvarBitmap(void)
{
    arquivo = fopen(nomeArquivo.c_str(), "wb");
    if (!arquivo)
    {
        cout << " Impossivel criar/sobrescrever o arquivo." << endl;
        system("pause");
        exit(1);
    }

    fwrite(&cabecalho.identificador, sizeof(cabecalho.identificador), 1, arquivo);
    fwrite(&cabecalho.tamanho, sizeof(cabecalho.tamanho), 1, arquivo);
    fwrite(&cabecalho.areaReservada1, sizeof(cabecalho.areaReservada1), 1, arquivo);
    fwrite(&cabecalho.areaReservada2, sizeof(cabecalho.areaReservada2), 1, arquivo);
    fwrite(&cabecalho.enderecoInicial, sizeof(cabecalho.enderecoInicial), 1, arquivo);

    fwrite(&cabDados.tamanho, sizeof(cabDados.tamanho), 1, arquivo);
    fwrite(&cabDados.largura, sizeof(cabDados.largura), 1, arquivo);
    fwrite(&cabDados.altura, sizeof(cabDados.altura), 1, arquivo);
    fwrite(&cabDados.planosCor, sizeof(cabDados.planosCor), 1, arquivo);
    fwrite(&cabDados.bpp, sizeof(cabDados.bpp), 1, arquivo);
    fwrite(&cabDados.metodoCompressao, sizeof(cabDados.metodoCompressao), 1, arquivo);
    fwrite(&cabDados.tamanhoImagem, sizeof(cabDados.tamanhoImagem), 1, arquivo);
    fwrite(&cabDados.resolucaoHorizontal, sizeof(cabDados.resolucaoHorizontal), 1, arquivo);
    fwrite(&cabDados.resolucaoVertical, sizeof(cabDados.resolucaoVertical), 1, arquivo);
    fwrite(&cabDados.coresNaPaleta, sizeof(cabDados.coresNaPaleta), 1, arquivo);
    fwrite(&cabDados.coresImportantes, sizeof(cabDados.coresImportantes), 1, arquivo);

    char fimDeLinha[bytesDeAjuste];

    for (int i = 0; i < bytesDeAjuste; i++)
        fimDeLinha[i] = 0;

    for (int pix = 0, larg = 0; pix < qtdePixels;)
    {
        if (larg == cabDados.largura)
        {
            fwrite(&fimDeLinha, bytesDeAjuste, 1, arquivo);
            larg = 0;
            continue;
        }
        fwrite(&pixels[pix++], sizeof(RGB), 1, arquivo);
        larg++;
    }

    fclose(arquivo);
}

string Esteganografia::extrairMensagem(void)
{
    string mensagem;
    char caracter;
    int indice = 0;

    /*
     * A cada 3 pixels (9 bytes), pegamos os bits menos significativos
     * dos 8 primeiros bytes - da mesma forma como foi gravado
     */

    do
    {
        caracter = (pixels[indice].vermelho & 1) +
                   ((pixels[indice].verde & 1) * 2) +
                   ((pixels[indice].azul & 1) * 4) +
                   ((pixels[indice+1].vermelho  & 1) * 8 ) +
                   ((pixels[indice+1].verde & 1) * 16) +
                   ((pixels[indice+1].azul & 1) * 32) +
                   ((pixels[indice+2].vermelho & 1) * 64) +
                   ((pixels[indice+2].verde & 1) * 128);

        mensagem += caracter;
        indice += 3;
    }
    while (caracter != '\0');

    mensagem = mensagem.substr(0, mensagem.length() - 1);
    return mensagem;
}

void Esteganografia::imprimirInfoArquivo(void)
{
    printf("\n INFORMACOES DO BITMAP\n\n");
    printf(" Identificador: %c%c\n", cabecalho.identificador[0], cabecalho.identificador[1]);
    printf(" Tamanho: %i\n", cabecalho.tamanho);
    printf(" Endereco Inicial: 0x%X\n", cabecalho.enderecoInicial);
    printf(" Area Reservada 1: %hi\n", cabecalho.areaReservada1);
    printf(" Area Reservada 2: %hi\n", cabecalho.areaReservada2);
    printf(" Altura: %i\n", cabDados.altura);
    printf(" Largura: %i\n", cabDados.largura);
    printf(" Pixels: %i\n", qtdePixels);
    printf(" Bytes de ajuste: %i\n ", bytesDeAjuste);
    system("pause");
}