Новости МирТесен

voinkiber@bk.ru


Стеганография

Поделиться:

В этой статье я хочу рассказать вам о стеганографии в звуковых файлах, а также показать код самодельной программы, которая умеет прятать данные в файл формата wav.

Что такое стеганография?

Стеганография — это наука о том, как спрятать сообщение внутри другого сообщения. Суть состоит в том, чтобы незначительно изменить несущий файл так, чтобы изменения были незаметны внешне, но при этом оставалась возможность извлечь скрытые данные.

Тут уместно сравнение с аналоговым цветным телевидением. Видеосигнал описывает темные и светлые участки экрана. Сигналы цветности подмешиваются в видеосигнал и занимают в его спектре небольшую полосу в районе 4.5 мегагерц. На экране старого черно-белого телевизора сигналы цветности выглядят как легкая рябь. В цветном телевизоре эти сигналы бережно отделяются, усиливаются и раскрашивают картинку.

Как правило, скрытое сообщение (payload) по объему в несколько раз меньше, чем сообщение-контейнер (carrier). Посмотрите на статью в википедии en.wikipedia.org/wiki/Steganography. Найдите в статье картинку с деревом на фоне неба. В этой картинке формат пикселя rgb24, то есть, три цветовых канала по восемь бит в каждом (интенсивность каждого канала может принимать значение от 0 до 255). Если изменять младшие биты каждого канала, то цвет пикселя будет незначительно меняться, например синий компонент был 10011100 (156), а стал 10011111 (159), пиксель стал чуть ярче, но в целом картинка осталась прежней. Изменяя младшие биты мы можем заставить картинку хранить скрытое сообщение. Теперь посмотрите на картинку, которая была восстановнена из картинки с деревом. Обратите внимание на глубину цвета, используются пиксели только нескольких цветов. Вес файла картинки также заметно ниже, чем картинки-контейнера.

Разумеется, перед тем как встроить данные, их нужно зашифровать. При этом желательно, чтобы зашифрованные данные были похожи на цифровой шум, чтобы они никак не выдавали свое присутствие и чтобы по ним нельзя было определить, каким инструментом они зашифрованы.

Не стоит путать стеганографию и криптографию. Криптография не позволяет прочитать скрытое сообщение без ключа, однако само зашифрованное сообщение привлекает к себе внимание. Хорошая стеганография прячет сам факт наличия секретного сообщения. В таком случае стегоаналитик не может точно установить, является ли этот цифровой шум просто шумом, или же этот шум был специально изменен, и из него можно, зная ключ, восстановить сообщение.

Большие возможности стеганографии открываются в звуковых файлах. Постепенно набирают популярность форматы lossless (сжатие без потерь), которые часто встречаются на трекерах и в личных архивах. Основным форматом для работы со звуком является WAV, который содержит звуковые семплы в чистом виде, в последствии этот файл можно сжать кодеком flac, а затем при надобности восстановить обратно в wav.

Что такое цифровой звук?

Аналоговый звуковой сигнал можно загнать в компьютер, но для этого его надо сначала оцифровать. Сигнал поступает на специальный аналого-цифровой конвертер (ADC), этот конвертер производит равномерные переодические замеры уровня входного сигнала и выдает на выходе последовательность цифр, чем больше напряжение, тем больше цифры. Цифровой сигнал можно преобразовать обратно в аналоговый, для этого цифры подают на цифро-аналоговый конвертер (DAC), на выходе которого появляется меняющееся напряжение, которое можно усилить и прослушать на громкоговорителе. Такой способ записи звука называется LPCM (Linear Pulse Code Modulation) или просто PCM.

Важный параметр PCM — ширина семпла в битах. От него зависит то, насколько тихие звуки может передавать звуковой канал, и при этом эти звуки не утонут в шуме квантования. В звуковом компакт-диске используется ширина 16 бит на каждый из двух каналов, при этом сигнал может принимать значения от -32768 до +32767. Профессиональная аппаратура способна работать с шириной 24 бита (от -8388608 до +8388607). 8-битное аудио сейчас почти нигде не используется из за очень высокого порога шумов.

Второй важный параметр — это частота семплов, для компакт-диска стандартная скорость — 44100 семплов в секунду. Для звуковой дорожки из видео файла стандарт — 48000 семплов в секунду. Также могут быть другие частоты, например на торрентсру часто выкладывают пластинки в высоком разрешении 24/96, 24 бита, частота семплов — 96000 герц.

Как устроен wav файл?

Наглядная спецификация: soundfile.sapp.org/doc/WaveFormat/

Wav файл является разновидностью RIFF контейнера. Файл состоит из чанков — небольших частей. У каждого чанка первые четыре байта обозначают тип чанка, следующие четыре байта содержат цифру, обозначающую длину тела чанка в байтах, затем идет тело чанка. Чанки идут по очереди друг за другом. Внутри тела можно хранить всякие данные.

Все цифры, которые указывают длину чанков и параметры потока, записаны в формате little-endian (младшим концом вперед), их можно копировать напрямую в регистры процессора при условии, что программа выполняется на little-endian машине.
Существует версия файла в формате big-endian, в таком случае в начале файла сигнатура будет не RIFF а RIFX.

Звуковые семплы находятся в теле чанка «data» в виде блоков, в каждом блоке несколько семплов, по одному для каждого канала. Семплы представляют собой знаковые числа (two’s complement) либо 16 либо 24 бита длиной, либо беззнаковые 8-битные. 16-битные семплы принимают значения от -32768 до 32767.

Wav файл содержит чанк «fmt «, который хранит сведения о потоке: количество каналов, ширину семпла, скорость воспроизведения потока.

Звук также можно хранить в виде сырых данных (raw format). Попробуйте поэкспериментировать на линуксе с программой sox:

sox some_wav_file.wav -t raw some_raw_file

Теперь файл some_raw_file содержит сырые звуковые данные, вы можете посмотреть его в шестнадцатиричном формате с помощью команды xxd. Чтобы воспроизвести файл, нужно правильно указать частоту семплов, ширину семплов, количество каналов, тип числа (знаковое или беззнаковое), и порядок байтов (little-endian или big-endian):

play -t raw -r 44100 -b 16 -c 2 -e signed-integer some_raw_file

Какие виды звуковой стеганографии бывают?

Самый простой метод — это метод наименее значимого бита (LSB Last Significant Bit), мы уже рассматривали его в примере с картинкой выше. Мы можем менять самый младший бит в каждом семпле звукового файла. Такой способ обеспечивает максимальную емкость, однако, есть способ обнаружить такую стеганографию по изменению среднего значения (BIAS). После встраивания зашифрованного потока это значение будет в районе 0.5, хотя в исходном файле оно могло быть другим.

Более интересный способ — проверка четности регионов: аудиопоток разбивается на регионы, например, по 100 блоков в регионе, в каждом блоке по два семпла (для стерео). Затем проверяется четность всего региона. Результат проверки и есть один бит спрятанных данных. Если изменить один из младших битов в регионе, то меняется четность всего региона. Этот способ используется в программе ниже.

Данные, спрятанные этими способами, сохраняются при сжатии в flac, но погибают при перезаписи на аналоговый носитель или сжатии в mp3. Существуют способы акустической стеганографии, которые могут жить на аналоговых носителях, к ним относятся: кодирование изменением фазы, эхо-кодирование. Такие способы будут оставлять легкообнаружимые артефакты.

Стеганография в MP3 файлах?

Не рекомендую. Все известные способы встраивания данных в mp3 либо используют служебные поля, либо искажают звуковой поток, отчего такой файл будет отличаться от обычного mp3, и наверняка уже существуют способы автоматического обнаружения таких файлов. Это же касается других lossy-форматов и JPEG картинок.

Код

Программа получилась немного сложная и запутанная. Три файла:

stealth.c — код основной части программы:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <errno.h>
#include <endian.h>

