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