Image Encryption/Decryption tool bug


#1

Hello I am trying to make an image encryptor/decryptor for a hobby project of mine and I’ve gotten it to work but it has a weird bug.
Here are the images (code further down)
Original:

Encrypted file(Slightly downscaled version just to be able to be uploaded):

Decrypted file:

As you can see the program struggles with darker shades and blacks

And I am wondering how I can fix this issue as it can be quite annoying on some images.
I suspect that the issue is how I round the pixel data around the 255/0 points but I cannot seem to make it work

Here is the code that I am currently using:

import interfascia.*;

PImage inputIMG, inputIMGSmall, outputIMG, outputIMGSmall;
GUIController gui;
IFTextField userinputfile, password, useroutputfile;
IFLabel labelinput, labeloutput, passlabel, progresslabel;
IFButton startbutton, startencoding, startdecoding;
String inid, outid, passid;
Long pass;
Float progressfloat = 0.0;
int progressint = 0;
ArrayList<Integer> changes = new ArrayList();
PFont UIF;

void setup() {

  size(360, 420);
  surface.setTitle("ProjectEgami - Idle");

  UIF = createFont("Arial", 20, true);
  textFont(UIF, 20);

  gui = new GUIController(this);

  labelinput = new IFLabel("Input File", 10, 15);
  userinputfile = new IFTextField("FileIn", 10, 30, 120);
  labeloutput = new IFLabel("Output File", 10, 65);
  useroutputfile = new IFTextField("FileOut", 10, 80, 120);
  passlabel = new IFLabel("Passcode", 10, 115);
  password = new IFTextField("FileOut", 10, 130, 120);
  startbutton = new IFButton("Load Files", 10, 160, 120, 17);
  startencoding = new IFButton("Start Encoding", 10, 180, 120, 17);
  startdecoding = new IFButton("Start Decoding", 10, 200, 120, 17);

  userinputfile.addActionListener(this);
  useroutputfile.addActionListener(this);
  password.addActionListener(this);
  startbutton.addActionListener(this);
  startencoding.addActionListener(this);
  startdecoding.addActionListener(this);

  gui.add(labelinput);
  gui.add(labeloutput);
  gui.add(userinputfile);
  gui.add(useroutputfile);
  gui.add(passlabel);
  gui.add(password);
  gui.add(startbutton);
}

void draw() {
  background(230);

  if (inputIMGSmall != null) {
    image(inputIMGSmall, 150, 10);
  }
  if (outputIMG != null && outputIMGSmall == null) {
    outputIMGSmall = outputIMG.copy();
    outputIMGSmall.resize(0, 200);
    image(outputIMGSmall, 150, 210);
  } else if (outputIMGSmall != null) {
    image(outputIMGSmall, 150, 210);
  }
}

void LoadImagery() {
  inputIMG = loadImage("data/input/" + inid);
  inputIMGSmall = inputIMG.copy();

  inputIMGSmall.resize(0, 200);

  gui.add(startencoding);
  gui.add(startdecoding);
}

void StartEncoding() {

  float r, g, b;
  float randomVal;
  pass = Long.parseLong(passid); 

  outputIMG = inputIMG.copy();
  outputIMG.loadPixels();

  println(pass);
  for (int i = 0; i < outputIMG.pixels.length; i++) {
    progressfloat = i/((float)outputIMG.pixels.length);
    randomSeed(pass);
    randomVal = random(255);
    r = red(outputIMG.pixels[i]);
    g = green(outputIMG.pixels[i]);
    b = blue(outputIMG.pixels[i]);

    r -= randomVal;
    if (r < 0) {
      r += 255;
    }
    g += randomVal;
    if (g > 255) {
      g -= 255;
    }
    b -= randomVal;
    if (b < 0) {
      b += 255;
    }

    outputIMG.pixels[i] = color(r, g, b);

    pass *= pass/2;
    pass += 5;

    if (progressint < (int) (progressfloat * 100)) {
      surface.setTitle("ProjectEgami - " + (int) (progressfloat * 100) + "%");
      progressint = (int) (progressfloat * 100);
    }

    println(progressfloat);
  }

  outputIMG.updatePixels();
  outputIMG.save("data/output/" + outid);
  surface.setTitle("ProjectEgami - Finish");
  println(outputIMG.pixels.length + " " + changes.size());
}