#include "sha1.c"
#include "md5.c"

#define MIN_SOUND_LEV 30 // Минимальный уровень сигнала (должен быть кратен двойке)

int err, i, odd;
long int h;

char *program_name;

enum {ENCODE, DECODE, DRAIN} program_mode;

FILE *logfile = NULL;
FILE *microscope_file = NULL;
FILE *wav_in = NULL;
FILE *wav_out = NULL;
FILE *hidefile_in = NULL;
FILE *hidefile_out = NULL;
//FILE *noise_debug_file = NULL;

char *wav_in_filename = NULL;
char *wav_out_filename = NULL;
char *hidden_filename = NULL;

uint32_t bytes_count = 0; // Счетчик байтов от начала файла
long int blocks_count = 0; // Счетчик блоков от начала звукового потока

unsigned char hidden_bit = 0;
char hidden_bit_number = 0;
char hidden_byte = 0;
char hidden_crypted_byte = 0;
long int hidden_byte_count = 0;
long int hidden_file_out_size = 0;
int crypto_enabled = 1;

SHA1_CTX sha1context;
uint8_t sha1result[20];
MD5_CTX md5context;
uint8_t md5result[16];
uint64_t sha1counter = 0;
char *hidden_password = NULL; // парольная фраза для PRNG
uint8_t random_byte_one = 0;
uint8_t random_byte_two = 0;
char random_bit = 0;

char riff_chunk_name[5] = "    ";
uint32_t riff_chunk_size = 0;
char riff_type[5] = "    ";
uint8_t *wave_fmt = NULL;
uint16_t *wave_audio_format = NULL;
uint16_t *wave_channels = NULL;
uint32_t *wave_sample_rate = NULL;
uint16_t *wave_block_align = NULL;
uint16_t *wave_bits_per_sample = NULL;

char buffer_char;
uint8_t *sound_buffer = NULL; // буфер для блоков
int sound_buffer_length = 40; // длина буфера в блоках
int sound_buffer_size = 0; // размер выделенного буфера в байтах
int32_t **buffer_samples = NULL; // буферы для семплов, по одному на каждый канал
long int buffer_samples_size = 0;
int sound_buffer_bytes_read = 0;
int sound_buffer_blocks_read = 0;
uint8_t *last_byte_p = NULL;

long int read_offset = 73; // начальное смещение в блоках

long int microscope_offset = 0;
int microscope_display_blocks = 0;
int microscope_on = 0;

int end_of_hidden_file = 0;

long int good_regions = 0;
long int bad_regions = 0;
long int bias_one = 0;
long int bias_zero = 0;
long int alt_one = 0;
long int alt_zero = 0;

void process_region();

void usage (void) {
  fprintf(stderr, "Wav steganography tool.\n"
  "Please read manual carefully.\nUsage:\n"
  "%s command [options]\n"
  "commands are: encode, decode, drain\n"
  "options are:\n-wavin=in.wav\n-wavout=out.wav\n-hiddenin=secret.bin\n"
  "-hiddenout=secret.bin\n-offset=2000000\n-readsize=500\n-region=40\n"
  "-microscope usage\n-disable-crypto\n-pass=xyzzy\n",
  program_name);
  exit(-1);
};

void microscope_usage (void) {
  fprintf(stderr, "Microscope usage:\n"
  "-microscope offset_in_blocks length_of_printing_in_blocks\n"
  "\n"
  "Examples:\n"
  "%s encode -wavin=infile.wav -microscope 5000 200\n", program_name);
  exit(-1);
};

void malloc_error_check (void *p) {
  if (p == NULL) {
    fprintf(stderr, "%s: Ошибка: Память не выделяется\n", program_name);
    exit(-1);
  };
};

void fwrite_check (char *buff_name) {
  if (err != 1) {
    fprintf(stderr, "%s: Ошибка записи %s\n", program_name, buff_name);
    exit(-1);
  };
};

void error_stop (char *str) {
  fprintf(stderr, "%s: %s\n", program_name, str);
  exit(-1);
};

void fread_check (char *buff_name) {
  if (err != 1) {
    fprintf(stderr, "%s: Ошибка чтения %s\n", program_name, buff_name);
    exit(-1);
  };
};

void checkfile (FILE *f, char *str) {
  if (f == NULL) {
    perror(str);
    exit(-1);
  };
};

void print_summary (void) {
  fprintf(logfile, "%s: Хороших регионов %li, плохих %li\n", program_name, good_regions, bad_regions);
  if (program_mode == ENCODE) fprintf(logfile,
  "%s: BIAS до записи: единиц %li, нулей %li, после: %li, %li\n", program_name,
  bias_one, bias_zero, alt_one, alt_zero);
};

char get_random_byte (uint8_t *data_pointer, int data_size) {
  uint64_t sha1counter_big_endian = htobe64(sha1counter++);
  int hr;
  char random_byte;

  if (!hidden_password) error_stop("Ошибка, пароль не задан");
  SHA1Init(&sha1context);
  SHA1Update(&sha1context, (uint8_t *) hidden_password, strlen(hidden_password));
  SHA1Update(&sha1context, (uint8_t *) &sha1counter_big_endian, 8);
  SHA1Update(&sha1context, data_pointer, data_size);
  SHA1Final(sha1result, &sha1context);
  md5_init(&md5context);
  md5_update(&md5context, (unsigned char *) hidden_password, strlen(hidden_password));
  md5_update(&md5context, (unsigned char *) &sha1counter_big_endian, 8);
  md5_update(&md5context, data_pointer, data_size);
  md5_final(&md5context, md5result);
  random_byte = 0;
  for (hr=0; hr < 20; hr++) random_byte ^= sha1result[hr];
  for (hr=0; hr < 16; hr++) random_byte ^= md5result[hr];
  //fwrite(&random_byte, 1, 1, noise_debug_file);
  return random_byte;
};

void get_next_hidden_bit(void) {
  if (hidden_bit_number == 0) {
    err = fread(&hidden_byte, 1, 1, hidefile_in);
    if (err != 1) {
      if (feof(hidefile_in)) {
        fprintf(logfile, "%s: Успешно встроено %li байт\n", program_name, hidden_byte_count);
        print_summary();
        end_of_hidden_file = 1;
        return;
      } else error_stop("Ошибка чтения секретного потока входа");
    };
    hidden_byte_count++;
    // fprintf(microscope_file, "Байт для встраивания %02x\n", hidden_byte);
  };
  hidden_bit = (hidden_byte & (1 << hidden_bit_number)) ? 1 : 0;
  if (crypto_enabled && random_bit) hidden_bit = (hidden_bit) ? 0 : 1;
  hidden_bit_number++;
  if (hidden_bit_number > 7) hidden_bit_number = 0;
};

void hidden_bit_out(int bit) {
  if (crypto_enabled && random_bit) bit = (bit) ? 0 : 1;
  if (hidden_bit_number == 0) hidden_byte = 0;
  if (bit) hidden_byte |= 1 << hidden_bit_number;
  hidden_bit_number++;
  if (hidden_bit_number > 7) {
    hidden_bit_number = 0;
    err = fwrite(&hidden_byte, 1, 1, hidefile_out);
    fflush(hidefile_out);
    fwrite_check("hidden_byte");
    hidden_byte_count++;
    if (hidden_byte_count >= hidden_file_out_size) {
      fprintf(logfile, "%s: Успешно извлечено %li байт\n", program_name, hidden_byte_count);
      print_summary();
      end_of_hidden_file = 1;
      fclose(hidefile_out);
      if (!microscope_display_blocks && !wav_out) exit(0);
    };
  };
};

int wav_write (void *p, int a, int b, FILE *fp) {
  if (fp == NULL) {
    return 1;
  } else {
    return fwrite(p, a, b, fp);
  };
};

