隐写术,只有 jpg 作为输入有效,当使用 png 时,生成的图像看起来很奇怪

Steganography, only jpg as input works, when using png the resulting image looks strange

我构建了一个 java 小程序,它使用最低有效位方法在图像中隐藏消息。输入 jpg 文件时它工作正常。输出可能是 png 或 jpg。但是当输入png时,结果看起来很奇怪。

这里分别是原图和结果图:

public abstract class Builder{

public static void leastSignificantBitEncryption(String imageSource, String message, String newPath) {
    BufferedImage image = returnImage(imageSource);
    //prepare variables
    String[] messageBinString = null;
    String[] pixelBinString = null;
    final byte[] messageBin = message.getBytes(StandardCharsets.UTF_8);
    final byte[] pixelsBin = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
    //convert message and image to binary string array
    try {
        messageBinString = stringToBinaryStrings(messageBin);
        pixelBinString = stringToBinaryStrings(pixelsBin);
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    String[] messageBinStringCut = splitIn2Bit(messageBinString);   //split message binary into 2 bit strings
    String[] pixelBinStringNew = pixelBinString.clone();    //insert 2 bit strings in last 2 bits of bytes from bitmap
    insert2Bit(messageBinStringCut, pixelBinStringNew);
    byte[] pixelsBinNew = stringArrayToByteArray(pixelBinStringNew);    //Convert string array to byte array
    try {   //Create new image out of bitmap
        int w = image.getWidth();
        int h = image.getHeight();
        BufferedImage imageNew = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
        imageNew.setData(Raster.createRaster(imageNew.getSampleModel(), new DataBufferByte(pixelsBinNew, pixelsBinNew.length), new Point()));
        File imageFile = new File(newPath);
        ImageIO.write(imageNew, "png", imageFile);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private static String[] stringToBinaryStrings(byte[] messageBin) throws UnsupportedEncodingException{
    String[] bytes = new String[messageBin.length];
    int i = 0;
    for(byte b : messageBin) {
        bytes[i] = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
        i++;
    }
    return bytes;
}

private static String binaryStringsToString(String[] messageBin) throws UnsupportedEncodingException{
    StringBuilder stringBuilder = new StringBuilder();
    int i = 0;
    while(messageBin[i] != null) {
        stringBuilder.append((char) Integer.parseInt(messageBin[i], 2));
        i++;
    }
    return stringBuilder.toString();
}

private static BufferedImage returnImage(String imageSource) {
    try{
        try {
            return ImageIO.read(new URL(imageSource));
        } catch (MalformedURLException e) {
            return ImageIO.read(new File(imageSource));
        }
    } catch (IOException ioe) {
        ioe.printStackTrace();
        return null;
    }
}

private static byte[] stringArrayToByteArray(String[] stringArray) {
    byte[] byteArray = new byte[stringArray.length];
    for(int i = 0; i < stringArray.length; i++) {
        byteArray[i] = (byte) Integer.parseInt(stringArray[i], 2);
    }
    return byteArray;
}

private static String[] splitIn2Bit(String[] inputArray) {
    String[] outputArray = new String[inputArray.length * 4];
    for(int i = 0; i < outputArray.length; i += 4) {
        String[] splitByte = inputArray[i / 4].split("(?<=\G..)");
        outputArray[i] = splitByte[0];
        outputArray[i + 1] = splitByte[1];
        outputArray[i + 2] = splitByte[2];
        outputArray[i + 3] = splitByte[3];
    }
    return outputArray;
}

private static String[] insert2Bit(String[] twoBitArray, String[] insertArray) {
    for(int i = 0; i < twoBitArray.length; i++) {
        insertArray[i] = insertArray[i].substring(0, 6) + twoBitArray[i];
    }
    return insertArray;
}

}

此外,测试类:

public class Test {

    public static void main(String[] args) {
        Builder.leastSignificantBitEncryption("IMAGEPATH OR URL", "MESSAGE", "PATH FOR IMAGE CONTAINING MESSAGE");
        Builder.leastSignificantBitDecryption("PATH OF IMAGE CONTAINING MESSAGE", "PATH FOR TXT CONTAINING OUTPUT");
    }
}

错误源于png图像有一个额外的透明通道。 System.out.println(pixelsBin.length); returns jpg 为 338355 字节,png 为 451140 字节。

最简单的解决方案是根据格式文件创建适当的 imageNew。例如,

int w = image.getWidth();
int h = image.getHeight();
BufferedImage imageNew = null;
if (imageSource.matches(".*jpg$")) {
    imageNew = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
} else if (imageSource.matches(".*png$")) {
    imageNew = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
} else {
    // whatever
}
imageNew.setData(Raster.createRaster(imageNew.getSampleModel(), new DataBufferByte(pixelsBinNew, pixelsBinNew.length), new Point()));

但是,您必须注意消息并未嵌入两种类型的相同像素中。 3 通道图像(无透明度)的字节数组如下所示

first-pixel-BLUE, first-pixel-GREEN, first-pixel-RED, second-pixel-BLUE, etc

而对于 4 通道图像

first-pixel-ALPHA, first-pixel-BLUE, first-pixel-GREEN, first-pixel-RED, second-pixel-ALPHA, etc

如果您关心该细节,您可能首先对 removing the alpha channel from the png 感兴趣,因此您总是使用 3 通道图像。