This C++ code implements a complete universal steganography system for BMP files, incorporating AES-128 encryption in CTR mode and a SP800-90A-compliant cryptographic pseudo-random number generator (CSPRNG). The aes_ctr.hpp file provides a minimal, standalone implementation of AES-128-CTR with key expansion, a full S-box, encryption operations (SubBytes, ShiftRows, MixColumns, AddRoundKey), and keystream generation, while csprng.hpp offers a robust CSPRNG based on HMAC-SHA256 with automatic reseeding after 1 MiB, relying on system entropy sources (getrandom on Linux, CryptGenRandom on Windows). The main program main.cpp handles encoding and decoding of arbitrary data into the 3 LSB channels of 24/32-bit BMP pixels (all formats supported: compression 0/3, top-down/bottom-up), with a steganographic header "STG1" containing metadata, salt, and IV, key derivation from a password via the CSPRNG, an optional pixel-order permutation to strengthen security, and automatic generation of an external key file in hexadecimal format for decoding—ensuring perfect invisibility of the hidden data.
The files aes-ctr.hpp, csprng.hpp, and error_logger.hpp are the same as in the post: https://www.thecodingforums.com/thr...c-smart-decrypt-ctrl-v-c-windows-hook.976966/
Only the files aes-ctr.hpp, csprng.hpp, and error_logger.hpp need to be included in the project to compile the image encoder/decoder application; the two other source files after main.cpp are additional tools to make the application easier to use.
Use these commands to encode and decode:
app.exe encode -i test_512x512.bmp -e secret.txt -o stego.bmp -p pass123
and
app.exe decode -i stego.bmp -o extract_secret.txt.
main.cpp :
Here’s the code to generate compatible BMP images — I used the test_512x512 image and it works.
Finally, here’s the code to extract the hidden data from the output image to verify that the message is encrypted in the image.
The files aes-ctr.hpp, csprng.hpp, and error_logger.hpp are the same as in the post: https://www.thecodingforums.com/thr...c-smart-decrypt-ctrl-v-c-windows-hook.976966/
Only the files aes-ctr.hpp, csprng.hpp, and error_logger.hpp need to be included in the project to compile the image encoder/decoder application; the two other source files after main.cpp are additional tools to make the application easier to use.
Use these commands to encode and decode:
app.exe encode -i test_512x512.bmp -e secret.txt -o stego.bmp -p pass123
and
app.exe decode -i stego.bmp -o extract_secret.txt.
main.cpp :
C++:
// main.cpp - Steganography BMP UNIVERSAL avec AES-128-CTR et CSPRNG (tous BMP acceptés)
/*
MIT License
Copyright (c) 2025 CoTon_TiGe_MoUaRf
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <cstring>
#include <cstdint>
#include <iomanip>
#include <sstream>
#include <algorithm>
#include <cstdlib>
#include <array>
#include "aes_ctr.hpp"
#include "csprng.hpp"
// Structures BMP avec packing strict
#pragma pack(push, 1)
struct BMP_FILEHEADER {
uint16_t bfType;
uint32_t bfSize;
uint16_t bfReserved1;
uint16_t bfReserved2;
uint32_t bfOffBits;
};
struct BMP_INFOHEADER {
uint32_t biSize;
int32_t biWidth;
int32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
int32_t biXPelsPerMeter;
int32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
};
#pragma pack(pop)
// En-tête stéganographie (32 bytes) - SANS padding
#pragma pack(push, 1)
struct StegoHeader {
char signature[4]; // "STG1"
uint8_t version; // 1
uint8_t flags; // bit0: chiffrement, bit1: permutation
uint32_t orig_size; // taille payload original
uint32_t enc_size; // taille payload chiffré
uint8_t salt[16]; // salt pour KDF
uint8_t iv[12]; // IV pour AES-CTR (96 bits)
};
#pragma pack(pop)
namespace stego {
constexpr int BITS_PER_CHANNEL = 1;
constexpr size_t HEADER_SIZE = sizeof(StegoHeader);
csprng::CSPRNG global_rng;
// ÉCRIRE la clé dans un fichier texte HEX (encode)
void save_key_to_file(const uint8_t key[16], const std::string& key_file) {
std::ofstream out(key_file);
if (!out) throw std::runtime_error("Impossible d'écrire " + key_file);
for(int i = 0; i < 16; i++) {
out << std::hex << std::uppercase << std::setfill('0') << std::setw(2) << (int)key[i];
}
out << std::dec;
std::cout << "💾 Clé sauvée dans: " << key_file << "\n";
}
// CHARGER la clé depuis un fichier texte HEX (decode)
void load_key_from_file(uint8_t key[16], const std::string& key_file) {
std::ifstream in(key_file);
if (!in) throw std::runtime_error("Impossible de lire " + key_file + ". Utilisez la clé générée à l'encodage.");
std::string key_hex;
in >> key_hex;
if (key_hex.length() != 32) throw std::runtime_error("Clé invalide dans " + key_file + " (32 hex chars attendus)");
for(int i = 0; i < 16; i++) {
std::string byte_hex = key_hex.substr(i*2, 2);
key[i] = static_cast<uint8_t>(std::stoi(byte_hex, nullptr, 16));
}
std::cout << "📂 Clé chargée depuis: " << key_file << "\n";
}
void derive_key(const std::string& password, const uint8_t salt[16], uint8_t key[16]) {
csprng::CSPRNG local_rng; // RNG local pour éviter contamination
std::vector<uint8_t> input(password.begin(), password.end());
input.insert(input.end(), salt, salt + 16);
local_rng.reseed_manual(input.data(), input.size());
local_rng.generate(key, 16);
for(int i = 0; i < 16; i++) key[i] ^= salt[i];
}
size_t calculate_capacity(int width, int height, int channels_in_use) {
return size_t(width) * height * channels_in_use * BITS_PER_CHANNEL;
}
// Lire TOUS types BMP (24/32-bit, compression 0/3, top/bottom-up) - CORRIGÉ BGR
std::vector<uint8_t> read_bmp_pixels(const std::string& filename, int& width, int& height,
size_t& row_padding, int& bytes_per_pixel,
BMP_FILEHEADER& file_hdr, BMP_INFOHEADER& info_hdr) {
std::ifstream file(filename, std::ios::binary);
if (!file) throw std::runtime_error("Impossible d'ouvrir l'image");
file.read(reinterpret_cast<char*>(&file_hdr), sizeof(BMP_FILEHEADER));
if (file.gcount() != sizeof(BMP_FILEHEADER)) {
throw std::runtime_error("Lecture file header BMP échouée");
}
if (file_hdr.bfType != 0x4D42) throw std::runtime_error("Pas un fichier BMP valide");
uint32_t biSize;
file.read(reinterpret_cast<char*>(&biSize), sizeof(uint32_t));
if (file.gcount() != sizeof(uint32_t)) {
throw std::runtime_error("Lecture biSize échouée");
}
info_hdr.biSize = biSize;
if (biSize < 40) throw std::runtime_error("Header BMP trop petit");
file.read(reinterpret_cast<char*>(&info_hdr) + 4, sizeof(BMP_INFOHEADER) - 4);
if (file.gcount() != sizeof(BMP_INFOHEADER) - 4) {
throw std::runtime_error("Lecture info header BMP échouée");
}
if (info_hdr.biPlanes != 1) throw std::runtime_error("Seulement 1 plan de couleur supporté");
if (info_hdr.biBitCount != 24 && info_hdr.biBitCount != 32)
throw std::runtime_error("Seulement 24/32 bits supportés");
// Skip color masks si BI_BITFIELDS (compression=3)
if (info_hdr.biCompression == 3) {
uint32_t dummy[3];
file.read(reinterpret_cast<char*>(dummy), 12);
}
width = info_hdr.biWidth;
height = std::abs(info_hdr.biHeight);
bytes_per_pixel = info_hdr.biBitCount / 8;
row_padding = (4u - (static_cast<size_t>(width) * bytes_per_pixel % 4u)) % 4u;
bool top_down = (info_hdr.biHeight < 0);
size_t pixel_size = static_cast<size_t>(width) * height * bytes_per_pixel;
std::vector<uint8_t> pixels(pixel_size);
file.seekg(file_hdr.bfOffBits, std::ios::beg);
int y_start = top_down ? 0 : height - 1;
int y_end = top_down ? height : -1;
int y_step = top_down ? 1 : -1;
for (int y = y_start; y != y_end; y += y_step) {
size_t row_offset = static_cast<size_t>(y) * width * bytes_per_pixel;
file.read(reinterpret_cast<char*>(pixels.data() + row_offset),
static_cast<std::streamsize>(width * bytes_per_pixel));
if (row_padding > 0) file.ignore(static_cast<std::streamsize>(row_padding));
}
// CORRECTION BGR : Pour 24-bit (BGR), les canaux sont 0=B,1=G,2=R
// Pour 32-bit (BGRA), les canaux sont 0=B,1=G,2=R,3=A (on ignore A)
// On utilise toujours B,G,R dans cet ordre pour la stéganographie
std::cout << "📐 Image: " << width << "x" << height << " ("
<< bytes_per_pixel*8 << "-bit, " << (top_down ? "top-down" : "bottom-up") << ")\n";
return pixels;
}
void write_bmp_pixels(const std::string& filename, const std::vector<uint8_t>& pixels,
int width, int height, size_t row_padding, int bytes_per_pixel,
const BMP_FILEHEADER& file_hdr_orig, const BMP_INFOHEADER& info_hdr_orig) {
std::ofstream file(filename, std::ios::binary);
if (!file) throw std::runtime_error("Impossible d'écrire l'image");
BMP_FILEHEADER file_hdr = file_hdr_orig;
BMP_INFOHEADER info_hdr = info_hdr_orig;
size_t image_size = pixels.size();
file_hdr.bfSize = file_hdr.bfOffBits + static_cast<uint32_t>(image_size);
info_hdr.biSizeImage = static_cast<uint32_t>(image_size);
file.write(reinterpret_cast<const char*>(&file_hdr), sizeof(file_hdr));
file.write(reinterpret_cast<const char*>(&info_hdr), sizeof(info_hdr));
bool top_down = (info_hdr.biHeight < 0);
int y_start = top_down ? 0 : height - 1;
int y_end = top_down ? height : -1;
int y_step = top_down ? 1 : -1;
for (int y = y_start; y != y_end; y += y_step) {
size_t offset = static_cast<size_t>(y) * width * bytes_per_pixel;
file.write(reinterpret_cast<const char*>(pixels.data() + offset),
static_cast<std::streamsize>(width * bytes_per_pixel));
if (row_padding > 0) {
char padding[3] = {0, 0, 0};
file.write(padding, static_cast<std::streamsize>(row_padding));
}
}
}
uint8_t get_bit(uint8_t byte, int pos) { return (byte >> pos) & 1u; }
void set_bit(uint8_t& byte, int pos, uint8_t bit) {
byte = static_cast<uint8_t>((byte & ~static_cast<uint8_t>(1u << pos)) | (bit << pos));
}
// CSPRNG LOCALE pour éviter contamination d'état global
std::vector<size_t> generate_pixel_order(int width, int height, const uint8_t salt[16]) {
csprng::CSPRNG local_rng;
local_rng.reseed_manual(salt, 16);
size_t total_pixels = static_cast<size_t>(width) * height;
std::vector<size_t> order(total_pixels);
for (size_t i = 0; i < total_pixels; i++) order[i] = i;
for (size_t i = total_pixels; i > 1; ) {
size_t j = static_cast<size_t>(local_rng() % i);
std::swap(order[--i], order[j]);
}
return order;
}
void encode(const std::string& input_bmp, const std::string& payload_file,
const std::string& output_bmp, const std::string& password, bool use_perm) {
std::ifstream payload(payload_file, std::ios::binary | std::ios::ate);
if (!payload) throw std::runtime_error("Impossible d'ouvrir le payload");
size_t orig_size = static_cast<size_t>(payload.tellg());
std::vector<uint8_t> payload_data(orig_size);
payload.seekg(0, std::ios::beg);
payload.read(reinterpret_cast<char*>(payload_data.data()), static_cast<std::streamsize>(orig_size));
int width, height, bytes_per_pixel;
size_t row_padding;
BMP_FILEHEADER file_hdr;
BMP_INFOHEADER info_hdr;
std::vector<uint8_t> pixels = read_bmp_pixels(input_bmp, width, height, row_padding, bytes_per_pixel, file_hdr, info_hdr);
int channels_in_use = (bytes_per_pixel == 4) ? 3 : bytes_per_pixel;
size_t capacity = calculate_capacity(width, height, channels_in_use);
size_t iv_size = 16;
size_t enc_payload_size = orig_size + iv_size;
size_t header_bits = HEADER_SIZE * 8;
size_t payload_bits = enc_payload_size * 8;
size_t total_bits = header_bits + payload_bits;
if (total_bits > capacity) {
throw std::runtime_error("Capacité insuffisante");
}
// === HEADER ===
StegoHeader header = {};
std::memcpy(header.signature, "STG1", 4);
header.version = 2; // Version 2 = clé externe
header.flags = static_cast<uint8_t>((use_perm ? 2u : 0u) | 1u);
header.orig_size = static_cast<uint32_t>(orig_size);
header.enc_size = static_cast<uint32_t>(enc_payload_size);
global_rng.generate(header.salt, sizeof(header.salt));
// === GÉNÉRATION CLÉ + SAUVEGARDE ===
uint8_t key[16];
derive_key(password, header.salt, key);
// 💾 SAUVEGARDE CLÉ dans fichier <output_bmp>.key
std::string key_file = output_bmp + ".key";
save_key_to_file(key, key_file);
printf("🔑 ENCODE - Clé générée: ");
for(int i=0; i<16; i++) printf("%02x", key[i]);
printf("\n");
// === IV + CHIFFREMENT ===
std::array<uint8_t, 16> aes_iv = {};
global_rng.generate(aes_iv.data(), 12);
std::memset(aes_iv.data() + 12, 0, 4);
std::vector<uint8_t> encrypted_payload(iv_size + orig_size);
std::memcpy(encrypted_payload.data(), aes_iv.data(), iv_size);
std::memcpy(encrypted_payload.data() + iv_size, payload_data.data(), orig_size);
AES128_CTR aes(key, aes_iv.data());
aes.process(encrypted_payload.data() + iv_size, orig_size);
// === STREAM ===
std::vector<uint8_t> stream(HEADER_SIZE + enc_payload_size);
std::memcpy(stream.data(), &header, HEADER_SIZE);
std::memcpy(stream.data() + HEADER_SIZE, encrypted_payload.data(), enc_payload_size);
std::vector<size_t> pixel_order;
if (use_perm) pixel_order = generate_pixel_order(width, height, header.salt);
// Écriture bits
size_t bit_pixel_count = 0;
for (size_t byte_idx = 0; byte_idx < stream.size(); ++byte_idx) {
uint8_t byte = stream[byte_idx];
for (int bit_pos = 7; bit_pos >= 0; --bit_pos) {
uint8_t bit = (byte >> bit_pos) & 1u;
size_t px = use_perm ? pixel_order[bit_pixel_count / channels_in_use] : (bit_pixel_count / channels_in_use);
int channel = static_cast<int>(bit_pixel_count % channels_in_use);
size_t pixel_offset = px * bytes_per_pixel + channel;
if (pixel_offset < pixels.size()) {
set_bit(pixels[pixel_offset], 0, bit);
}
++bit_pixel_count;
}
}
write_bmp_pixels(output_bmp, pixels, width, height, row_padding, bytes_per_pixel, file_hdr, info_hdr);
std::cout << "✅ Encodage terminé. Utilisez: " << key_file << " pour décoder\n";
}
std::vector<uint8_t> decode(const std::string& input_bmp, const std::string& password) {
int width, height, bytes_per_pixel;
size_t row_padding;
BMP_FILEHEADER file_hdr;
BMP_INFOHEADER info_hdr;
std::vector<uint8_t> pixels = read_bmp_pixels(input_bmp, width, height, row_padding, bytes_per_pixel, file_hdr, info_hdr);
int channels_in_use = (bytes_per_pixel == 4) ? 3 : bytes_per_pixel;
// === LECTURE HEADER ===
std::vector<uint8_t> header_bytes(HEADER_SIZE, 0);
size_t bit_pixel_count = 0;
for (size_t byte_idx = 0; byte_idx < HEADER_SIZE; ++byte_idx) {
for (int bit_pos = 7; bit_pos >= 0; --bit_pos) {
size_t px = bit_pixel_count / channels_in_use;
int channel = static_cast<int>(bit_pixel_count % channels_in_use);
size_t pixel_offset = px * bytes_per_pixel + channel;
uint8_t bit = (pixel_offset < pixels.size()) ? get_bit(pixels[pixel_offset], 0) : 0;
header_bytes[byte_idx] |= (bit << bit_pos);
++bit_pixel_count;
}
}
StegoHeader header;
std::memcpy(&header, header_bytes.data(), HEADER_SIZE);
if (std::strncmp(reinterpret_cast<char*>(header.signature), "STG1", 4) != 0) {
throw std::runtime_error("❌ Signature 'STG1' non trouvée");
}
if (header.version != 2) {
throw std::runtime_error("❌ Version header non supportée (nécessite v2 avec clé externe)");
}
bool use_perm = (header.flags & 2u) != 0;
std::vector<size_t> pixel_order;
size_t bit_pixel_count_payload = HEADER_SIZE * 8;
std::cout << "📋 Header v" << (int)header.version
<< " orig=" << header.orig_size << " enc=" << header.enc_size << "\n";
if (use_perm) {
pixel_order = generate_pixel_order(width, height, header.salt);
}
// === LECTURE PAYLOAD ===
size_t enc_payload_size = static_cast<size_t>(header.enc_size);
std::vector<uint8_t> encrypted_payload(enc_payload_size, 0);
for (size_t byte_idx = 0; byte_idx < enc_payload_size; ++byte_idx) {
for (int bit_pos = 7; bit_pos >= 0; --bit_pos) {
size_t px = use_perm ? pixel_order[bit_pixel_count_payload / channels_in_use] : (bit_pixel_count_payload / channels_in_use);
int channel = static_cast<int>(bit_pixel_count_payload % channels_in_use);
size_t pixel_offset = px * bytes_per_pixel + channel;
uint8_t bit = (pixel_offset < pixels.size()) ? get_bit(pixels[pixel_offset], 0) : 0;
encrypted_payload[byte_idx] |= (bit << bit_pos);
++bit_pixel_count_payload;
}
}
// === CHARGEMENT CLÉ EXTERNE ===
if (header.flags & 1u) {
std::string key_file = input_bmp + ".key";
uint8_t key[16];
load_key_from_file(key, key_file); // 📂 CHARGEMENT CLÉ
// EXTRAIRE IV
std::array<uint8_t, 16> aes_iv;
std::memcpy(aes_iv.data(), encrypted_payload.data(), 16);
printf("🔑 DECODE - Clé chargée: ");
for(int i=0; i<16; i++) printf("%02x", key[i]);
printf("\n🔢 DECODE - IV extrait: ");
for(int i=0; i<16; i++) printf("%02x ", aes_iv[i]);
printf("\n");
// DÉCHIFFRER
size_t data_size = enc_payload_size - 16;
AES128_CTR aes(key, aes_iv.data());
aes.process(encrypted_payload.data() + 16, data_size);
}
size_t result_size = static_cast<size_t>(header.orig_size);
return std::vector<uint8_t>(encrypted_payload.begin() + 16, encrypted_payload.begin() + 16 + result_size);
}
}
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "\n=== Steganography BMP AES-CTR (UNIVERSAL) v2 ===\n";
std::cerr << "Usage ENCODE:\n";
std::cerr << " ./stego encode -i input.bmp -e payload.bin -o output.bmp [-p password] [--perm]\n";
std::cerr << " → Génère automatiquement output.bmp.key\n\n";
std::cerr << "Usage DECODE:\n";
std::cerr << " ./stego decode -i stego.bmp -o output.bin\n";
std::cerr << " → Lit automatiquement stego.bmp.key\n\n";
std::cerr << "⚠️ Gardez le fichier .key pour décoder !\n";
return 1;
}
std::string command(argv[1]);
try {
if (command == "encode") {
std::string input_bmp, payload_file, output_bmp, password = "default";
bool use_perm = false;
for (int i = 2; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-i" && i+1 < argc) input_bmp = argv[++i];
else if (arg == "-e" && i+1 < argc) payload_file = argv[++i];
else if (arg == "-o" && i+1 < argc) output_bmp = argv[++i];
else if (arg == "-p" && i+1 < argc) password = argv[++i];
else if (arg == "--perm") use_perm = true;
}
if (input_bmp.empty() || payload_file.empty() || output_bmp.empty()) {
throw std::runtime_error("❌ Paramètres -i -e -o obligatoires pour encode");
}
stego::encode(input_bmp, payload_file, output_bmp, password, use_perm);
}
else if (command == "decode") {
std::string input_bmp, output_bin, password = "default";
for (int i = 2; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-i" && i+1 < argc) input_bmp = argv[++i];
else if (arg == "-o" && i+1 < argc) output_bin = argv[++i];
else if (arg == "-p" && i+1 < argc) password = argv[++i];
}
if (input_bmp.empty() || output_bin.empty()) {
throw std::runtime_error("❌ Paramètres -i -o obligatoires pour decode");
}
auto decrypted = stego::decode(input_bmp, password);
std::ofstream out(output_bin, std::ios::binary);
if (!out) throw std::runtime_error("Impossible d'écrire " + output_bin);
out.write(reinterpret_cast<const char*>(decrypted.data()), static_cast<std::streamsize>(decrypted.size()));
std::cout << "💾 Fichier décodé: " << output_bin << " (" << decrypted.size() << " bytes)\n";
}
else {
throw std::runtime_error("❌ Commande inconnue. Utilisez 'encode' ou 'decode'");
}
std::cout << "✓ Opération terminée avec succès!\n";
}
catch (const std::exception& e) {
std::cerr << "\n✗ Erreur: " << e.what() << "\n";
return 1;
}
return 0;
}
Here’s the code to generate compatible BMP images — I used the test_512x512 image and it works.
C++:
/*
MIT License
Copyright (c) 2025 CoTon_TiGe_MoUaRf
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdint>
#include <cstring>
#pragma pack(push, 1)
struct BMP_FILEHEADER {
uint16_t bfType; // 0x4D42 = 'BM'
uint32_t bfSize; // Taille totale fichier
uint16_t bfReserved1;
uint16_t bfReserved2;
uint32_t bfOffBits; // Offset données pixels (54)
};
struct BMP_INFOHEADER {
uint32_t biSize; // 40
int32_t biWidth; // Largeur
int32_t biHeight; // Hauteur (>0 = bottom-up)
uint16_t biPlanes; // 1
uint16_t biBitCount; // 24
uint32_t biCompression; // 0 = AUCUNE
uint32_t biSizeImage; // Taille pixels
int32_t biXPelsPerMeter; // 0
int32_t biYPelsPerMeter; // 0
uint32_t biClrUsed; // 0
uint32_t biClrImportant; // 0
};
#pragma pack(pop)
void createPerfectBMP(const char* filename, int width, int height, uint8_t r=200, uint8_t g=220, uint8_t b=255) {
// Calculer tailles
size_t rowSize = ((width * 3 + 3) / 4) * 4; // Padding 4 bytes
size_t imageSize = rowSize * height;
size_t fileSize = 54 + imageSize;
// Headers
BMP_FILEHEADER fileHdr = {};
fileHdr.bfType = 0x4D42; // 'BM'
fileHdr.bfSize = static_cast<uint32_t>(fileSize);
fileHdr.bfReserved1 = 0;
fileHdr.bfReserved2 = 0;
fileHdr.bfOffBits = 54;
BMP_INFOHEADER infoHdr = {};
infoHdr.biSize = 40;
infoHdr.biWidth = width;
infoHdr.biHeight = height; // >0 = bottom-up
infoHdr.biPlanes = 1;
infoHdr.biBitCount = 24; // CRITIQUE
infoHdr.biCompression = 0; // AUCUNE compression
infoHdr.biSizeImage = static_cast<uint32_t>(imageSize);
infoHdr.biXPelsPerMeter = 0;
infoHdr.biYPelsPerMeter = 0;
infoHdr.biClrUsed = 0;
infoHdr.biClrImportant = 0;
// Pixels BGR (bottom-up + padding)
std::vector<uint8_t> pixels(imageSize, 0);
for (int y = 0; y < height; ++y) {
size_t rowOffset = static_cast<size_t>(height - 1 - y) * rowSize;
for (int x = 0; x < width; ++x) {
size_t pxOffset = rowOffset + x * 3;
pixels[pxOffset + 0] = b; // Bleu
pixels[pxOffset + 1] = g; // Vert
pixels[pxOffset + 2] = r; // Rouge
}
}
// Écriture
std::ofstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "❌ Erreur création " << filename << std::endl;
return;
}
file.write(reinterpret_cast<const char*>(&fileHdr), sizeof(fileHdr));
file.write(reinterpret_cast<const char*>(&infoHdr), sizeof(infoHdr));
file.write(reinterpret_cast<const char*>(pixels.data()), imageSize);
std::cout << "✅ " << filename << " créée !" << std::endl;
std::cout << " 📐 " << width << "x" << height << " px" << std::endl;
std::cout << " 💾 " << (fileSize/1024) << " Ko" << std::endl;
std::cout << " 🎨 Couleur RGB(" << (int)r << "," << (int)g << "," << (int)b << ")" << std::endl;
std::cout << " 🔗 Compatible stéganographie !" << std::endl;
}
int main() {
// Créer plusieurs tailles pour tests
createPerfectBMP("test_100x100.bmp", 100, 100, 255, 0, 0); // Rouge
createPerfectBMP("test_512x512.bmp", 512, 512, 0, 255, 0); // Vert
createPerfectBMP("test_1024x768.bmp", 1024, 768, 0, 0, 255); // Bleu
std::cout << "\n🎉 3 images parfaites créées !" << std::endl;
std::cout << "Testez avec : stego.exe encode -i test_512x512.bmp -e secret.txt -o stego.bmp -p test" << std::endl;
return 0;
}
Finally, here’s the code to extract the hidden data from the output image to verify that the message is encrypted in the image.
C++:
// main_verify.cpp - VÉRIFICATION Steganography BMP (header + payload CHIFFRÉ SEUL)
/*
MIT License
Copyright (c) 2025 CoTon_TiGe_MoUaRf
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <cstring>
#include <cstdint>
#include <iomanip>
#include "csprng.hpp"
// Structures BMP avec packing strict
#pragma pack(push, 1)
struct BMP_FILEHEADER {
uint16_t bfType;
uint32_t bfSize;
uint16_t bfReserved1;
uint16_t bfReserved2;
uint32_t bfOffBits;
};
struct BMP_INFOHEADER {
uint32_t biSize;
int32_t biWidth;
int32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
int32_t biXPelsPerMeter;
int32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
};
#pragma pack(pop)
// En-tête stéganographie (32 bytes)
#pragma pack(push, 1)
struct StegoHeader {
char signature[4]; // "STG1"
uint8_t version; // 1
uint8_t flags; // bit0: chiffrement, bit1: permutation
uint32_t orig_size; // taille payload original
uint32_t enc_size; // taille payload chiffré
uint8_t salt[16]; // salt pour KDF
uint8_t iv[12]; // IV pour AES-CTR (96 bits)
};
#pragma pack(pop)
namespace verify {
constexpr int BITS_PER_CHANNEL = 1;
constexpr size_t HEADER_SIZE = sizeof(StegoHeader);
csprng::CSPRNG global_rng;
// Lire BMP (24/32-bit, compression 0/3, top/bottom-up)
std::vector<uint8_t> read_bmp_pixels(const std::string& filename, int& width, int& height,
size_t& row_padding, int& bytes_per_pixel,
BMP_FILEHEADER& file_hdr, BMP_INFOHEADER& info_hdr) {
std::ifstream file(filename, std::ios::binary);
if (!file) throw std::runtime_error("Impossible d'ouvrir l'image");
file.read(reinterpret_cast<char*>(&file_hdr), sizeof(BMP_FILEHEADER));
if (file.gcount() != sizeof(BMP_FILEHEADER)) {
throw std::runtime_error("Lecture file header BMP échouée");
}
if (file_hdr.bfType != 0x4D42) throw std::runtime_error("Pas un fichier BMP valide");
uint32_t biSize;
file.read(reinterpret_cast<char*>(&biSize), sizeof(uint32_t));
info_hdr.biSize = biSize;
if (biSize < 40) throw std::runtime_error("Header BMP trop petit");
file.read(reinterpret_cast<char*>(&info_hdr) + 4, sizeof(BMP_INFOHEADER) - 4);
if (info_hdr.biPlanes != 1) throw std::runtime_error("Seulement 1 plan supporté");
if (info_hdr.biBitCount != 24 && info_hdr.biBitCount != 32)
throw std::runtime_error("Seulement 24/32 bits supportés");
if (info_hdr.biCompression == 3) {
uint32_t dummy[3];
file.read(reinterpret_cast<char*>(dummy), 12);
}
width = info_hdr.biWidth;
height = std::abs(info_hdr.biHeight);
bytes_per_pixel = info_hdr.biBitCount / 8;
row_padding = (4u - (static_cast<size_t>(width) * bytes_per_pixel % 4u)) % 4u;
bool top_down = (info_hdr.biHeight < 0);
size_t pixel_size = static_cast<size_t>(width) * height * bytes_per_pixel;
std::vector<uint8_t> pixels(pixel_size);
file.seekg(file_hdr.bfOffBits, std::ios::beg);
int y_start = top_down ? 0 : height - 1;
int y_end = top_down ? height : -1;
int y_step = top_down ? 1 : -1;
for (int y = y_start; y != y_end; y += y_step) {
size_t row_offset = static_cast<size_t>(y) * width * bytes_per_pixel;
file.read(reinterpret_cast<char*>(pixels.data() + row_offset),
static_cast<std::streamsize>(width * bytes_per_pixel));
if (row_padding > 0) file.ignore(static_cast<std::streamsize>(row_padding));
}
std::cout << "📐 Image: " << width << "x" << height << " (" << bytes_per_pixel*8 << "-bit)\n";
return pixels;
}
uint8_t get_bit(uint8_t byte, int pos) { return (byte >> pos) & 1u; }
std::vector<size_t> generate_pixel_order(int width, int height, const uint8_t salt[16]) {
csprng::CSPRNG local_rng;
local_rng.reseed_manual(salt, 16);
size_t total_pixels = static_cast<size_t>(width) * height;
std::vector<size_t> order(total_pixels);
for (size_t i = 0; i < total_pixels; i++) order[i] = i;
for (size_t i = total_pixels; i > 1; ) {
size_t j = static_cast<size_t>(local_rng() % i);
std::swap(order[--i], order[j]);
}
return order;
}
// EXTRAIRE header + payload CHIFFRÉ UNIQUEMENT
void extract_hidden_data(const std::string& input_bmp) {
int width, height, bytes_per_pixel;
size_t row_padding;
BMP_FILEHEADER file_hdr;
BMP_INFOHEADER info_hdr;
std::vector<uint8_t> pixels = read_bmp_pixels(input_bmp, width, height, row_padding, bytes_per_pixel, file_hdr, info_hdr);
int channels_in_use = (bytes_per_pixel == 4) ? 3 : bytes_per_pixel;
std::cout << "🔍 EXTRACTION DONNÉES CACHÉES...\n\n";
// === HEADER (256 bits = 32 bytes) ===
std::vector<uint8_t> header_bytes(HEADER_SIZE, 0);
size_t bit_pixel_count = 0;
for (size_t byte_idx = 0; byte_idx < HEADER_SIZE; ++byte_idx) {
for (int bit_pos = 7; bit_pos >= 0; --bit_pos) {
size_t px = bit_pixel_count / channels_in_use;
int channel = static_cast<int>(bit_pixel_count % channels_in_use);
size_t pixel_offset = px * bytes_per_pixel + channel;
uint8_t bit = (pixel_offset < pixels.size()) ? get_bit(pixels[pixel_offset], 0) : 0;
header_bytes[byte_idx] |= (bit << bit_pos);
++bit_pixel_count;
}
}
StegoHeader header;
std::memcpy(&header, header_bytes.data(), HEADER_SIZE);
// === AFFICHAGE HEADER ===
std::cout << "📋 === HEADER TROUVÉ ===\n";
std::cout << "Signature: '" << std::string(header.signature, 4) << "' ✅\n";
std::cout << "Version: " << (int)header.version << "\n";
std::cout << "Flags: 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)header.flags << std::dec << "\n";
std::cout << " → Chiffrement: " << ((header.flags & 1u) ? "🔒 OUI" : "NON") << "\n";
std::cout << " → Permutation: " << ((header.flags & 2u) ? "🔀 OUI" : "NON") << "\n";
std::cout << "Taille originale: " << header.orig_size << " bytes\n";
std::cout << "Taille chiffrée: " << header.enc_size << " bytes\n";
std::cout << "Salt: ";
for(int i = 0; i < 16; i++) printf("%02x", header.salt[i]);
printf("\n\n");
if (std::strncmp(reinterpret_cast<char*>(header.signature), "STG1", 4) != 0) {
throw std::runtime_error("❌ PAS de stéganographie détectée");
}
bool use_perm = (header.flags & 2u) != 0;
std::vector<size_t> pixel_order;
size_t bit_pixel_count_payload = HEADER_SIZE * 8;
if (use_perm) {
pixel_order = generate_pixel_order(width, height, header.salt);
}
// === PAYLOAD CHIFFRÉ ===
std::cout << "🔒 === SECRET CHIFFRÉ EXTRAIT ===\n";
size_t enc_payload_size = static_cast<size_t>(header.enc_size);
std::vector<uint8_t> encrypted_payload(enc_payload_size, 0);
for (size_t byte_idx = 0; byte_idx < enc_payload_size; ++byte_idx) {
for (int bit_pos = 7; bit_pos >= 0; --bit_pos) {
size_t px = use_perm ? pixel_order[bit_pixel_count_payload / channels_in_use] : (bit_pixel_count_payload / channels_in_use);
int channel = static_cast<int>(bit_pixel_count_payload % channels_in_use);
size_t pixel_offset = px * bytes_per_pixel + channel;
uint8_t bit = (pixel_offset < pixels.size()) ? get_bit(pixels[pixel_offset], 0) : 0;
encrypted_payload[byte_idx] |= (bit << bit_pos);
++bit_pixel_count_payload;
}
}
// === AFFICHAGE IV + PAYLOAD CHIFFRÉ ===
std::cout << "📍 IV (16 bytes): ";
for(int i = 0; i < 16; i++) printf("%02x ", encrypted_payload[i]);
printf("\n");
std::cout << "🔐 Payload CHIFFRÉ (début): ";
for(int i = 16; i < 16+32 && i < encrypted_payload.size(); i++) printf("%02x ", encrypted_payload[i]);
printf("\n");
if (encrypted_payload.size() > 48) {
std::cout << "🔐 Payload CHIFFRÉ (fin): ";
for(int i = encrypted_payload.size()-16; i < encrypted_payload.size(); i++) printf("%02x ", encrypted_payload[i]);
printf("\n");
}
std::cout << "\n📊 STATISTIQUES:\n";
std::cout << " Bits header: " << HEADER_SIZE * 8 << "\n";
std::cout << " Bits payload: " << enc_payload_size * 8 << "\n";
std::cout << " TOTAL bits: " << (HEADER_SIZE + enc_payload_size) * 8 << "\n";
std::cout << " Pixels utilisés: " << (bit_pixel_count_payload / channels_in_use) << "/"
<< (width * height) << "\n\n";
std::cout << "🎉 CHIFFREMENT CONFIRMÉ! Le secret est bien caché et chiffré.\n";
}
}
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "\n=== VÉRIFICATEUR Steganography (Header + Secret Chiffré) ===\n";
std::cerr << "Usage: ./verify image_stego.bmp\n\n";
std::cerr << "Exemple: ./verify output.bmp\n";
return 1;
}
std::string input_bmp = argv[1];
try {
verify::extract_hidden_data(input_bmp);
}
catch (const std::exception& e) {
std::cerr << "\n✗ Erreur: " << e.what() << "\n";
return 1;
}
return 0;
}