// эта функция возвращает 1 если уровень сигнала слишком низкий
int silence_check() {
  long int hu, huh;
  for (huh = 0; huh < *wave_channels; huh++) {
    for (hu = 2; hu < sound_buffer_blocks_read; hu++) {
      //if (microscope_on) fprintf(microscope_file, "%li %li \n", huh, (long int)buffer_samples[huh][hu]);
      if (-MIN_SOUND_LEV+1 < buffer_samples[huh][hu-2] && buffer_samples[huh][hu-2] < MIN_SOUND_LEV)
      if (-MIN_SOUND_LEV+1 < buffer_samples[huh][hu-1] && buffer_samples[huh][hu-1] < MIN_SOUND_LEV)
      if (-MIN_SOUND_LEV+1 < buffer_samples[huh][hu] && buffer_samples[huh][hu] < MIN_SOUND_LEV) return 1;
    };
  };
  
  return 0;
};


long int decode_sample (uint8_t *blockpointer, int channel) {
  // Здесь происходит преобразование семпла в 32-битный формат
  long int sample = 0;
  if (*wave_bits_per_sample == 24) {
    sample = 65536 * (*(int8_t*)(blockpointer+2+(channel * 3)));
    sample += 256 * (*(uint8_t*)(blockpointer+1+(channel * 3)));
    sample += *(uint8_t*)(blockpointer+(channel * 3));
  } else {
    sample = 256 * (*(int8_t*)(blockpointer+1+(channel * 2)));
    sample += *(uint8_t*)(blockpointer+(channel * 2));
  };
  return sample;
};

void analize_block (uint32_t show_address, uint32_t show_block, char *show_mode, long int block_number) {
  uint8_t *block_pointer = sound_buffer+((*wave_block_align)*block_number);
  if (block_number >= sound_buffer_blocks_read) error_stop("Ошибка логики");

  for (i = 0; i < *wave_channels; i++) {
    buffer_samples[i][block_number] = decode_sample(block_pointer, i);
    
    if (buffer_samples[i][block_number] & 1) odd = (odd) ? 0 : 1;
    //if (microscope_on) fprintf(microscope_file, (buffer_samples[i][block_number] & 1) ? " Нечетный " : " Четный   ");
  };

  if (microscope_on) {
    fprintf(microscope_file, "0x%08x %08li %s ", show_address, (unsigned long int)show_block,
    show_mode);
    fprintf(microscope_file, "%05li ", block_number);
    for (i = 0; i < *wave_block_align; i++) {
      fprintf(microscope_file, " %02x", *(block_pointer+i));
    };
    for (i = 0; i < *wave_channels; i++) {
      fprintf(microscope_file, "  %+08li  0x%08lx", (long int)buffer_samples[i][block_number], (long int)buffer_samples[i][block_number]);
    };
  
    fprintf(microscope_file, "\n");
    
  };
};

void print_chunk (void) {
  fprintf(logfile, "%s: Чанк %s - размер %li байт\n", program_name, riff_chunk_name, (long int) riff_chunk_size);
};

void read_chunk (void) {
  err = fread(riff_chunk_name, 1, 4, wav_in);
  if (err != 4) {
    if (feof(wav_in)) {
      fprintf(logfile, "%s: Входной звуковой файл закончился\n", program_name);
      if (!end_of_hidden_file) {
        fprintf(stderr, "%s: Внимание: Скрытый файл поместился не полностью, ", program_name);
        if (program_mode == ENCODE) fprintf(stderr, "встроено только %li байт\n", hidden_byte_count);
        if (program_mode == DECODE) fprintf(stderr, "прочитано только %li байт\n", hidden_byte_count);
        print_summary();
        exit(-2);
      };
      exit(0);
    };
    error_stop("Ошибка чтения riff_chunk_name");
  };
  err = wav_write(riff_chunk_name, 4, 1, wav_out);
  fwrite_check("riff_chunk_name");
  
  err = fread(&riff_chunk_size, 4, 1, wav_in);
  fread_check("riff_chunk_size");
  err = wav_write(&riff_chunk_size, 4, 1, wav_out);
  fwrite_check("riff_chunk_size");
  bytes_count += 8;
  print_chunk();
  if (riff_chunk_size & 1) error_stop("Ошибка: Размер чанка не четный!!1");
};

