Skip to content

FFTHideStrinWAV

stegobox.codec.FFTHideStrinWAV

Bases: BaseCodec

This module implements simple FFT steganography hide string in audio (wav).

Referenced from shalom06/Audio-Stego.

Source code in stegobox/codec/fft_hide_str_in_wav.py
class FFTHideStrinWAV(BaseCodec):
    """
    This module implements simple FFT steganography hide string in audio (wav).

    Referenced from [shalom06/Audio-Stego](https://github.com/shalom06/Audio-Stego).
    """

    def __init__(self) -> None:
        super().__init__()

    def encode(self, carrier: np.ndarray, payload: str) -> tuple[np.ndarray, int]:
        """
        Encoder requires the carrier audio in format WAV and the payload in format
        string.

        Args:
            carrier (np.ndarray): Carrier audio in format WAV. Read with
                `stegobox.io.audio.wavfile_read()`.
            payload (str): Payload (secret message) to be encoded.

        Returns:
            np.ndarray: the frame information of the audio with the payload embedded.
            int: the rate of the audio with the payload embedded.
        """
        # step 1 divide into chunks
        carrier = carrier.copy()
        payload_length = 8 * len(payload)
        blocklength = int(2 * 2 ** np.ceil(np.log2(2 * payload_length)))
        blocknumber = int(np.ceil(carrier.shape[0] / blocklength))

        # checks shape to change data to 1 axis
        if len(carrier.shape) == 1:
            carrier.resize(blocknumber * blocklength, refcheck=False)
            carrier = carrier[np.newaxis]
        else:
            carrier.resize(
                (blocknumber * blocklength, carrier.shape[1]), refcheck=False
            )
            carrier = carrier.T

        blocks = carrier[0].reshape((blocknumber, blocklength))
        # Calculate DFT using fft
        blocks = np.fft.fft(blocks)

        # calculate magnitudes
        magnitudes = np.abs(blocks)

        # create phase matrix
        phases = np.angle(blocks)

        # get phase differences
        phasediffs = np.diff(phases, axis=0)

        # conert message to encode into binary
        textinbinary = np.array(
            [[int(y) for y in format(ord(x), "08b")] for x in payload]
        )
        textinbinary = textinbinary.reshape(-1)

        # Convert txt to phase differences
        textinpi = textinbinary.copy()
        textinpi[textinpi == 0] = -1
        textinpi = textinpi * -np.pi / 2

        blockmid = blocklength // 2

        # do phase conversion
        phases[0, blockmid - payload_length : blockmid] = textinpi
        phases[0, blockmid + 1 : blockmid + 1 + payload_length] = -textinpi[::-1]

        # re compute  the ophase amtrix
        for i in range(1, len(phases)):
            phases[i] = phases[i - 1] + phasediffs[i - 1]

        # apply i-dft
        blocks = magnitudes * np.exp(1j * phases)
        blocks = np.fft.ifft(blocks).real

        # combining all block of audio again
        carrier[0] = blocks.ravel().astype(np.int16)

        return carrier.T, payload_length

    def decode(self, _):
        raise NotImplementedError("This codec does not support decoding without length")

    def decode_with_length(self, carrier: np.ndarray, payload_length: int) -> str:
        """
        Decode the secret payload from the carrier audio

        Args:
            carrier (np.ndarray): Carrier audio in format WAV. Read with
                `stegobox.io.audio.wavfile_read()`.
            payload_length (int): The length of secret message.

        Returns:
            str: The decoded payload (secret message).
        """

        blocklength = 2 * int(2 ** np.ceil(np.log2(2 * payload_length)))
        blockmid = blocklength // 2

        # get header info
        if len(carrier.shape) == 1:
            secret = carrier[:blocklength]
        else:
            secret = carrier[:blocklength, 0]

        # get the phase and convert it to binary
        secretphases = np.angle(np.fft.fft(secret))[
            blockmid - payload_length : blockmid
        ]
        secretinbinary = (secretphases < 0).astype(np.int8)

        #  convert into characters
        secretincode = secretinbinary.reshape((-1, 8)).dot(
            1 << np.arange(8 - 1, -1, -1)
        )

        # combine characters to original text
        return "".join(np.char.mod("%c", secretincode))

encode(carrier, payload)

Encoder requires the carrier audio in format WAV and the payload in format string.

Parameters:

Name Type Description Default
carrier ndarray

Carrier audio in format WAV. Read with stegobox.io.audio.wavfile_read().

required
payload str

Payload (secret message) to be encoded.

required

Returns:

Name Type Description
ndarray

np.ndarray: the frame information of the audio with the payload embedded.

int int

the rate of the audio with the payload embedded.

Source code in stegobox/codec/fft_hide_str_in_wav.py
def encode(self, carrier: np.ndarray, payload: str) -> tuple[np.ndarray, int]:
    """
    Encoder requires the carrier audio in format WAV and the payload in format
    string.

    Args:
        carrier (np.ndarray): Carrier audio in format WAV. Read with
            `stegobox.io.audio.wavfile_read()`.
        payload (str): Payload (secret message) to be encoded.

    Returns:
        np.ndarray: the frame information of the audio with the payload embedded.
        int: the rate of the audio with the payload embedded.
    """
    # step 1 divide into chunks
    carrier = carrier.copy()
    payload_length = 8 * len(payload)
    blocklength = int(2 * 2 ** np.ceil(np.log2(2 * payload_length)))
    blocknumber = int(np.ceil(carrier.shape[0] / blocklength))

    # checks shape to change data to 1 axis
    if len(carrier.shape) == 1:
        carrier.resize(blocknumber * blocklength, refcheck=False)
        carrier = carrier[np.newaxis]
    else:
        carrier.resize(
            (blocknumber * blocklength, carrier.shape[1]), refcheck=False
        )
        carrier = carrier.T

    blocks = carrier[0].reshape((blocknumber, blocklength))
    # Calculate DFT using fft
    blocks = np.fft.fft(blocks)

    # calculate magnitudes
    magnitudes = np.abs(blocks)

    # create phase matrix
    phases = np.angle(blocks)

    # get phase differences
    phasediffs = np.diff(phases, axis=0)

    # conert message to encode into binary
    textinbinary = np.array(
        [[int(y) for y in format(ord(x), "08b")] for x in payload]
    )
    textinbinary = textinbinary.reshape(-1)

    # Convert txt to phase differences
    textinpi = textinbinary.copy()
    textinpi[textinpi == 0] = -1
    textinpi = textinpi * -np.pi / 2

    blockmid = blocklength // 2

    # do phase conversion
    phases[0, blockmid - payload_length : blockmid] = textinpi
    phases[0, blockmid + 1 : blockmid + 1 + payload_length] = -textinpi[::-1]

    # re compute  the ophase amtrix
    for i in range(1, len(phases)):
        phases[i] = phases[i - 1] + phasediffs[i - 1]

    # apply i-dft
    blocks = magnitudes * np.exp(1j * phases)
    blocks = np.fft.ifft(blocks).real

    # combining all block of audio again
    carrier[0] = blocks.ravel().astype(np.int16)

    return carrier.T, payload_length

decode_with_length(carrier, payload_length)

Decode the secret payload from the carrier audio

Parameters:

Name Type Description Default
carrier ndarray

Carrier audio in format WAV. Read with stegobox.io.audio.wavfile_read().

required
payload_length int

The length of secret message.

required

Returns:

Name Type Description
str str

The decoded payload (secret message).

Source code in stegobox/codec/fft_hide_str_in_wav.py
def decode_with_length(self, carrier: np.ndarray, payload_length: int) -> str:
    """
    Decode the secret payload from the carrier audio

    Args:
        carrier (np.ndarray): Carrier audio in format WAV. Read with
            `stegobox.io.audio.wavfile_read()`.
        payload_length (int): The length of secret message.

    Returns:
        str: The decoded payload (secret message).
    """

    blocklength = 2 * int(2 ** np.ceil(np.log2(2 * payload_length)))
    blockmid = blocklength // 2

    # get header info
    if len(carrier.shape) == 1:
        secret = carrier[:blocklength]
    else:
        secret = carrier[:blocklength, 0]

    # get the phase and convert it to binary
    secretphases = np.angle(np.fft.fft(secret))[
        blockmid - payload_length : blockmid
    ]
    secretinbinary = (secretphases < 0).astype(np.int8)

    #  convert into characters
    secretincode = secretinbinary.reshape((-1, 8)).dot(
        1 << np.arange(8 - 1, -1, -1)
    )

    # combine characters to original text
    return "".join(np.char.mod("%c", secretincode))