The Social Project: Configurando a Assinatura do App no CI

Se você ainda não conhece as motivações deste projeto, leia o primeiro artigo aqui.

Olá pessoal! No último artigo, configuramos o nosso build script para suportar o Kotlin. No post de hoje, vamos configurar a assinatura do app e levantar um ponto importante: como guardar seus segredos, principalmente quando o app é open source?

Com certeza, se você procurar um pouco, você encontrará diversos repositório com todo tipo de informação privada, desde chaves de APIs a ferramentas pagas. Todas, commitadas de forma acidental (ou mesmo proposital, mas de forma inadvertida) no repositório público do projeto.

No Android, um dos maiores “segredos” que mantemos é a chave de assinatura do app. Ela, juntamente com o Application ID, fazem um app se tornar único. Como o identificador é público, o vazamento de uma chave pode gerar um seríssimo risco de segurança, possibilitando que versões modificadas do seu app sejam passíveis de instalação sobre o app original, não só possibilitando comportamentos potencialmente perigosos para o usuário, como o acesso a informações internas e privadas que eventualmente o seu app tenha salvo localmente (como dados de autenticação na sua API, por exemplo).

A perda dessa chave também pode ser um problemão. Sem ela, é impossível enviar atualizações do seu app para o Google Play, por exemplo. Hoje felizmente já temos formas de minimizar esse risco, utilizando o Google Play App Signing (assunto que com certeza será abordado nessa série de posts).

Falando especificamente do nosso app, temos então um problema com relação a isso. Para a assinatura, precisamos de um arquivo (geralmente com a extensão .keystore ou .jks), duas senhas e um alias. O primeiro questionamento é sobre o arquivo em si. Colocaremos ele no repositório? Por mais que a chave necessite das senhas, ter o arquivo exposto envolve algum grau de risco.

Para solucionar esse problema temos basicamente duas abordagens: a primeira delas seria colocar o arquivo de assinatura em algum serviço externo, como Google Drive ou Dropbox, e baixá-lo no momento da build utilizando curl. Nesse cenário, teríamos uma chave de API exposta via variável de ambiente. Apesar de viável, esse cenário tem o problema de estarmos acoplando uma dependência externa ao simples processo de build do APK. Além de oscilações de rede que podem causar falha da build, também temos que manter essa request em dia, tendo que ajustar a chamada de download no caso de eventuais mudanças nas APIs desses serviços.

A segunda abordagem, e é a que vou utilizar aqui, será criptografar o arquivo e colocá-lo no repositório junto com o projeto. No CI, teremos exportado como variável de ambiente a chave que consegue descriptografar o arquivo, bem como as outras chaves necessárias para assinar o APK.

A maneira mais simples de gerarmos uma chave é através do próprio Android Studio.

Com a chave gerada, utilizaremos o openssl para criptografar o arquivo. Podemos fazer isso utilizando o comando:

openssl aes-256-cbc -e -in release.keystore -out release.keystore-cipher -md sha256 -k $CIPHER_DECRYPT_KEY

Perceba que no comando temos uma variável chamada CIPHER_DECRYPT_KEY, que é a chave que irá descriptografar o nosso arquivo lá no CI.

Com o arquivo criptografado gerado, vamos criar uma pasta no projeto para guardar as chaves. Além do arquivo criptografado release.keystore-cipher, também estou colocando o arquivo que assina as releases de debug. Isso é particularmente útil quando existe mais de uma pessoa trabalhando no projeto ou quando eventualmente há a necessidade de se trocar ou formatar a máquina, já que essa chave é gerada sempre que uma nova instalação da SDK do Android é realizada.

Com os arquivos no projeto, vamos primeiramente dizer ao CI para descriptografar o arquivo antes de executar a build. Perceba que adicionamos um passo chamado Decrypt release key ao nosso arquivo config.yml. Também modifiquei o passo de build para chamar a task assemble em vez da build (para validar a parte de assinatura de ambos os build variants):

version: 2
jobs:
  build:
    docker:
      - image: circleci/android:api-27-alpha

    working_directory: ~/social-app

    environment:
      JVM_OPTS: -Xmx3200m
      CIRCLE_JDK_VERSION: oraclejdk8

    steps:
      - checkout

      - restore_cache:
          key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}

      - run:
          name: Accept licenses
          command: yes | sdkmanager --licenses || true

      - run:
          name: Decrypt release key
          command: openssl aes-256-cbc -d -in distribution/release.keystore-cipher -out distribution/release.keystore -md sha256 -k $CIPHER_DECRYPT_KEY

      - run:
          name: Build
          command: ./gradlew clean check assemble assembleAndroidTest

      - save_cache:
          paths:
            - ~/.gradle
          key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}

Feito isso, vamos ao nosso build script configurar a assinatura e atribui-las para cada um dos build variants:

...