int main (int argc, char **argv) {

  //noise_debug_file = fopen("noise_debug_file.bin", "wb");

  logfile = stderr;
  microscope_file = stderr;
  wav_in = NULL;
  wav_out = NULL;
  hidefile_in = NULL;
  hidefile_out = NULL;
  
  program_name = argv[0];
  
  if (argc < 3) usage();
  if (strncmp(argv[1], "encode", 6) == 0) {
    program_mode = ENCODE;
    hidefile_in = stdin;
  } else if (strncmp(argv[1], "decode", 6) == 0) {
    program_mode = DECODE;
    hidefile_out = stdout;
    hidden_file_out_size = 20000;
  } else if (strncmp(argv[1], "drain", 5) == 0) {
    program_mode = DRAIN;
  } else usage();
  
  // парсим опции
  for (i = 2; i < argc; i++) {
    if (strncmp(argv[i], "-wavin=", 7) == 0) {
      wav_in_filename = argv[i]+7;
      if (wav_in_filename[0] == '-') {
        wav_in = stdin;
      } else {        
        wav_in = fopen(wav_in_filename, "rb");
        checkfile(wav_in, wav_in_filename);
      };
    } else if (strncmp(argv[i], "-disable-crypto", 15) == 0) {
      crypto_enabled = 0;
    } else if (strncmp(argv[i], "-hiddenin=", 10) == 0) {
      hidden_filename = argv[i]+10;
      if (hidden_filename[0] == '-') {
        hidefile_in = stdin;
      } else {
        hidefile_in = fopen(hidden_filename, "rb");
        checkfile(hidefile_in, hidden_filename);
      };
    } else if (strncmp(argv[i], "-wavout=", 8) == 0) {
      wav_out_filename = argv[i]+8;
      if (wav_out_filename[0] == '-') {
        wav_out = stdout;
      } else {        
        wav_out = fopen(wav_out_filename, "wb");
        checkfile(wav_in, wav_out_filename);
      };
    } else if (strncmp(argv[i], "-hiddenout=", 11) == 0) {
      if (program_mode == ENCODE) error_stop("Ошибка: Команда encode не поддерживает опцию -hiddenout");
      hidden_filename = argv[i]+11;
      if (hidden_filename[0] == '-') {
        hidefile_out = stdout;
      } else {        
        hidefile_out = fopen(hidden_filename, "wb");
        checkfile(hidefile_out, hidden_filename);
      };
    } else if (strncmp(argv[i], "-offset=", 8) == 0) {
      read_offset = strtol(argv[i]+8, NULL, 10);
    } else if (strncmp(argv[i], "-readsize=", 10) == 0) {
      hidden_file_out_size = strtol(argv[i]+10, NULL, 10);
    } else if (strncmp(argv[i], "-region=", 8) == 0) {
      sound_buffer_length = strtol(argv[i]+8, NULL, 10);
      if (sound_buffer_length < 3) error_stop("Ошибка: Минимально возможный размер региона - 3");
    } else if (strncmp(argv[i], "-pass=", 6) == 0) {
      hidden_password = argv[i]+6;
    } else if (strncmp(argv[i], "-microscope", 11) == 0) {
      if (i+2 < argc) {
        microscope_file = stdout;
        microscope_offset = strtol(argv[i+1], NULL, 10);
        microscope_display_blocks = strtol(argv[i+2], NULL, 10);
        i += 2;
      } else microscope_usage();
    } else {
      fprintf(stderr, "%s: Ошибка: незнакомая опция %s\n", program_name, argv[i]);
      exit(-1);
    };
  };
  
  if (wav_in == NULL) error_stop("Ошибка: Входной аудиофайл не задан");
  if (wav_in == stdin) {
    fprintf(logfile, "%s: Беру WAV файл из стандартного потока входа\n", program_name);
  } else fprintf(logfile, "%s: wavin = %s\n", program_name, wav_in_filename);
  
  switch (program_mode) {
    case ENCODE:
    if (hidefile_in == stdin) {
      fprintf(logfile, "%s: Беру секретный файл из стандартного потока входа\n", program_name);
    } else fprintf(logfile, "%s: hiddenin = %s\n", program_name, hidden_filename);
    if (wav_out == NULL) fprintf(logfile, "%s: Выходной аудиофайл не задан, симулирую\n", program_name);
    break;
    case DECODE:
    break;
    case DRAIN:
    break;
  };

  if (microscope_display_blocks != 0 && wav_out == stdout) wav_out = NULL;

  if (wav_in == stdin && hidefile_in == stdin) error_stop("Ошибка: неправильно заданы входы");

  fprintf(logfile, "%s: смещение = %li блоков\n", program_name, read_offset);
  fprintf(logfile, "%s: размер региона = %i блоков\n", program_name, sound_buffer_length);
  fprintf(logfile, "%s: пароль = \"%s\"\n", program_name, hidden_password);
  if (!crypto_enabled) fprintf(logfile, "%s: Внимание, шифрование выключено\n", program_name);

  read_chunk(); // RIFF
  if (strncmp(riff_chunk_name, "RIFX", 4) == 0)
    error_stop("Ошибка: Вы даете звуковой поток в формате big-endian\n"
    "Эта программа без переделки поддерживает только little-endian");

  if (strncmp(riff_chunk_name, "RIFF", 4) != 0)
    error_stop("Ошибка: Формат звукового файла не тот, должен быть RIFF WAVE");
  
  err = fread(riff_type, 4, 1, wav_in);
  fread_check("riff_type");
  fprintf(stderr, "%s: Тип RIFF - %s\n", program_name, riff_type);
  if (strncmp(riff_type, "WAVE", 4) != 0) error_stop("Ошибка: тип RIFF не тот, должен быть WAVE");
  err = wav_write(riff_type, 4, 1, wav_out);
  fwrite_check("riff_type");
  bytes_count += 4;
  
  while(1) {
    read_chunk();
    
    if (strncmp(riff_chunk_name, "fmt\x20", 4) == 0) {
      if (wave_fmt == NULL) {
        wave_fmt = malloc(riff_chunk_size);
        malloc_error_check(wave_fmt);
      } else error_stop("Ошибка: порядок чанков неправильный.");
      err = fread(wave_fmt, riff_chunk_size, 1, wav_in);
      fread_check("wave_fmt");
      err = wav_write(wave_fmt, riff_chunk_size, 1, wav_out);
      fwrite_check("wave_fmt");
      bytes_count += riff_chunk_size;
      
      wave_audio_format = (uint16_t*)wave_fmt;
      fprintf(logfile, "%s: Формат потока = %i\n", program_name, *wave_audio_format);
      if (*wave_audio_format != 1) error_stop("Ошибка: звуковой поток на входе закодирован");

      wave_channels = (uint16_t*)(wave_fmt+2);
      fprintf(logfile, "%s: Количество каналов = %i\n", program_name, *wave_channels);
      wave_sample_rate = (uint32_t*)(wave_fmt+4);
      fprintf(logfile, "%s: Частота семплов = %li\n", program_name, (long int) *wave_sample_rate);
      wave_block_align = (uint16_t*)(wave_fmt+12);
      fprintf(logfile, "%s: Выравнивание блоков = %i байт\n", program_name, *wave_block_align);
      wave_bits_per_sample = (uint16_t*)(wave_fmt+14);
      fprintf(logfile, "%s: Ширина семпла = %i бит\n", program_name, *wave_bits_per_sample);

      if (((*wave_bits_per_sample) != 16) && ((*wave_bits_per_sample) != 24)) error_stop("Ошибка: программа"
      " поддерживает ширину семпла либо 16 либо 24");

      if ((*wave_bits_per_sample) * (*wave_channels) != (*wave_block_align) * 8) 
        error_stop("Ошибка: ширина семпла не кратна восьми, расположение каналов непонятно.");

      sound_buffer_size = (*wave_block_align) * sound_buffer_length;
      sound_buffer = malloc(sound_buffer_size);
      malloc_error_check(sound_buffer);
      fprintf(logfile, "%s: Debugging: выделена память для буфера - %i байт, %i блоков \n",
      program_name, sound_buffer_size, sound_buffer_length);

      buffer_samples = malloc(sizeof(void*) * (*wave_channels));
      malloc_error_check(buffer_samples);

      buffer_samples_size = sound_buffer_length * sizeof(uint32_t);
      for (i = 0; i < *wave_channels; i++) {
        buffer_samples[i] = malloc(buffer_samples_size);
        malloc_error_check(buffer_samples[i]);
        fprintf(logfile, "%s: Debugging: выделена память для 32-семплов - %li байт, канал №%i\n",
        program_name, buffer_samples_size, i);
      };

      //fprintf(logfile, "%s: Debugging: fmt прочитан\n", program_name);

    } else if (strncmp(riff_chunk_name, "data", 4) == 0) {
      
      if (wave_bits_per_sample && *wave_bits_per_sample) {
        while (riff_chunk_size > 0) {
          if (microscope_display_blocks != 0) {
            if (blocks_count >= microscope_offset) microscope_on = 1;
          };
          if (riff_chunk_size < (*wave_block_align)) {
            sound_buffer_bytes_read = riff_chunk_size;
            sound_buffer_blocks_read = 0;
            err = fread(sound_buffer, sound_buffer_bytes_read, 1, wav_in);
            fread_check("выравнивающего зазора в конце чанка");
            fprintf(logfile, "%s: В конце найден выравнивающий зазор - %i байт\n", program_name, riff_chunk_size);
          } else if (end_of_hidden_file || read_offset) {
            sound_buffer_bytes_read = *wave_block_align; // прочитать один блок
            sound_buffer_blocks_read = 1;
            err = fread(sound_buffer, sound_buffer_bytes_read, 1, wav_in);
            fread_check((read_offset) ? "sound_buffer, режим offset" : "sound_buffer, режим done");
            analize_block(bytes_count, blocks_count,
            (read_offset) ? "offset" : "done", 0);
            if (read_offset > 0) {
              read_offset--;
            };
          } else {
            if (microscope_on) fprintf(microscope_file, "\n");
            sound_buffer_blocks_read = sound_buffer_length;
            if (sound_buffer_length * (*wave_block_align) > riff_chunk_size) {
              // Если чанк с данными подошел к концу, то буфер заполняется не полностью
              sound_buffer_blocks_read = riff_chunk_size / (*wave_block_align);
            };
            sound_buffer_bytes_read = (*wave_block_align) * sound_buffer_blocks_read;
            err = fread(sound_buffer, sound_buffer_bytes_read, 1, wav_in); // Заполнить буфер
            fread_check("sound_buffer");

            process_region();
            
          };
          
          err = wav_write(sound_buffer, sound_buffer_bytes_read, 1, wav_out);
          fwrite_check("sound_buffer");
          bytes_count += sound_buffer_bytes_read;
          blocks_count += sound_buffer_blocks_read;
          riff_chunk_size -= sound_buffer_bytes_read;
          if (microscope_on && (microscope_display_blocks > 0)) {
            microscope_display_blocks -= sound_buffer_blocks_read;
            if (microscope_display_blocks <= 0) {
              microscope_display_blocks = 0;
              microscope_on = 0;
              exit(0); // Микроскоп сработал, теперь стоп
            };
          };
        };
      } else error_stop("Ошибка: отсутствует fmt");

    } else {
      fprintf(logfile, "%s: Неопознаный чанк, копирую\n", program_name);
      for ( ; riff_chunk_size > 0; riff_chunk_size--) {
        err = fread(&buffer_char, 1, 1, wav_in);
        fread_check("buffer_char");
        err = wav_write(&buffer_char, 1, 1, wav_out);
        fwrite_check("buffer_char");
        bytes_count++;
      };
    };
  };
  return 0;
}

