All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.bloodshura.x.steganography.AsciiRgbCipher Maven / Gradle / Ivy

/*
 * Copyright (c) 2013-2018, João Vitor Verona Biazibetti - All Rights Reserved
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see .
 *
 * https://www.github.com/BloodShura
 */

package com.github.bloodshura.x.steganography;

import com.github.bloodshura.x.assets.image.Image;
import com.github.bloodshura.x.assets.image.process.ImageProcessor;
import com.github.bloodshura.x.charset.TextBuilder;
import com.github.bloodshura.x.charset.sequence.CharSet;
import com.github.bloodshura.x.io.stream.FastByteArrayOutputStream;
import com.github.bloodshura.x.steganography.exception.SteganographyException;
import com.github.bloodshura.x.worker.StringWorker;

import javax.annotation.Nonnull;
import java.util.function.Consumer;
import java.util.function.IntFunction;

// TODO Work in progress.
public class AsciiRgbCipher implements Cipher {
	private byte[] magic;

	public AsciiRgbCipher() {
		setMagic((byte) 242, (byte) 123, (byte) 64);
	}

	@Override
	public boolean canEncode(@Nonnull Image image, @Nonnull CharSequence sequence) {
		return sequence.length() <= maxDataLength(image) && StringWorker.containsOnly(sequence, CharSet.ASCII);
	}

	@Override
	public boolean canEncodeData(@Nonnull Image image, @Nonnull byte[] data) {
		return data.length <= maxDataLength(image);
	}

	@Nonnull
	@Override
	public String decode(@Nonnull Image image) throws SteganographyException {
		TextBuilder builder = new TextBuilder();

		doDecode(image, value -> builder.append((char) value.byteValue()), false);

		return builder.toString();
	}

	@Nonnull
	@Override
	public byte[] decodeData(@Nonnull Image image) throws SteganographyException {
		FastByteArrayOutputStream output = new FastByteArrayOutputStream();

		doDecode(image, output::write, false);

		return output.toArray();
	}

	@Override
	public void encode(@Nonnull Image image, @Nonnull CharSequence sequence) throws SteganographyException {
		if (!canEncode(image, sequence)) {
			throw new SteganographyException("Cannot encode \"" + sequence + "\" into specified image; see SimplePixelCipher.canEncode()");
		}

		doEncode(image, sequence.length(), index -> (byte) sequence.charAt(index));
	}

	@Override
	public void encodeData(@Nonnull Image image, @Nonnull byte[] data) throws SteganographyException {
		if (!canEncodeData(image, data)) {
			throw new SteganographyException("Cannot encode data into specified image; see SimplePixelCipher.canEncodeData()");
		}

		doEncode(image, data.length, index -> data[index]);
	}

	@Nonnull
	public byte[] getMagic() {
		return magic;
	}

	@Override
	public boolean isEncoded(@Nonnull Image image) {
		try {
			doDecode(image, value -> {
			}, true);

			return true;
		}
		catch (SteganographyException exception) {
			return false;
		}
	}

	public int maxDataLength(@Nonnull Image image) {
		return ((image.getWidth() * image.getHeight() * 3) / Byte.SIZE) - getMagic().length - 1 /* null char */;
	}

	public void setMagic(@Nonnull byte... magic) {
		this.magic = magic;
	}

	protected void doDecode(@Nonnull Image image, @Nonnull Consumer consumer, boolean stopOnMagic) throws SteganographyException {
		byte current = 0x0;
		int curBit = 0;
		int curChar = 0;
		byte[] magic = getMagic();

		for (int x = 0; x < image.getWidth(); x++) {
			for (int y = 0; y < image.getHeight(); y++) {
				int pixel = image.pixelAt(x, y).hashCode();

				for (int i = 1; i <= 3; i++) {
					int bit = (pixel >> (24 - i * Byte.SIZE)) & 0x1;

					if (bit == 0x1) {
						current |= 1 << curBit;
					}
					else {
						current &= ~(1 << curBit);
					}

					if (++curBit >= Byte.SIZE) {
						curBit = 0;

						if (curChar < magic.length) {
							if (current != magic[curChar]) {
								throw new SteganographyException("Magic number does not matches");
							}
						}
						else {
							if (stopOnMagic || current == 0x0) {
								return;
							}

							consumer.accept(current);
						}

						curChar++;
					}
				}
			}
		}

		throw new SteganographyException("Ending null character not found");
	}

	protected void doEncode(@Nonnull Image image, int supplierLength, @Nonnull IntFunction supplier) throws SteganographyException {
		ImageProcessor processor = image.newProcessor();
		int curChar = 0;
		int curBit = 0;
		byte[] magic = getMagic();

		loop:
		for (int x = 0; x < image.getWidth(); x++) {
			for (int y = 0; y < image.getHeight(); y++) {
				int pixel = image.pixelAt(x, y).hashCode();

				for (int i = 1; i <= 3; i++) {
					byte ch = 0x0; // Null char

					if (curChar < magic.length) {
						ch = magic[curChar]; // Magic char
					}
					else if ((curChar - magic.length) < supplierLength) {
						ch = supplier.apply(curChar - magic.length); // Sequence char
					}

					int bit = (ch >> curBit) & 0x1;

					if (bit == 0x1) {
						pixel |= 1 << (24 - i * Byte.SIZE);
					}
					else {
						pixel &= ~(1 << (24 - i * Byte.SIZE));
					}

					if (++curBit >= Byte.SIZE) {
						curBit = 0;
						curChar++;

						if (curChar > supplierLength + magic.length) {
							processor.setPixel(x, y, pixel);

							break loop;
						}
					}
				}

				processor.setPixel(x, y, pixel);
			}
		}

		processor.finish();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy