Strong changes a WAV file when you change one bit. Steganography

425 views Asked by At

encode_text() function implements the LSB coding method. First recorded message length, then the message itself.

numberOfBits number of bits which are allocated for writing messages. Write to one bit of a byte or two or three ...

This method of encoding using one bit for the record, the sound changes should not be noticeable to the ear. As well as changes in the image to the eye. But this is not the case. For some reason, at the beginning of the sound is changed to "noise". That should not be.

read() and save() OK, if you read the data file and then burn them there are no changes.

The question is what is wrong with encode_text() function. Or maybe I did something wrong?

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


}
1

There are 1 answers

0
Ludwig Schulze On

You're modifying the least significant bit of every byte. So you are introducing noise of only ~ -48dB re Full Scale, regardless of how many bits one sample has in your sound file. This would be clearly Audible in silence, which I guess is what you mean with "At the beginning of the sound".

You probably meant to modify only the least significant bit of the usual 16 bit which would make only noise of ~ -96dB re Full Scale.

If you modifyied only the LSB of 16 bit samples, then your noise would be much much softer. But be aware that it would still be perceptible to normal hearing people in silent parts of the sound file on good equipment at loud playback volumes. It would also be fairly obvious to anyone looking at your sound file with an audio editor or similar. For effective steganography, you would have to (at least) adapt your coding to the time-dependent sound levels.