隐写术之后的第一个字节比特一个字位

时间:2015-11-17 16:54:43

标签: java binary bit-manipulation byte off-by-one

目前正致力于一个隐写术项目,在这个项目中,给定一个以字节为单位的消息和每个字节要修改的位数,在任意字节数组中隐藏一条消息。

在结果消息的第一个解码字节中,该值将其第一个(最左侧)位设置为“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.只有结果的第一个字符以其第一个字符集结束,并且此问题仅发生在与{1}相关的指定值处。初始数据。

  • 为什么,this.maxBits的某些值是第一位 结果解码的消息总是1?
  • 为什么不同的输入对maxBits具有不同的值 工作正常,其他人没有?
  • maxBitsmaxBits的值是什么模式 导致与原始数据相关的错误结果?

编码和解码方法:

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;
    }

2 个答案:

答案 0 :(得分:1)

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

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

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

更具体地说,如果您嵌入了0011,那就没问了。但是01将被视为1110将被视为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,我们被允许继续下一个字节。但是,这个额外的第4位将写在第一个字节的第0位,因为x % this.maxBits将是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,或
  • 在嵌入实际数据(标题)之前添加几位,这将告诉您如何提取数据,例如,每个字节要读取多少字节和多少位。

答案 1 :(得分:0)

你可能会遇到的一件事就是字符编码的本质。

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

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

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