void StartDecoding() {

  float r, g, b;
  float randomVal;
  pass = Long.parseLong(passid); 

  outputIMG = inputIMG.copy();
  outputIMG.loadPixels();

  println(pass);
  for (int i = 0; i < outputIMG.pixels.length; i++) {
    progressfloat = i/((float)outputIMG.pixels.length);
    randomSeed(pass);
    randomVal = random(255);
    r = red(outputIMG.pixels[i]);
    g = green(outputIMG.pixels[i]);
    b = blue(outputIMG.pixels[i]);

    r += randomVal;
    if (r > 255) {
      r -= 255;
    }
    g -= randomVal;
    if (g < 0) {
      g += 255;
    }
    b += randomVal;
    if (b > 255) {
      b -= 255;
    }

    outputIMG.pixels[i] = color(r, g, b);

    pass *= pass/2;
    pass += 5;

    if (progressint < (int) (progressfloat * 100)) {
      surface.setTitle("ProjectEgami - " + (int) (progressfloat * 100) + "%");
      progressint = (int) (progressfloat * 100);
    }

    println(progressfloat);
  }

  outputIMG.updatePixels();
  outputIMG.save("data/output/" + outid);
  surface.setTitle("ProjectEgami - Finish");
  println(outputIMG.pixels.length + " " + changes.size());
}

void actionPerformed(GUIEvent e) {
  if (e.getMessage().equals("Modified")) {
    if (e.getSource() == userinputfile) {
      inid = userinputfile.getValue();
    } else if (e.getSource() == useroutputfile) {
      outid = useroutputfile.getValue();
    } else if (e.getSource() == password) {
      passid = password.getValue();
    }
  }
  if (e.getSource() == startbutton) {
    println("Input: " + inid + " Output: " + outid + " Password: " + passid);
    randomSeed(Long.parseLong(passid));
    LoadImagery();
  } else if (e.getSource() == startencoding) {
    println("Starting Encoding");
    StartEncoding();
  } else if (e.getSource() == startdecoding) {
    println("Starting Decoding");
    StartDecoding();
  }
}

Any help or tips would be greately appriciated
Best regards
cheetah


#2

If you remove your println statements from your loops, it will run faster.

To reproduce the above, would you load then encode then decode? Or would you load then encode. Next step is to load the encoded version and then decode? Notice that using randomseed does not guarantee consistency across different platforms.

Kf


#3

If you want to save / load encoded images then

  1. you must use a lossless image compression algorithm e.g. png, gif
  2. you would also need to store the random seed value so you can decode it.

I suggest that you avoid using random numbers for the reason kfrajer stated. I would suggest the following approach

  1. Use the SHA-1 hashing algorithm to convert a password or passphrase to a 20 byte array (h[])
  2. convert the 20 byte array into an array of 17 integers (e[]) using bitwise operators to fill the 32 bits of an integer

e[0] = (h[0] << 24) | (h[1] << 16) | (h[2] << 8) | h(3)
e[1] = (h[1] << 24) | (h[2] << 16) | (h[3] << 8) | h(4)
...
e[16] = (h[16] << 24) | (h[17] << 16) | (h[18] << 8) | h(19)

Then load the image pixels and loop through the array, xoring the image data with the next element in the integer array

img.loadPixels();
int[] d = img.pixels;
int ip = 0;
for(int i = 0; i < d.length; i++){
  d[i] = d[i] ^ e[ip]; // perform XOR
  ip = (ip + 1) % e.length; // loop through encryption values
}
img.updatePixels();

The advantage is that the SHA-1 algorithm has a standard implementation where random number generators can vary between machines.

Decoding the image can use the same code because the xor operation is reversable i.e.
if P = Q ^ N is true then Q = P ^ N is also true.

The Staganos library uses this approach when encrypting images although the algorithm is more complex because it uses a second hash algorithm (SHA-256) with the passphrase to generate an array used to rotate the color bits in the encrypted image. All the code for this is in the Encryption.java class.