【问题标题】:Strong changes a WAV file when you change one bit. Steganography当您更改一位时,Strong 会更改 WAV 文件。隐写术
【发布时间】:2017-05-10 09:12:42
【问题描述】:

encode_text() 函数实现了LSB 编码方式。先记录消息长度,然后记录消息本身。

numberOfBits 分配用于写入消息的位数。写入一个字节的一个位或两个或三个...

这种使用一位编码的方法进行记录,声音的变化应该不会被耳朵注意到。以及图像到眼睛的变化。但这种情况并非如此。不知为何,开头的声音变成了“noise”。那不应该。

read()save() 好的,如果您读取数据文件然后刻录它们,则没有任何变化。

问题是 encode_text() 函数有什么问题。还是我做错了什么?

import java.io.*;
import java.util.Arrays;

public class wavIO
{

private String myPath;
private long myChunkSize;
private long mySubChunk1Size;
private int myFormat;
private long myChannels;
private long mySampleRate;
private long myByteRate;
private int myBlockAlign;
private int myBitsPerSample;
private long myDataSize;

public byte[] myData;

public String getPath()
{
    return myPath;
}
public void setPath(String newPath)
{
    myPath = newPath;
}

public wavIO()
    {
    myPath = "";
    }
public wavIO(String tmpPath)
    {
    myPath = tmpPath;
    }

// read a wav file into this class
public boolean read()
{
    DataInputStream inFile = null;
    myData = null;
    byte[] tmpLong = new byte[4];
    byte[] tmpInt = new byte[2];

    try
    {
        inFile = new DataInputStream(new FileInputStream(myPath));

        //System.out.println("Reading wav file...\n"); // for debugging only

        String chunkID = "" + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte();

        inFile.read(tmpLong); // read the ChunkSize
        myChunkSize = byteArrayToLong(tmpLong);

        String format = "" + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte();

        // print what we've read so far
        //System.out.println("chunkID:" + chunkID + " chunk1Size:" + myChunkSize + " format:" + format); // for debugging only



        String subChunk1ID = "" + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte();

        inFile.read(tmpLong); // read the SubChunk1Size
        mySubChunk1Size = byteArrayToLong(tmpLong);

        inFile.read(tmpInt); // read the audio format.  This should be 1 for PCM
        myFormat = byteArrayToInt(tmpInt);

        inFile.read(tmpInt); // read the # of channels (1 or 2)
        myChannels = byteArrayToInt(tmpInt);

        inFile.read(tmpLong); // read the samplerate
        mySampleRate = byteArrayToLong(tmpLong);

        inFile.read(tmpLong); // read the byterate
        myByteRate = byteArrayToLong(tmpLong);

        inFile.read(tmpInt); // read the blockalign
        myBlockAlign = byteArrayToInt(tmpInt);

        inFile.read(tmpInt); // read the bitspersample
        myBitsPerSample = byteArrayToInt(tmpInt);
        // print what we've read so far
        //System.out.println("SubChunk1ID:" + subChunk1ID + " SubChunk1Size:" + mySubChunk1Size + " AudioFormat:" + myFormat + " Channels:" + myChannels + " SampleRate:" + mySampleRate);


        // read the data chunk header - reading this IS necessary, because not all wav files will have the data chunk here - for now, we're just assuming that the data chunk is here
        String dataChunkID = "" + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte() + (char)inFile.readByte();

        inFile.read(tmpLong); // read the size of the data
        myDataSize = byteArrayToLong(tmpLong);


        // read the data chunk
        myData = new byte[(int)myDataSize];
        inFile.read(myData);

        // close the input stream
        inFile.close();

    }
    catch(Exception e)
    {
        return false;
    }

    return true; // this should probably be something more descriptive
}

// write out the wav file
public boolean save(String outputPath)
{
    try
    {
        //DataOutputStream outFile  = new DataOutputStream(new FileOutputStream(myPath));
        DataOutputStream outFile  = new DataOutputStream(new FileOutputStream(outputPath));

        // write the wav file per the wav file format
        outFile.writeBytes("RIFF");                 // 00 - RIFF
        outFile.write(intToByteArray((int)myChunkSize), 0, 4);      // 04 - how big is the rest of this file?
        outFile.writeBytes("WAVE");                 // 08 - WAVE
        outFile.writeBytes("fmt ");                 // 12 - fmt 
        outFile.write(intToByteArray((int)mySubChunk1Size), 0, 4);  // 16 - size of this chunk
        outFile.write(shortToByteArray((short)myFormat), 0, 2);     // 20 - what is the audio format? 1 for PCM = Pulse Code Modulation
        outFile.write(shortToByteArray((short)myChannels), 0, 2);   // 22 - mono or stereo? 1 or 2?  (or 5 or ???)
        outFile.write(intToByteArray((int)mySampleRate), 0, 4);     // 24 - samples per second (numbers per second)
        outFile.write(intToByteArray((int)myByteRate), 0, 4);       // 28 - bytes per second
        outFile.write(shortToByteArray((short)myBlockAlign), 0, 2); // 32 - # of bytes in one sample, for all channels
        outFile.write(shortToByteArray((short)myBitsPerSample), 0, 2);  // 34 - how many bits in a sample(number)?  usually 16 or 24
        outFile.writeBytes("data");                 // 36 - data
        outFile.write(intToByteArray((int)myDataSize), 0, 4);       // 40 - how big is this data chunk
        outFile.write(myData);                      // 44 - the actual data itself - just a long string of numbers
    }
    catch(Exception e)
    {
        System.out.println(e.getMessage());
        return false;
    }

    return true;
}

// return a printable summary of the wav file
public String getSummary()
{
    //String newline = System.getProperty("line.separator");
    String newline = "<br>";
    String summary = "<html>Format: " + myFormat + newline + "Channels: " + myChannels + newline + "SampleRate: " + mySampleRate + newline + "ByteRate: " + myByteRate + newline + "BlockAlign: " + myBlockAlign + newline + "BitsPerSample: " + myBitsPerSample + newline + "DataSize: " + myDataSize + "</html>";
    return summary;
}


// ===========================
// CONVERT BYTES TO JAVA TYPES
// ===========================

// these two routines convert a byte array to a unsigned short
public static int byteArrayToInt(byte[] b)
{
    int start = 0;
    int low = b[start] & 0xff;
    int high = b[start+1] & 0xff;
    return (int)( high << 8 | low );
}


// these two routines convert a byte array to an unsigned integer
public static long byteArrayToLong(byte[] b)
{
    int start = 0;
    int i = 0;
    int len = 4;
    int cnt = 0;
    byte[] tmp = new byte[len];
    for (i = start; i < (start + len); i++)
    {
        tmp[cnt] = b[i];
        cnt++;
    }
    long accum = 0;
    i = 0;
    for ( int shiftBy = 0; shiftBy < 32; shiftBy += 8 )
    {
        accum |= ( (long)( tmp[i] & 0xff ) ) << shiftBy;
        i++;
    }
    return accum;
}


// ===========================
// CONVERT JAVA TYPES TO BYTES
// ===========================
// returns a byte array of length 4
private static byte[] intToByteArray(int i)
{
    byte[] b = new byte[4];
    b[0] = (byte) (i & 0x00FF);
    b[1] = (byte) ((i >> 8) & 0x000000FF);
    b[2] = (byte) ((i >> 16) & 0x000000FF);
    b[3] = (byte) ((i >> 24) & 0x000000FF);
    return b;
}

// convert a short to a byte array
public static byte[] shortToByteArray(short data)
{
    return new byte[]{(byte)(data & 0xff),(byte)((data >>> 8) & 0xff)};
}


public void encode(String text, int numberOfBits)
{
    byte[] byteMessage = text.getBytes();
    byte[] messageLength = bit_conversion(byteMessage.length);
    encodeText(messageLength, myData, 0, numberOfBits);
    encodeText(byteMessage, myData, 32, numberOfBits);  
}

private void encodeText(byte[] addition, byte[] byteDataInputWav, int offset, int numberOfBits)
{
    if(addition.length + offset > byteDataInputWav.length)
    {
        System.out.println("File not long enough!");
    }
    else
    {
        for(int i=0; i<addition.length; ++i)
        {
            int add = addition[i];
            for(int bit=7; bit>=0; --bit, ++offset)
            {
                int b = (add >>> bit) & 1;
                byteDataInputWav[offset] = (byte)((byteDataInputWav[offset] & numberOfBits) | b );
            }
        }
    }
}  

private byte[] bit_conversion(int i)
{
    byte byte3 = (byte)((i & 0xFF000000) >>> 24);
    byte byte2 = (byte)((i & 0x00FF0000) >>> 16);
    byte byte1 = (byte)((i & 0x0000FF00) >>> 8 ); 
byte byte0 = (byte)((i & 0x000000FF)       );
return(new byte[]{byte3,byte2,byte1,byte0});
}

public String decode(String inputPath)
{
    byte[] byteDataOutputWav = myData;
    int length = 0;
int offset2 = 32;
for(int i=0; i<32; ++i)
{
    length = (length << 1) | (byteDataOutputWav[i] & 1);
}
byte[] result = new byte[length];
for(int b=0; b<result.length; ++b )
{
        for(int i=0; i<8; ++i, ++offset2)
        {
            result[b] = (byte)((result[b] << 1) | (byteDataOutputWav[offset2] & 1));
        }
}
    return new String(result);
}


}

【问题讨论】:

    标签: java wav steganography


    【解决方案1】:

    您正在修改每个字节的最低有效位。因此,无论一个样本在您的声音文件中有多少位,您都只引入了 ~ -48dB re Full Scale 的噪声。这在静默中显然是可听的,我猜这就是“在声音的开头”的意思。

    您可能打算只修改通常的 16 位中的最低有效位,这只会产生 ~ -96dB re Full Scale 的噪声。

    如果您只修改了 16 位样本的 LSB,那么您的噪音会柔和得多。但请注意,在高音量播放的优质设备上,正常听力的人在声音文件的静音部分仍然可以感知到。对于任何使用音频编辑器或类似工具查看您的声音文件的人来说,这也是相当明显的。对于有效的隐写术,您必须(至少)使您的编码适应与时间相关的声级。

    【讨论】:

      猜你喜欢
      • 2016-08-18
      • 2019-11-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-26
      • 2018-10-02
      • 1970-01-01
      相关资源
      最近更新 更多