1

目前正在从事隐写术项目,在该项目中,给定消息(以字节为单位)和每个字节要修改的位数,将消息隐藏在任意字节数组中。

在结果消息的第一个解码字节中,该值的第一个(最左边)位设置为“1”而不是“0”。例如,当使用消息时"Foo".getBytes()maxBits = 1结果是“Æoo”,而不是“Foo”(0b01000110 变为 0b11000110)。消息"Æoo".getBytes()maxBits = 1结果是“Æoo”,这意味着据我所知,该位没有被翻转。

只有某些maxBits消息字节的某些值会导致此错误,例如"Foo"maxBits等于 1、5 和 6 时"Test"遇到此问题,而在maxBits等于 1、3 和 5 时遇到此问题。只有生成的第一个字符以它的结尾第一位设置,并且此问题仅发生在this.maxBits与初始数据相关的指定值处。

  • 为什么对于 的某些值maxBits,得到的解码消息的第一位总是 1?
  • 为什么不同的输入有不同的值maxBits可以正常工作,而其他输入则没有?
  • maxBits与原始数据相关的值和产生的错误结果的模式是什么?

编码和解码方法:

public byte[] encodeMessage(byte[] data, byte[] message) {
    byte[] encoded = data;
    boolean[] messageBits = byteArrToBoolArr(message);
    int index = 0;
    for (int x = 0; x < messageBits.length; x++) {
        encoded[index] = messageBits[x] ? setBit(encoded[index], x % this.maxBits) : unsetBit(encoded[index], x % this.maxBits);
        if (x % this.maxBits == 0 && x != 0)
            index++;
    }
    return encoded;
}

public byte[] decodeMessage(byte[] data) {
    boolean[] messageBits = new boolean[data.length * this.maxBits];
    int index = 0;
    for (int x = 0; x < messageBits.length; x++) {
        messageBits[x] = getBit(data[index], x % this.maxBits);
        if (x % this.maxBits == 0 && x != 0)
            index++;
    }
    return boolArrToByteArr(messageBits);
}

取消设置、设置和获取方法:

public byte unsetBit(byte data, int pos) {
    return (byte) (data & ~((1 << pos)));
}

public byte setBit(byte data, int pos) {
    return (byte) (data | ((1 << pos)));
}

public boolean getBit(byte data, int pos) {
    return ((data >>> pos) & 0x01) == 1;
}

转换方法:

public boolean[] byteArrToBoolArr(byte[] b) {
    boolean bool[] = new boolean[b.length * 8];
    for (int x = 0; x < bool.length; x++) {
        bool[x] = false;
        if ((b[x / 8] & (1 << (7 - (x % 8)))) > 0)
            bool[x] = true;
    }
    return bool;
}

public byte[] boolArrToByteArr(boolean[] bool) {
    byte[] b = new byte[bool.length / 8];
    for (int x = 0; x < b.length; x++) {
        for (int y = 0; y < 8; y++) {
            if (bool[x * 8 + y]) {
                b[x] |= (128 >>> y);
            }
        }
    }
    return b;
}

示例代码和输出:

    test("Foo", 1);//Æoo
    test("Foo", 2);//Foo
    test("Foo", 3);//Foo
    test("Foo", 4);//Foo
    test("Foo", 5);//Æoo
    test("Foo", 6);//Æoo
    test("Foo", 7);//Foo
    test("Foo", 8);//Foo

    test("Test", 1);//Ôest
    test("Test", 2);//Test
    test("Test", 3);//Ôest
    test("Test", 4);//Test
    test("Test", 5);//Ôest
    test("Test", 6);//Test
    test("Test", 7);//Test
    test("Test", 8);//Test

    private static void test(String s, int x) {
        BinaryModifier bm = null;
        try {
            bm = new BinaryModifier(x);//Takes maxBits as constructor param
        } catch (BinaryException e) {
            e.printStackTrace();
        }
        System.out.println(new String(bm.decodeMessage(bm.encodeMessage(new byte[1024], s.getBytes()))));
        return;
    }
4

2 回答 2

1

您的递增逻辑index有两个缺陷,它们覆盖了第一个字母的第一位。显然,当覆盖位与第一位不同时,就会表示错误。