// Эта функция ищет подходящее место для внедрения, чтобы показание проверки тишины не изменилось
void find_place (void) {
  long int ht = 0;
  long int embed_hc = random_byte_two; // Этот счетчик будет уменьшаться
  int it = 0;

  long int onesample;
  while (1) {
    onesample = buffer_samples[it][ht];
    if (-MIN_SOUND_LEV+1 >= onesample || onesample >= MIN_SOUND_LEV) {
      h = ht;
      i = it;
      if (!embed_hc) break;
    };
    it++;
    if (it >= *wave_channels) {
      it = 0;
      ht++;
      if (ht >= sound_buffer_blocks_read -2) ht = 0;
    };
    embed_hc--;
    if (embed_hc < 0) embed_hc = 0;
  };
  if (microscope_on) fprintf(microscope_file, "подходящий блок - %li, канал - %i\n", h, i);
  last_byte_p = sound_buffer+(((*wave_block_align)*h)+(((*wave_bits_per_sample) / 8)*i));
};

void process_region() {
  odd = 0;
  for (h = 0; h < sound_buffer_blocks_read; h++) {
    analize_block(bytes_count+((*wave_block_align)*h), blocks_count+h, "work", h);
  };

  if (microscope_on) fprintf(microscope_file, "Нечетность региона = %i\n", odd); // 0 - четный, 1 - нечетный
  
  if (sound_buffer_blocks_read == sound_buffer_length) {
    if (!silence_check()) {
      good_regions++;

      // генерируем два псевдо-случайных байта, из первого получаем бит для кодирования
      // второй байт определяет позицию внедрения
      random_byte_one = get_random_byte(sound_buffer+((*wave_block_align)*sound_buffer_blocks_read-1),
      *wave_block_align);
      random_bit = 0;
      for (h = 0; h < 8; h++) random_bit ^= (random_byte_one >> h) & 1;
      //fprintf(stderr, "%x", random_bit);
      random_byte_two = get_random_byte(
        sound_buffer+((*wave_block_align)*sound_buffer_blocks_read-1),
        *wave_block_align
      );
      switch(program_mode) {
        case DECODE:
        hidden_bit_out(odd);
        break;
        case ENCODE:
        get_next_hidden_bit();
        if (end_of_hidden_file) break;
        if (microscope_on) fprintf(microscope_file, "Бит для встраивания = %i\n",  hidden_bit);
        if (hidden_bit != odd) {
          h = 0;
          i = 0;
          find_place();
          analize_block(bytes_count+((*wave_block_align)*h), blocks_count+h, "БЫЛО ", h);
          (*(last_byte_p) & 1) ? bias_one++ : bias_zero++;
          *(last_byte_p) ^= 1; // здесь происходит замена младшего бита
          (*(last_byte_p) & 1) ? alt_one++ : alt_zero++;
          analize_block(bytes_count+((*wave_block_align)*h), blocks_count+h, "СТАЛО", h);
        };
        if (silence_check()) error_stop("Ошибка тишины");
        break;
        case DRAIN:
        break;
      };
    } else {
      if (microscope_on) fprintf(microscope_file, "Регион содержит тишину\n");
      bad_regions++;
    };
  };

};

Следующие два файла — это хеш-функции, на их основе построен PRNG

sha1.c:
/*
SHA-1 in C
By Steve Reid <steve@edmweb.com>
100% Public Domain

Test Vectors (from FIPS PUB 180-1)
"abc"
  A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
  84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
A million repetitions of "a"
  34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
*/

/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */
/* #define SHA1HANDSOFF * Copies data before messing with it. */

#define SHA1HANDSOFF

#include <stdio.h>
#include <string.h>

/* for uint32_t */
#include <stdint.h>

//#include "sha1.h" Содержимое файла sha1.h, кроме прототипов функций, вставлено сюда
#ifndef SHA1_H
#define SHA1_H

#include "stdint.h"

typedef struct
{
    uint32_t state[5];
    uint32_t count[2];
    unsigned char buffer[64];
} SHA1_CTX;

#endif /* SHA1_H */


#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))

/* blk0() and blk() perform the initial expand. */
/* I got the idea of expanding during the round function from SSLeay */
#if BYTE_ORDER == LITTLE_ENDIAN
#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
    |(rol(block->l[i],8)&0x00FF00FF))
#elif BYTE_ORDER == BIG_ENDIAN
#define blk0(i) block->l[i]
#else
#error "Endianness not defined!"
#endif
#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
    ^block->l[(i+2)&15]^block->l[i&15],1))

/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);


/* Hash a single 512-bit block. This is the core of the algorithm. */

void SHA1Transform(
    uint32_t state[5],
    const unsigned char buffer[64]
)
{
    uint32_t a, b, c, d, e;

    typedef union
    {
        unsigned char c[64];
        uint32_t l[16];
    } CHAR64LONG16;

#ifdef SHA1HANDSOFF
    CHAR64LONG16 block[1];      /* use array to appear as a pointer */

    memcpy(block, buffer, 64);
#else
    /* The following had better never be used because it causes the
     * pointer-to-const buffer to be cast into a pointer to non-const.
     * And the result is written through.  I threw a "const" in, hoping
     * this will cause a diagnostic.
     */
    CHAR64LONG16 *block = (const CHAR64LONG16 *) buffer;
#endif
    /* Copy context->state[] to working vars */
    a = state[0];
    b = state[1];
    c = state[2];
    d = state[3];
    e = state[4];
    /* 4 rounds of 20 operations each. Loop unrolled. */
    R0(a, b, c, d, e, 0);
    R0(e, a, b, c, d, 1);
    R0(d, e, a, b, c, 2);
    R0(c, d, e, a, b, 3);
    R0(b, c, d, e, a, 4);
    R0(a, b, c, d, e, 5);
    R0(e, a, b, c, d, 6);
    R0(d, e, a, b, c, 7);
    R0(c, d, e, a, b, 8);
    R0(b, c, d, e, a, 9);
    R0(a, b, c, d, e, 10);
    R0(e, a, b, c, d, 11);
    R0(d, e, a, b, c, 12);
    R0(c, d, e, a, b, 13);
    R0(b, c, d, e, a, 14);
    R0(a, b, c, d, e, 15);
    R1(e, a, b, c, d, 16);
    R1(d, e, a, b, c, 17);
    R1(c, d, e, a, b, 18);
    R1(b, c, d, e, a, 19);
    R2(a, b, c, d, e, 20);
    R2(e, a, b, c, d, 21);
    R2(d, e, a, b, c, 22);
    R2(c, d, e, a, b, 23);
    R2(b, c, d, e, a, 24);
    R2(a, b, c, d, e, 25);
    R2(e, a, b, c, d, 26);
    R2(d, e, a, b, c, 27);
    R2(c, d, e, a, b, 28);
    R2(b, c, d, e, a, 29);
    R2(a, b, c, d, e, 30);
    R2(e, a, b, c, d, 31);
    R2(d, e, a, b, c, 32);
    R2(c, d, e, a, b, 33);
    R2(b, c, d, e, a, 34);
    R2(a, b, c, d, e, 35);
    R2(e, a, b, c, d, 36);
    R2(d, e, a, b, c, 37);
    R2(c, d, e, a, b, 38);
    R2(b, c, d, e, a, 39);
    R3(a, b, c, d, e, 40);
    R3(e, a, b, c, d, 41);
    R3(d, e, a, b, c, 42);
    R3(c, d, e, a, b, 43);
    R3(b, c, d, e, a, 44);
    R3(a, b, c, d, e, 45);
    R3(e, a, b, c, d, 46);
    R3(d, e, a, b, c, 47);
    R3(c, d, e, a, b, 48);
    R3(b, c, d, e, a, 49);
    R3(a, b, c, d, e, 50);
    R3(e, a, b, c, d, 51);
    R3(d, e, a, b, c, 52);
    R3(c, d, e, a, b, 53);
    R3(b, c, d, e, a, 54);
    R3(a, b, c, d, e, 55);
    R3(e, a, b, c, d, 56);
    R3(d, e, a, b, c, 57);
    R3(c, d, e, a, b, 58);
    R3(b, c, d, e, a, 59);
    R4(a, b, c, d, e, 60);
    R4(e, a, b, c, d, 61);
    R4(d, e, a, b, c, 62);
    R4(c, d, e, a, b, 63);
    R4(b, c, d, e, a, 64);
    R4(a, b, c, d, e, 65);
    R4(e, a, b, c, d, 66);
    R4(d, e, a, b, c, 67);
    R4(c, d, e, a, b, 68);
    R4(b, c, d, e, a, 69);
    R4(a, b, c, d, e, 70);
    R4(e, a, b, c, d, 71);
    R4(d, e, a, b, c, 72);
    R4(c, d, e, a, b, 73);
    R4(b, c, d, e, a, 74);
    R4(a, b, c, d, e, 75);
    R4(e, a, b, c, d, 76);
    R4(d, e, a, b, c, 77);
    R4(c, d, e, a, b, 78);
    R4(b, c, d, e, a, 79);
    /* Add the working vars back into context.state[] */
    state[0] += a;
    state[1] += b;
    state[2] += c;
    state[3] += d;
    state[4] += e;
    /* Wipe variables */
    a = b = c = d = e = 0;
#ifdef SHA1HANDSOFF
    memset(block, '\0', sizeof(block));
#endif
}