android {
  ...
  signingConfigs {
    debug {
      storeFile file("$rootDir/distribution/debug.keystore")
      storePassword 'android'
      keyAlias 'androiddebugkey'
      keyPassword 'android'
    }

    release {
      storeFile file("$rootDir/distribution/release.keystore")
      storePassword System.env.RELEASE_STORE_PASSWORD
      keyAlias System.env.RELEASE_KEY_ALIAS
      keyPassword System.env.RELEASE_KEY_PASSWORD
    }
  }

  buildTypes {
    debug {
      signingConfig signingConfigs.debug
    }

    release {
      signingConfig signingConfigs.release
      minifyEnabled false
      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
  }
}

...

Perceba que a parte da assinatura de release vai pegar também os dados através de variáveis de ambiente, que serão adicionadas ao CircleCI:

E é isso! O Pull Request foi aberto e aprovado para este caso, tendo passado no CI e integrado na branch develop do repositório.

No próximo post vamos fazer a configuração do Firebase no projeto 🙂

Gerenciando suas Chaves no Android de forma Eficiente

Olá pessoal! Depois de um bom tempo sumido, estou de volta para falar de gerenciamento de chaves de assinatura no Android.

Beleza, você já desenvolveu seu aplicativo matador, que vai te gerar milhões em 2 dias, e agora vai gerar a sua build de release para publicar no Google Play. Nesse caso, precisamos criar a nossa assinatura de release no arquivo build.gradle do nosso projeto. Alguma coisa mais ou menos assim:

signingConfigs {
    release {
        storeFile file('/home/rafael/minha_key.jks')
        storePassword 'senha secreta'
        keyAlias 'app_android'
        keyPassword 'senha mais secreta ainda'
    }
}

Mas… tem alguma coisa errada aí. Principalmente se estamos utilizando algum controle de versão, como o Git ou SVN, não é uma boa prática enviarmos dados sensíveis para eles (como senhas, keystores e coisas do tipo). Durante algum tempo, a saída utilizada por mim foi adicionar esses dados ao arquivo gradle.properties na raiz do projeto, e não enviá-lo para o controle de versão.

STORE_FILE=/home/rafael/minha_key.jks
STORE_PASSWORD=senha secreta
KEY_ALIAS=app_android
KEY_PASSWORD=senha mais secreta ainda

Dessa forma, a nossa configuração de assinatura ficaria algo assim:

signingConfigs {
    release {
        storeFile file(STORE_FILE)
        storePassword STORE_PASSWORD
        keyAlias KEY_ALIAS
        keyPassword KEY_PASSWORD
    }
}

Funciona bem, não temos dados sensíveis no nosso controle de versão mas… temos basicamente dois problemas principais nessa abordagem:

  • No clone do projeto, você terá um erro de build. Ou seja, se entrou alguém na equipe e clonar o projeto, baterá com a cara na porta logo de cara. Isso já vai atrasar um pouco o join no projeto. Esse problema também se aplica quando o código está em um servidor de CI (Jenkins, Travis, CircleCI, etc.)
  • Tem o agravante humano, ou seja, sempre há o risco de, sem querer, você enviar o arquivo original por algum motivo. Obviamente que você pode adicionar este arquivo no .gitignore ou equivalente no SVN, mas nesse caso caímos no primeiro cenário problemático.

Então… qual a saída?

Depois de pensar um pouco, acabei encontrando, hoje, a melhor saída com o uso de variáveis de ambiente! Como assim?

Vamos lá: o primeiro passo é criarmos uma assinatura default para o projeto. No meu caso, eu simplesmente copio a debug.keystore (que fica dentro da pasta .android do  usuário) e coloco, por exemplo, dentro de um diretório chamado distribution na raiz do meu projeto. Essa chave vai garantir que a build não vai quebrar, por exemplo, quando alguém clonar o projeto ou em um servidor de CI, num primeiro momento.

Agora, na configuração da minha assinatura, podemos fazer algo do tipo:

signingConfigs {
    release {
        storeFile file(System.getenv('STORE_FILE') != null ? System.getenv('STORE_FILE') : '../distribution/debug.keystore')
        storePassword System.getenv('STORE_PASSWORD') != null ? System.getenv('STORE_PASSWORD') : 'android'
        keyAlias System.getenv('KEY_ALIAS') != null ? System.getenv('KEY_ALIAS') : 'androiddebug'
        keyPassword System.getenv('KEY_PASSWORD') != null ? System.getenv('KEY_PASSWORD') : 'android'
    }
}

Nota: caso seu servidor de CI esteja gerando builds de release, você pode setar as variáveis de ambiente nele também para gerar builds assinadas corretamente.

E pronto! Agora, para gerar a build corretamente, você pode exportar as variáveis de ambiente ou, como eu faço, colocar os dados da chave no comando apenas na hora de gerar a build de release:

$ STORE_FILE='/home/rafael/minha_key.jks' STORE_PASSWORD='senha secreta' KEY_ALIAS='app_android' KEY_PASSWORD='senha mais secreta ainda' ./gradlew clean assembleRelease

 E é isso! Espero que a dica seja útil para vocês e, caso tenham sugestões, podem postar aí nos comentários!

Até a próxima! 😀

Tutorial Mentawai #4 – Autorização

Olá pessoal! Tudo certo?

No último tutorial sobre o Mentawai, vimos como implementar facilmente autenticação em um aplicativo web. No post de hoje, vamos complementar o conceito, mostrando o que o Mentawai pode oferecer em se falando de Autorização.

De fato, Autenticação e Autorização são conceitos complementares. Não faz sentido se falar em Autorização (o que cada usuário tem acesso) se o aplicativo não implementa Autenticação (apenas usuários autênticos podem ter acesso ao aplicativo).

No Mentawai, o conceito de Autorização é implementado através de Grupos e Permissões. Um Usuário possui um ou mais grupos. Um Grupo, por sua vez, possui uma ou mais Permissões.

A criação dos Grupos e consequente definição das permissões deve ocorrer no método init() do ApplicationManager, dessa forma:

@Override
public void init(Context application) {
Group administrador = new Group("administrador");
// Pode adicionar uma permissão de cada vez
administrador.addPermission("criarUsuario");
// Ou adicionar encadeada
administrador.addPermission("configurarSistema").addPermission("visualizarMenu");
AuthorizationManager.addGroup(administrador);
Group usuarioComum = new Group("usuarioComum");
usuarioComum.addPermission("visualizarMenu");
AuthorizationManager.addGroup(usuarioComum);
}
view raw snippet01.java hosted with ❤ by GitHub

Definidos os grupos, ao autenticar o usuário você atribui o(s) grupo(s) ao qual ele pertence.

@Override
public String execute() throws Exception {
String email = input.getString("email");
String senha = input.getString("senha");
. . .
if (email.equals("admin@rafaeltoledo.net")) {
setUserSession(email);
// pode-se fazer assim...
setUserGroup("administrador");
setUserGroup("usuarioComum");
// ... ou assim!
List<String> grupos = new ArrayList<String>();
grupos.add("administrador");
grupos.add("usuarioComum");
setUserGroups(grupos);
}
return SUCCESS;
}
view raw snippet02.java hosted with ❤ by GitHub

Já nas páginas, o Mentawai fornece algumas tags para controlar o que pode ou não ser visto por cada um dos usuários, controlando por grupos ou por permissões.

<!-- Protegendo a página toda, configurando vários grupos -->
<mtw:requiresAuthorization groups="administrador, usuarioComum"/>
<!-- Protegendo a página toda, configurando um grupo apenas -->
<mtw:requiresAuthorization group="administrador"/>
<!-- Ou ainda protegendo a página toda por permissões -->
<mtw:requiresAuthorization permission="configurarSistema"/>
<!-- Ou protegendo trechos da página por grupos... -->
<mtw:hasAuthorization group="administrador"><!-- também pode definir mais de um grupo -->
<p>Só administradores veem isso.</p>
</mtw:hasAuthorization>
<!-- ... ou por permissões -->
<mtw:hasAuthorization permission="configurarSistema"><!-- também pode definir mais de uma permissão -->
<p>Só quem tem a permissão configurarSistema vê isso.</p>
</mtw:hasAuthorization>
view raw snippet03.jsp hosted with ❤ by GitHub

Para concluir, basta adicionar uma consequência global ao ApplicationManager para redirecionar os usuários sem determinada permissão para uma página de erro, por exemplo. Você também pode proteger, em vez da página, a própria chamada à Action.

@Override
public void loadActions() {
addGlobalFilter(new AuthenticationFilter());
addGlobalConsequence(AuthenticationFilter.LOGIN, new Redirect("/login.jsp"));
addGlobalConsequence(AuthorizationFilter.ACCESSDENIED, new Redirect("/erro.jsp"));
ActionConfig ac = new ActionConfig("CriarUsuario", CriarUsuarioAction.class);
ac.addConsequence(CriarUsuarioAction.SUCCESS, new Redirect("/usuarioList.jsp"));
ac.addFilter(new AuthorizationFilter(new Permission("criarUsuario")));
addActionConfig(ac);
. . .
}
view raw snippet04.java hosted with ❤ by GitHub

Simples não? Se você tem os papéis de cada um dos usuários bem definidos dentro de seu aplicativo, é muito fácil criar o seu controle de acesso utilizando o Mentawai. Em 2010 precisei fazer um sistema mais “maleável” e apanhei um pouco… vou procurar o código e em breve faço um post mostrando como resolvi.

Por enquanto é só! Até a próxima! 😀

Esteganografia com Arquivos Bitmap em C++

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.

#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
view raw esteganografia.h hosted with ❤ by GitHub
#include "estaganografia.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
return this->extrairMensagem();
}
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() {
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() {
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");
}
view raw esteganografia.cpp hosted with ❤ by GitHub