if (x % this.maxBits == 0 && x != 0)
    index++;

第一个问题与每个字节仅嵌入一位有关,即maxBits = 1. 在您嵌入第一位并达到上述条件后,x仍然为 0,因为它将在循环结束时递增。此时您应该递增index,但x != 0会阻止您这样做。因此,第二位也将嵌入到第一个字节中,有效地覆盖了第一位。由于此逻辑也存在于 decode 方法中,因此您从第一个字节读取前两位。

更具体地说,如果您嵌入00or 11,就可以了。但是 a01将被读取为11并且 a10将被读取为00,即,无论第二位是什么值。如果第一个字母的 ascii 代码小于或等于 63 (00xxxxxx),或大于或等于 192 (11xxxxxx),则结果会很好。例如:

# -> # : 00100011 (35) -> 00100011 (35)
F -> Æ : 01000110 (70) -> 11000110 (198)

第二个问题与x % this.maxBits == 0零件有关。考虑我们每个字节嵌入 3 位的情况。在第 3 位之后,当我们达到条件时,我们仍然有x = 2,所以模运算将返回 false。在我们嵌入了第 4 位之后,我们确实有了x = 3并且可以继续前进到下一个字节。x % this.maxBits但是,这个额外的第 4 位将被写入第一个字节的第 0 位,因为3 % 3. 所以再一次,我们有一点覆盖我们的第一个位。然而,在第一个周期之后,模运算将正确地写入每个字节仅 3 位,因此我们的消息的其余部分将不受影响。

考虑“F”的二进制,即 01000110。通过每个字节嵌入 N 位,我们有效地将以下组嵌入到前几个字节中。

1 bit  01 0 0 0 1 1 0
2 bits 010 00 11 0x
3 bits 0100 011 0xx
4 bits 01000 110x
5 bits 010001 10xxxx
6 bits 0100011 0xxxxx
7 bits 01000110
8 bits 01000110x

如您所见,对于 5 位和 6 位的组,第一组的最后一位是 1,这将覆盖我们最初的 0 位。对于所有其他情况,覆盖不会影响任何事情。请注意,对于 8 位,我们最终使用第二个字母的第一位。如果碰巧有一个大于或等于 128 的 ascii 代码,它将再次覆盖最前面的 0 位。

要解决所有问题,请使用

for (int x = 0; x < messageBits.length; x++) {
    // code in the between
    if ((x + 1) % this.maxBits == 0)
        index++;
}

或者

for (int x = 0; x < messageBits.length; ) {
    // code in the between
    x++;
    if (x % this.maxBits == 0)
        index++;
}

您的代码还有另一个尚未表达的潜在问题。如果您的data数组大小为 1024,但您只嵌入 3 个字母,您将只影响前几个字节,具体取决于maxBits. 但是,对于提取,您将数组定义为大小为data.length * this.maxBits. 因此,您最终会从data数组的所有字节中读取位。这目前没有问题,因为您的数组由 0 填充,它们被转换为空字符串。但是,如果您的数组有实际数字,那么您最终会在嵌入数据点之外读取大量垃圾。

有两种通用的方法来解决这个问题。你要么

  • 在消息(标记)的末尾附加一个唯一的位序列,这样当您遇到该序列时,您会终止提取,例如八个 0,或
  • 在嵌入实际数据(标头)之前添加一些位,这将告诉您如何提取数据,例如,要读取多少字节以及每个字节有多少位。
于 2015-11-18T05:07:23.340 回答
0

您可能会遇到的一件事是字符编码的性质。

当您调用时,s.getBytes()您正在使用 JVM 的默认编码将字符串转换为字节。然后修改字节并使用默认编码再次从修改后的字节创建一个新字符串。

所以问题是编码是什么以及它是如何工作的。例如,在某些情况下,编码可能只查看与字符相关的字节的低 7 位,那么您对最高位的设置不会对从修改后的字节创建的字符串产生任何影响。

如果您真的想判断您的代码是否正常工作,请通过直接检查byte[]您的编码和解码方法生成的内容来进行测试,而不是通过将修改后的字节转换为字符串并查看字符串。

于 2015-11-17T21:17:43.590 回答