/* SHA1Init - Initialize new context */

void SHA1Init(
    SHA1_CTX * context
)
{
    /* SHA1 initialization constants */
    context->state[0] = 0x67452301;
    context->state[1] = 0xEFCDAB89;
    context->state[2] = 0x98BADCFE;
    context->state[3] = 0x10325476;
    context->state[4] = 0xC3D2E1F0;
    context->count[0] = context->count[1] = 0;
}


/* Run your data through this. */

void SHA1Update(
    SHA1_CTX * context,
    const unsigned char *data,
    uint32_t len
)
{
    uint32_t i;

    uint32_t j;

    j = context->count[0];
    if ((context->count[0] += len << 3) < j)
        context->count[1]++;
    context->count[1] += (len >> 29);
    j = (j >> 3) & 63;
    if ((j + len) > 63)
    {
        memcpy(&context->buffer[j], data, (i = 64 - j));
        SHA1Transform(context->state, context->buffer);
        for (; i + 63 < len; i += 64)
        {
            SHA1Transform(context->state, &data[i]);
        }
        j = 0;
    }
    else
        i = 0;
    memcpy(&context->buffer[j], &data[i], len - i);
}


/* Add padding and return the message digest. */

void SHA1Final(
    unsigned char digest[20],
    SHA1_CTX * context
)
{
    unsigned i;

    unsigned char finalcount[8];

    unsigned char c;

#if 0    /* untested "improvement" by DHR */
    /* Convert context->count to a sequence of bytes
     * in finalcount.  Second element first, but
     * big-endian order within element.
     * But we do it all backwards.
     */
    unsigned char *fcp = &finalcount[8];

    for (i = 0; i < 2; i++)
    {
        uint32_t t = context->count[i];

        int j;

        for (j = 0; j < 4; t >>= 8, j++)
            *--fcp = (unsigned char) t}
#else
    for (i = 0; i < 8; i++)
    {
        finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255);      /* Endian independent */
    }
#endif
    c = 0200;
    SHA1Update(context, &c, 1);
    while ((context->count[0] & 504) != 448)
    {
        c = 0000;
        SHA1Update(context, &c, 1);
    }
    SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
    for (i = 0; i < 20; i++)
    {
        digest[i] = (unsigned char)
            ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
    }
    /* Wipe variables */
    memset(context, '\0', sizeof(*context));
    memset(&finalcount, '\0', sizeof(finalcount));
}

void SHA1(
    char *hash_out,
    const char *str,
    int len)
{
    SHA1_CTX ctx;
    unsigned int ii;

    SHA1Init(&ctx);
    for (ii=0; ii<len; ii+=1)
        SHA1Update(&ctx, (const unsigned char*)str + ii, 1);
    SHA1Final((unsigned char *)hash_out, &ctx);
    hash_out[20] = '\0';
}

md5.c:
// Code by: B-Con (http://b-con.us) 
// Released under the GNU GPL 
// MD5 Hash Digest implementation (little endian byte order) 


// Bah, signed variables are for wimps 
#define uchar unsigned char 
#define uint unsigned int 

// DBL_INT_ADD treats two unsigned ints a and b as one 64-bit integer and adds c to it
#define DBL_INT_ADD(a,b,c) if (a > 0xffffffff - c) ++b; a += c; 
#define ROTLEFT(a,b) ((a << b) | (a >> (32-b))) 

#define F(x,y,z) ((x & y) | (~x & z)) 
#define G(x,y,z) ((x & z) | (y & ~z)) 
#define H(x,y,z) (x ^ y ^ z) 
#define I(x,y,z) (y ^ (x | ~z)) 

#define FF(a,b,c,d,m,s,t) { a += F(b,c,d) + m + t; \
                            a = b + ROTLEFT(a,s); }
#define GG(a,b,c,d,m,s,t) { a += G(b,c,d) + m + t; \
                            a = b + ROTLEFT(a,s); }
#define HH(a,b,c,d,m,s,t) { a += H(b,c,d) + m + t; \
                            a = b + ROTLEFT(a,s); } 
#define II(a,b,c,d,m,s,t) { a += I(b,c,d) + m + t; \
                            a = b + ROTLEFT(a,s); } 


typedef struct { 
   uchar data[64]; 
   uint datalen; 
   uint bitlen[2]; 
   uint state[4]; 
} MD5_CTX; 


