Saturday, July 9, 2022

Day 23/100 Steganography

Day 23/100 Steganography

How do you say Steganosaurus?


I've been working on this source code packager on and off. The whole idea for this program is that most forums on the web restrict the filesize for text, while allowing generous allowance for pictures. So, the idea of including text inside the picture was born.


There are several different ways to do this. You can simply draw the text in the picture, which is what I have been doing. You can encode the text as barcodes or QR codes. Or you can encode the text as black and white dots.


However, those solution involves creating pictures in such an ugly way. So, what if you can just include the text inside the picture itself? Hidden text, so to speak. Hence steganography.


In my case, I decided that since I'm using Raspberry Pi, the display can only show 16 bit color, whereas pictures are usually 24 bit color. So, it is obvious that I should design it as one byte per pixel. The problem is that I cannot just append a byte on the picture. I have to split it as 3-2-3 bits to fit into the 5-6-5 rgb scheme.


It took some doing. I made it easy for myself by skipping PNG format and go for PPM (P6) format. You're going to have to convert it to PNG format, but I'll leave that up to you. Personally, I use ImageMagick convert program.


This is to Write to the image:


      bit=(char) fgetc(fpImg);
      bit&=0xF8; bit|=(c&0xE0)>>5; bit1=bit;
      bit=(char) fgetc(fpImg);
      bit&=0xFC; bit|=(c&0x18)>>3; bit2=bit;
      bit=(char) fgetc(fpImg);
      bit&=0xF8; bit|=(c&0x07); bit3=bit;
      printf("%c%c%c",bit1,bit2,bit3);


And this is to Read from the image:


  for (i=0;i<l;i++) {
      bit1=(char) fgetc(fpImg);
      bit2=(char) fgetc(fpImg);
      bit3=(char) fgetc(fpImg);
      sout= ((bit1 & 0x07)<<5)
                  | ((bit2 & 0x03)<<3)
                  | ((bit3 & 0x07));
      putchar(sout);
  }


If you're wondering why the sout character isn't set to zero before reading in the data, that's because it's not necessary. sout is a character of 8 bits. I'm reading in 8 bits. So, sout variable is going to be wholly overwritten, anyway.


I was able to use the same program for both Write and Read. Depending whether you supply both picture name and data filename, or just the picture name, the program will select the operation appropriately.


 if (argc<2) {
    puts("Write: fileglob [picname.ppm] [datafilename]");
    puts("Read:  fileglob [picname.ppm]");
    puts("ppm is P6 file (binary)");
    return 1;
  }


The hardest part is design, and trying to decipher PPM format. I still have trouble, and I'm not at all sure that I have it. The specification stated that there should be a whitespace (usually newline) after the colordepth, but it gave me an off-by-one error which shifted the color of the picture. I don't think I quiet get it, yet. So, I can only guarantee that the program works on my machine.


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

#define MAXSTR 256
FILE *fpImg;
FILE *fpDat;
char buff[MAXSTR];
int w,h,d;
long l;
char sname[MAXSTR];

int Init(int argc, char *argv[]) {

  if ((fpImg=fopen(argv[1],"r"))==NULL) {
    puts("File open Error");
    return 1;
  }

  if ((fpDat=fopen(argv[2],"r"))==NULL) {
    puts("File open Error");
    return 1;
  }

// Read P6 PPM format
  if (fgets(buff,MAXSTR,fpImg)==NULL) {
    puts("Image file read error");
    return 1;
  }
  if (strncmp("P6",buff,2)) {
    puts("Image file not P6 PPM error");
    return 1;
  }
  puts("P6");

  fseek(fpDat,0L, SEEK_END);
  l = ftell(fpDat);
  fseek(fpDat,0L, SEEK_SET);
  printf("#steg Aa A %ld %s\n",l,argv[2]);


// Read width height
  fscanf(fpImg,"%d %d",&w,&h);  //Line 50
  printf("%d %d\n",w,h);

// Read depth
  fscanf(fpImg,"%s",buff);
  d=atoi(buff);
  printf("%d",d);

  if (d!=255) {
    puts("Image depth isn't 255");
    return 1;
  }

  if (l>(w*h)) {
    puts("Image size too small!");
    printf("Image: %ld  Data: %ld\n",(long)(w*h),l);
    return 1;
  }

  return 0;
}

void CleanUp() {
  if (fpImg!=stdin) fclose(fpImg);
  if (fpDat!=stdin) fclose(fpDat);
}

int ReadData(int argc, char *argv[]) {
  char bit1,bit2,bit3;
  char sout;

  long i;
  long c; char c1;
  char s1[MAXSTR];
  char s2[MAXSTR];
  char s3[MAXSTR];
  char s4[MAXSTR];
  char s5[MAXSTR];

  if ((fpImg=fopen(argv[1],"r"))==NULL) {
    puts("File open Error");
    return 1;
  }

// Read P6 PPM format
  if (fgets(buff,MAXSTR,fpImg)==NULL) {
    puts("Image file read error");
    return 1;
  }
  if (strncmp("P6",buff,2)) {
    puts("Image file not P6 PPM error");
    return 1;
  }
  fscanf(fpImg,"%s %s %s %s %s",s1,s2,s3,s4,s5);
  l=atol(s4); strcpy(sname,s5);

// Read width height
  fscanf(fpImg,"%d %d",&w,&h);  //Line 50

// Read depth
  fscanf(fpImg,"%d",&d);
  if (d!=255) {
    puts("Image depth isn't 255");
    return 1;
  }

  for (i=0;i<l;i++) {
      bit1=(char) fgetc(fpImg);
      bit2=(char) fgetc(fpImg);
      bit3=(char) fgetc(fpImg);
      sout= ((bit1 & 0x07)<<5)
                  | ((bit2 & 0x03)<<3)
                  | ((bit3 & 0x07));
      putchar(sout);
  }

  return 0;
}

void ProcessData() {
  long i,j;
  int c; char bit;
  char bit1,bit2,bit3;
  char sout;

  for (i=0;i<(w*h);i++) {
    c=fgetc(fpDat);
    if (c==EOF) {
      putchar(fgetc(fpImg));
      putchar(fgetc(fpImg));
      putchar(fgetc(fpImg));
    } else {
      bit=(char) fgetc(fpImg);
      bit&=0xF8; bit|=(c&0xE0)>>5; bit1=bit;
      bit=(char) fgetc(fpImg);
      bit&=0xFC; bit|=(c&0x18)>>3; bit2=bit;
      bit=(char) fgetc(fpImg);
      bit&=0xF8; bit|=(c&0x07); bit3=bit;
      printf("%c%c%c",bit1,bit2,bit3);
    }
  }
}

int main (int argc, char *argv[] ) {
  int e=0;

  if (argc<2) {
    puts("Write: fileglob [picname.ppm] [datafilename]");
    puts("Read:  fileglob [picname.ppm]");
    puts("ppm is P6 file (binary)");
    return 1;
  }

  if (argc==2) ReadData(argc,argv);

  if (argc==3) {
    Init(argc,argv);
    ProcessData();
    CleanUp();
  }

  return e;
}


One more thing:

Design is much, much harder than coding! In the process of programming this, I changed the design several times. It took me hours to finish this program up to this state, but that's because I keep changing the design. There used to be more parameters required in order to write the image, as well as trying it out directly with PNG format using Processing. But it's not until PPM idea comes into being that the program finally arrived at satisfactory design.

No comments:

Post a Comment