void md5_transform(MD5_CTX *ctx, uchar data[]) 
{  
   uint a,b,c,d,m[16],i,j; 
   
   // MD5 specifies big endian byte order, but this implementation assumes a little 
   // endian byte order CPU. Reverse all the bytes upon input, and re-reverse them 
   // on output (in md5_final()). 
   for (i=0,j=0; i < 16; ++i, j += 4) 
      m[i] = (data[j]) + (data[j+1] << 8) + (data[j+2] << 16) + (data[j+3] << 24); 
   
   a = ctx->state[0]; 
   b = ctx->state[1]; 
   c = ctx->state[2]; 
   d = ctx->state[3]; 
   
   FF(a,b,c,d,m[0],  7,0xd76aa478); 
   FF(d,a,b,c,m[1], 12,0xe8c7b756); 
   FF(c,d,a,b,m[2], 17,0x242070db); 
   FF(b,c,d,a,m[3], 22,0xc1bdceee); 
   FF(a,b,c,d,m[4],  7,0xf57c0faf); 
   FF(d,a,b,c,m[5], 12,0x4787c62a); 
   FF(c,d,a,b,m[6], 17,0xa8304613); 
   FF(b,c,d,a,m[7], 22,0xfd469501); 
   FF(a,b,c,d,m[8],  7,0x698098d8); 
   FF(d,a,b,c,m[9], 12,0x8b44f7af); 
   FF(c,d,a,b,m[10],17,0xffff5bb1); 
   FF(b,c,d,a,m[11],22,0x895cd7be); 
   FF(a,b,c,d,m[12], 7,0x6b901122);
   FF(d,a,b,c,m[13],12,0xfd987193); 
   FF(c,d,a,b,m[14],17,0xa679438e); 
   FF(b,c,d,a,m[15],22,0x49b40821); 
   
   GG(a,b,c,d,m[1],  5,0xf61e2562); 
   GG(d,a,b,c,m[6],  9,0xc040b340); 
   GG(c,d,a,b,m[11],14,0x265e5a51); 
   GG(b,c,d,a,m[0], 20,0xe9b6c7aa);
   GG(a,b,c,d,m[5],  5,0xd62f105d); 
   GG(d,a,b,c,m[10], 9,0x02441453); 
   GG(c,d,a,b,m[15],14,0xd8a1e681); 
   GG(b,c,d,a,m[4], 20,0xe7d3fbc8);
   GG(a,b,c,d,m[9],  5,0x21e1cde6); 
   GG(d,a,b,c,m[14], 9,0xc33707d6); 
   GG(c,d,a,b,m[3], 14,0xf4d50d87); 
   GG(b,c,d,a,m[8], 20,0x455a14ed);
   GG(a,b,c,d,m[13], 5,0xa9e3e905); 
   GG(d,a,b,c,m[2],  9,0xfcefa3f8); 
   GG(c,d,a,b,m[7], 14,0x676f02d9); 
   GG(b,c,d,a,m[12],20,0x8d2a4c8a);
   
   HH(a,b,c,d,m[5],  4,0xfffa3942); 
   HH(d,a,b,c,m[8], 11,0x8771f681); 
   HH(c,d,a,b,m[11],16,0x6d9d6122); 
   HH(b,c,d,a,m[14],23,0xfde5380c); 
   HH(a,b,c,d,m[1],  4,0xa4beea44); 
   HH(d,a,b,c,m[4], 11,0x4bdecfa9); 
   HH(c,d,a,b,m[7], 16,0xf6bb4b60); 
   HH(b,c,d,a,m[10],23,0xbebfbc70); 
   HH(a,b,c,d,m[13], 4,0x289b7ec6); 
   HH(d,a,b,c,m[0], 11,0xeaa127fa); 
   HH(c,d,a,b,m[3], 16,0xd4ef3085); 
   HH(b,c,d,a,m[6], 23,0x04881d05); 
   HH(a,b,c,d,m[9],  4,0xd9d4d039); 
   HH(d,a,b,c,m[12],11,0xe6db99e5); 
   HH(c,d,a,b,m[15],16,0x1fa27cf8); 
   HH(b,c,d,a,m[2], 23,0xc4ac5665); 
      
   II(a,b,c,d,m[0],  6,0xf4292244); 
   II(d,a,b,c,m[7], 10,0x432aff97); 
   II(c,d,a,b,m[14],15,0xab9423a7); 
   II(b,c,d,a,m[5], 21,0xfc93a039); 
   II(a,b,c,d,m[12], 6,0x655b59c3); 
   II(d,a,b,c,m[3], 10,0x8f0ccc92); 
   II(c,d,a,b,m[10],15,0xffeff47d); 
   II(b,c,d,a,m[1], 21,0x85845dd1); 
   II(a,b,c,d,m[8],  6,0x6fa87e4f); 
   II(d,a,b,c,m[15],10,0xfe2ce6e0); 
   II(c,d,a,b,m[6], 15,0xa3014314); 
   II(b,c,d,a,m[13],21,0x4e0811a1); 
   II(a,b,c,d,m[4],  6,0xf7537e82); 
   II(d,a,b,c,m[11],10,0xbd3af235); 
   II(c,d,a,b,m[2], 15,0x2ad7d2bb); 
   II(b,c,d,a,m[9], 21,0xeb86d391); 
   
   ctx->state[0] += a; 
   ctx->state[1] += b; 
   ctx->state[2] += c; 
   ctx->state[3] += d; 
}  

void md5_init(MD5_CTX *ctx) 
{  
   ctx->datalen = 0; 
   ctx->bitlen[0] = 0; 
   ctx->bitlen[1] = 0; 
   ctx->state[0] = 0x67452301; 
   ctx->state[1] = 0xEFCDAB89; 
   ctx->state[2] = 0x98BADCFE; 
   ctx->state[3] = 0x10325476; 
}  

void md5_update(MD5_CTX *ctx, uchar data[], uint len) 
{  
   uint i;
   
   for (i=0; i < len; ++i) { 
      ctx->data[ctx->datalen] = data[i]; 
      ctx->datalen++; 
      if (ctx->datalen == 64) { 
         md5_transform(ctx,ctx->data); 
         DBL_INT_ADD(ctx->bitlen[0],ctx->bitlen[1],512); 
         ctx->datalen = 0; 
      }  
   }  
}  

void md5_final(MD5_CTX *ctx, uchar hash[]) 
{  
   uint i;
   
   i = ctx->datalen; 
   
   // Pad whatever data is left in the buffer. 
   if (ctx->datalen < 56) { 
      ctx->data[i++] = 0x80; 
      while (i < 56) 
         ctx->data[i++] = 0x00; 
   }  
   else if (ctx->datalen >= 56) { 
      ctx->data[i++] = 0x80; 
      while (i < 64) 
         ctx->data[i++] = 0x00; 
      md5_transform(ctx,ctx->data); 
      memset(ctx->data,0,56); 
   }  
   
   // Append to the padding the total message's length in bits and transform. 
   DBL_INT_ADD(ctx->bitlen[0],ctx->bitlen[1],8 * ctx->datalen); 
   ctx->data[56] = ctx->bitlen[0]; 
   ctx->data[57] = ctx->bitlen[0] >> 8; 
   ctx->data[58] = ctx->bitlen[0] >> 16; 
   ctx->data[59] = ctx->bitlen[0] >> 24; 
   ctx->data[60] = ctx->bitlen[1]; 
   ctx->data[61] = ctx->bitlen[1] >> 8; 
   ctx->data[62] = ctx->bitlen[1] >> 16;  
   ctx->data[63] = ctx->bitlen[1] >> 24; 
   md5_transform(ctx,ctx->data); 
   
   // Since this implementation uses little endian byte ordering and MD uses big endian, 
   // reverse all the bytes when copying the final state to the output hash. 
   for (i=0; i < 4; ++i) { 
      hash[i]    = (ctx->state[0] >> (i*8)) & 0x000000ff; 
      hash[i+4]  = (ctx->state[1] >> (i*8)) & 0x000000ff; 
      hash[i+8]  = (ctx->state[2] >> (i*8)) & 0x000000ff; 
      hash[i+12] = (ctx->state[3] >> (i*8)) & 0x000000ff; 
   }  
}   

Как это работает?

Как скомпилировать программу?
Вам понадобится любой линукс. Если его нет, то перезагрузите компьютер с Linux Live-CD или Live-USB. (Впрочем, программу можно собрать и на Винде). Сделайте три файла выше, затем в этой папке выполните команду:

gcc -Wall stealth.c -o stealth

В результате у вас появится исполняемый файл stealth, который можно запустить из этого же терминала:

./stealth

Появится подсказка с опциями.

Теперь подробнее о принципе работы. Программа влияет на содержимое чанка ‘data’, остальные чанки копируются без изменения. Опция -wavin задает входной аудиофайл, -wavout — имя создаваемого аудиофайла, -hiddenin — секретный файл для встраивания. Вместо имени файла может стоять дефис, тогда программа будет читать стандартный поток ввода, либо писать в стандартный поток вывода.

Программа имеет удобный инструмент, который позволяет более детально контроллировать процесс работы. Возьмите какой-нибудь звуковой файл, и выполните с ним команду:

./stealth encode -wavin=sound.wav -pass=xyzzy -hiddenin=md5.c -region=7 -microscope 100 2000

Опция -microscope принимает два параметра: первый, это смещение в блоках, второй, это примерная длина вывода в блоках.

Фрагмент выхлопа этой команды:
0x00001ff0 00002033 work 00000  d8 fe 50 ff  -0000296  0xfffffed8  -0000176  0xffffff50
0x00001ff4 00002034 work 00001  da fe 55 ff  -0000294  0xfffffeda  -0000171  0xffffff55
0x00001ff8 00002035 work 00002  d9 fe 65 ff  -0000295  0xfffffed9  -0000155  0xffffff65
0x00001ffc 00002036 work 00003  cf fe 70 ff  -0000305  0xfffffecf  -0000144  0xffffff70
0x00002000 00002037 work 00004  d1 fe 65 ff  -0000303  0xfffffed1  -0000155  0xffffff65
0x00002004 00002038 work 00005  cc fe 6c ff  -0000308  0xfffffecc  -0000148  0xffffff6c
0x00002008 00002039 work 00006  c5 fe 61 ff  -0000315  0xfffffec5  -0000159  0xffffff61
Нечетность региона = 0
Бит для встраивания = 0

0x0000200c 00002040 work 00000  cc fe 50 ff  -0000308  0xfffffecc  -0000176  0xffffff50
0x00002010 00002041 work 00001  d9 fe 3f ff  -0000295  0xfffffed9  -0000193  0xffffff3f
0x00002014 00002042 work 00002  d2 fe 52 ff  -0000302  0xfffffed2  -0000174  0xffffff52
0x00002018 00002043 work 00003  c7 fe 54 ff  -0000313  0xfffffec7  -0000172  0xffffff54
0x0000201c 00002044 work 00004  c7 fe 53 ff  -0000313  0xfffffec7  -0000173  0xffffff53
0x00002020 00002045 work 00005  c5 fe 56 ff  -0000315  0xfffffec5  -0000170  0xffffff56
0x00002024 00002046 work 00006  c5 fe 58 ff  -0000315  0xfffffec5  -0000168  0xffffff58
Нечетность региона = 1
Бит для встраивания = 0
подходящий блок - 4, канал - 0
0x0000201c 00002044 БЫЛО  00004  c7 fe 53 ff  -0000313  0xfffffec7  -0000173  0xffffff53
0x0000201c 00002044 СТАЛО 00004  c6 fe 53 ff  -0000314  0xfffffec6  -0000173  0xffffff53

0x00002028 00002047 work 00000  c3 fe 65 ff  -0000317  0xfffffec3  -0000155  0xffffff65
0x0000202c 00002048 work 00001  b6 fe 61 ff  -0000330  0xfffffeb6  -0000159  0xffffff61
0x00002030 00002049 work 00002  b4 fe 69 ff  -0000332  0xfffffeb4  -0000151  0xffffff69

Первые две колонки, это адрес в файле и номер блока, третья колонка — режим работы, четвертая — номер блока в регионе. Затем идут четыре байта в том порядке, в котором они идут в файле, затем значения семплов для каждого канала. Обратите внимание, в каждом канале сначала идет младший байт, а затем старший.

Программа начинает внедрять данные с указанного смещения, смещение задается опцией -offset. Аудиопоток разделяется на регионы (я называю отрывки звука регионами), для каждого региона вычисляется четность. Чем больше размер региона, тем меньше влияние программы на поток и меньше вероятность обнаружения, однако количество данных, которые можно впихнуть в файл, уменьшается. Размер региона задается опцией -region.

PRNG (Pseudo-Random Number Generator) — генератор псевдослучайных чисел, он нужен в программе для того, чтобы генерировать криптостойкий шум. Этот шум XOR-ится с входным потоком, в результате получается зашифрованный поток, похожий на шум, качество шума зависит от качества генератора. Если генератору шума дать одно и то же зерно, то он произведет один и тот же поток. Это свойство используется при расшифровывании: зашифрованный поток XOR-ится этим же шумом, получается исходный поток.

В этой программе используется PRNG на основе двух хеш-функций. Сами по себе функции SHA1 и MD5 сегодня не рекомендуются к применению по прямому назначению, однако если использовать их одновременно в качестве PRNG, и с результатами выполнять операцию XOR, то энтропия получившегося потока будет выше. Результаты обеих функций XOR-ятся до одного байта.

В качестве материала для генерации случайных чисел используется парольная фраза, счетчик и один из блоков в каждом регионе. Программа не меняет биты двух последних блоков, потому что они понадобятся при декодировании.

В программе присутствует защита от встраивания битов в участки с нулевыми семплами (защита от тишины). Если регион содержит фрагмент с низким уровнем сигнала, такой фрагмент не затрагивается, а при чтении не учитывается. Во время декодирования важно, чтобы проверка тишины дала такой-же результат, чтобы в потоке не было пропущенных битов.

Чтобы встроить файл secret.txt в файл sound.wav выполните такую команду:

./stealth encode -wavin=sound.wav -hiddenin=secret.txt -pass=xyzzy -wavout=newsound.wav

Внимательно изучите сообщения на экране, для расшифровки нам потребуется знать размер региона, смещение, пароль, и количество байт для чтения. Чтобы извлечь секретное сообщение, выполните эту команду:

./stealth decode -wavin=newsound.wav -hiddenout=extracted.txt -pass=xyzzy

Если ввести неправильный пароль, то на выходе получится каша из символов. Никакой проверки целостности данных не производится.

Эта программа экспериментальная. Как и любое ПО для шифрования, эта программа не может дать абсолютной гарантированной безопасности. Программа способна обеспечить достаточно хорошую скрытность данных при условии правильного использования. Перед применением программы советую прочитать следующий раздел. Это не окончательная версия программы, в ней возможны исправления и дополнения.

Техника безопасности

Самый простой способ выявить стеганографию, это сравнить оригинальный файл без скрытого сообщения с этим же файлом после встраивания, тогда легкие изменения файла сразу будут видны. Из этого следует вывод: оригинальный файл рекомендуется уничтожить невосстанавливаемым способом. Ни у кого не должен быть оригинальный файл. Тут я рекомендую следующую методику: займитесь оцифровкой аналоговых носителей, пленки, грампластинок. Возьмите запись, которая вам нравится, и оцифруйте. Исходный файл до стеганографии следует хранить в памяти (ramfs), или в зашифрованном swap разделе, или на зашифрованном разделе жесткого диска, как вам нравится. Промежуточные результаты тоже должны быть зашифрованы и в последствии уничтожены.

Не следует использовать для стеганографии аудиофайлы скачанные из интернета, или взятые с компакт-диска из магазина, по той же причине.

Не следует использовать WAV, восстановленный из mp3. Возможно появление методов, отделяющих звук, полученный из mp3 декодера от помех, которые были внесены стего-программой.

Измененные биты также бывают хорошо видны на фоне повторяющихся кусков, сигналов прямоугольной формы и других особенностей, которые встречаются в современной электронной музыке.

От всего этого помогает способ, приведенный выше. Шум, который присутствует в любом аналоговом тракте, будет маскировать стеганографию.

Опасайтесь перегрузки АЦП. Звуковой сигнал может колебаться в пределах допустимых значений, если подать на вход звуковой карты громкий сигнал, то в момент перегрузки в файл запишется последовательность одинаковых семплов, измененный бит будет заметен среди них.

Также могут появиться другие артефакты, которые демаскируют стеганографию, например, это могут быть следы от авто-коррекции ошибок.

Программа избегает встраивания информации, если сигнал слабый. Других систем защиты в этой программе нет.

5 комментариев для “Стеганография”

  1. Strannik:

    Спасибо. Очень интересно. Узнал очень много нового для себя. Весьма полезная статья.

    1. Узнал что-то новое для себя! Полезная статья!

  2. Очень полезная информация! Было что узнать!

  3. Спасибо. Очень интересно

  4. Rafayel:

    Достаточно полезная статья

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Новости МирТесен