Skip to content

F5

stegobox.codec.F5

Bases: BaseCodec

F5 steganography

This module implements F5 steganography in JPEG images. * To encode: The input image and message are both converted into binary, 8 * 8 pieces of image DCT transform and quantization, remove the DCT coefficient of 16 non-zero, with one odd and negative even representative of positive and negative, odd and even represent 0, form a vector a, take out to embed the secret data, 4 bits of computing whether need to modify a, if the need to modify (r = 0), the first step is returned, continue to the next set of inserts. If (r is not 0) needs to be modified, the absolute value of the DCT coefficient to be modified is reduced by 1, and the sign remains unchanged; It is necessary to check that the modified DCT coefficient is 0: if not, return to the first step and proceed to the next set of embeddings.

  • To decode: The vector image was transformed into 8*8 small pieces of DCT and quantified, and 16 non-0 DCT coefficients were taken out. Positive odd and negative even were used to represent 1, and negative odd and positive even were used to represent 0 to form a vector A, which was multiplied by M matrix to obtain the hidden 4-bit information, and then repeated until all steganographic information was removed.
Source code in stegobox/codec/f5.py
class F5(BaseCodec):
    """F5 steganography

    This module implements F5 steganography in JPEG images.
    * To encode: The input image and message are both converted into binary, 8 * 8
      pieces of image DCT transform and quantization, remove the DCT coefficient of 16
      non-zero, with one odd and negative even representative of positive and negative,
      odd and even represent 0, form a vector a, take out to embed the secret data, 4
      bits of computing whether need to modify a, if the need to modify (r = 0), the
      first step is returned, continue to the next set of inserts. If (r is not 0) needs
      to be modified, the absolute value of the DCT coefficient to be modified is
      reduced by 1, and the sign remains unchanged; It is necessary to check that the
      modified DCT coefficient is 0: if not, return to the first step and proceed to the
      next set of embeddings.

    * To decode: The vector image was transformed into 8*8 small pieces of DCT and
      quantified, and 16 non-0 DCT coefficients were taken out. Positive odd and
      negative even were used to represent 1, and negative odd and positive even were
      used to represent 0 to form a vector A, which was multiplied by M matrix to obtain
      the hidden 4-bit information, and then repeated until all steganographic
      information was removed.
    """

    def __init__(self) -> None:
        # Standard quantization table
        self.Q = np.array(
            [
                [16, 11, 10, 16, 24, 40, 51, 61],
                [12, 12, 14, 19, 26, 58, 60, 55],
                [14, 13, 16, 24, 40, 57, 69, 56],
                [14, 17, 22, 29, 51, 87, 80, 62],
                [18, 22, 37, 56, 68, 109, 103, 77],
                [24, 35, 55, 64, 81, 104, 113, 92],
                [49, 64, 78, 87, 103, 121, 120, 101],
                [72, 92, 95, 98, 112, 100, 103, 99],
            ]
        )
        # Check matrix
        self.M = np.array(
            [
                [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
                [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1],
                [0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1],
                [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
            ]
        )
        super().__init__()

    def encode(self, carrier: Image.Image, payload: str) -> Image.Image:
        """Merge secret_img into host_img.

        Args:
            carrier: Carrier image in format JEPG.
            payload:  Payload (secret message) to be encoded.


        Returns:
            Image: container image.
        """
        self._check_empty_payload(payload)
        img = np.array(carrier)
        # A channel of images
        img1 = img[:, :, 1]
        h, w = img1.shape
        # D = np.zeros(img1.shape)
        img1 = img1.astype(np.float32)
        d = img1
        block_y = h // 8
        block_x = w // 8
        d = self._dct(d, block_y, block_x)
        byte_s = payload.encode("utf-8")
        # Convert payload to binary (removes leading '0b') contains string length
        hex_bin = byte_s.hex()
        oct_bin = oct(int(hex_bin, 16))
        binary_bin = bin(int(oct_bin, 8))[2:].zfill(len(hex_bin) * 4)
        lenth_bin = bin(len(binary_bin))[2:].zfill(12)
        binary_bin = lenth_bin + binary_bin
        data_lenth = len(binary_bin)
        stego = d
        num = 0
        a = np.zeros((15, 1))
        k = 0
        sit = np.zeros((15, 2))

        for i in range(block_y * 8):
            for j in range(block_x * 8):
                if (d[i, j] > 0 and (d[i, j] % 2) == 1) or (
                    d[i, j] < 0 and (d[i, j] % 2) == 0
                ):
                    a[k] = 1
                    sit[k][0] = i
                    sit[k][1] = j
                    k = k + 1
                elif (d[i, j] < 0 and (d[i, j] % 2) == 1) or (
                    d[i, j] > 0 and (d[i, j] % 2) == 0
                ):
                    a[k] = 0
                    sit[k][0] = i
                    sit[k][1] = j
                    k = k + 1
                if k >= 15:
                    data_bit = np.zeros((4, 1))
                    data_bit[0][0] = binary_bin[num]
                    num = num + 1
                    data_bit[1][0] = binary_bin[num]
                    num = num + 1
                    data_bit[2][0] = binary_bin[num]
                    num = num + 1
                    data_bit[3][0] = binary_bin[num]
                    temp = np.dot(self.M, a)
                    temp = temp % 2
                    ntemp = np.logical_xor(data_bit, temp)
                    n = (
                        ntemp[0][0] * 8
                        + ntemp[1][0] * 4
                        + ntemp[2][0] * 2
                        + ntemp[3][0]
                        - 1
                    )
                    if n >= 0:
                        if d[int(sit[n][0])][int(sit[n][1])] < 0:
                            stego[int(sit[n][0])][int(sit[n][1])] = (
                                d[int(sit[n][0])][int(sit[n][1])] + 1
                            )
                        elif d[int(sit[n][0])][int(sit[n][1])] > 0:
                            stego[int(sit[n][0])][int(sit[n][1])] = (
                                d[int(sit[n][0])][int(sit[n][1])] - 1
                            )
                        if stego[int(sit[n][0])][int(sit[n][1])] == 0:
                            k = k - 1
                            if n < 14:
                                sit[n:k, :] = sit[n + 1 : k + 1, :]
                                a[n:k] = a[n + 1 : k + 1]
                            if n == 14:
                                ...
                            num = num - 3
                            continue
                    num = num + 1
                    k = 0
                if num >= data_lenth:
                    break
            if num >= data_lenth:
                break
        stego = self._idct(stego, block_y, block_x)
        for i in range(h):
            for j in range(w):
                if stego[i][j] > 255:
                    stego[i][j] = 255
                if stego[i][j] < 0:
                    stego[i][j] = 0
        stego = np.round(stego)
        stego = np.uint8(stego)
        img[:, :, 1] = stego
        new_img = Image.fromarray(np.uint8(img))
        return new_img

    def decode(self, carrier: Image.Image) -> str:
        img = np.array(carrier)
        img1 = img[:, :, 1]
        data_lenth = 100
        extract = ""

        h, w = img1.shape
        d = np.zeros(img1.shape)
        block_y = h // 8
        block_x = w // 8
        img1 = img1.astype(np.float32)
        d = self._dct(img1, block_y, block_x)
        num = 0
        a = np.zeros((15, 1))
        k = 0
        for i in range(block_y * 8):
            for j in range(block_x * 8):
                if (d[i, j] > 0 and (d[i, j] % 2) == 1) or (
                    d[i, j] < 0 and (d[i, j] % 2) == 0
                ):
                    a[k] = 1
                    k = k + 1
                elif (d[i, j] < 0 and (d[i, j] % 2) == 1) or (
                    d[i, j] > 0 and (d[i, j] % 2) == 0
                ):
                    a[k] = 0
                    k = k + 1
                if k >= 15:
                    temp = np.dot(self.M, a)
                    temp = temp % 2
                    extract += str(int(temp[0][0]))
                    extract += str(int(temp[1][0]))
                    extract += str(int(temp[2][0]))
                    extract += str(int(temp[3][0]))
                    num = num + 4
                    if num == 12:
                        data_lenth = int(extract[:12], 2) + 12
                    k = 0
                if num >= data_lenth:
                    break
            if num >= data_lenth:
                break
        return bytes.fromhex(hex(int(extract[12:], 2))[2:]).decode("utf-8")

    def _check_empty_payload(self, payload: str) -> None:
        # check the paypload is empty or not
        if not payload:
            raise Exception("Payload must not be empty.")

    def _dct(self, before: np.ndarray, block_y: int, block_x: int) -> np.ndarray:
        # dct the matrix by 8 * 8 matrix
        for i in range(block_y):
            for j in range(block_x):
                before[8 * i : 8 * (i + 1), 8 * j : 8 * (j + 1)] = np.around(
                    cv2.dct(before[8 * i : 8 * (i + 1), 8 * j : 8 * (j + 1)])
                )
                before[8 * i : 8 * (i + 1), 8 * j : 8 * (j + 1)] = np.array(
                    np.around(
                        np.divide(
                            before[8 * i : 8 * (i + 1), 8 * j : 8 * (j + 1)], self.Q
                        )
                    )
                )
        return before

    def _idct(self, before: np.ndarray, block_y: int, block_x: int) -> np.ndarray:
        # idct the matrix by 8 * 8 matrix
        for i in range(block_y):
            for j in range(block_x):
                before[8 * i : 8 * (i + 1), 8 * j : 8 * (j + 1)] = np.array(
                    np.multiply(
                        before[8 * i : 8 * (i + 1), 8 * j : 8 * (j + 1)], self.Q
                    )
                )
                before[8 * i : 8 * (i + 1), 8 * j : 8 * (j + 1)] = cv2.idct(
                    before[8 * i : 8 * (i + 1), 8 * j : 8 * (j + 1)]
                )
        return before

encode(carrier, payload)

Merge secret_img into host_img.

Parameters:

Name Type Description Default
carrier Image

Carrier image in format JEPG.

required
payload str

Payload (secret message) to be encoded.

required

Returns:

Name Type Description
Image Image

container image.

Source code in stegobox/codec/f5.py
def encode(self, carrier: Image.Image, payload: str) -> Image.Image:
    """Merge secret_img into host_img.

    Args:
        carrier: Carrier image in format JEPG.
        payload:  Payload (secret message) to be encoded.


    Returns:
        Image: container image.
    """
    self._check_empty_payload(payload)
    img = np.array(carrier)
    # A channel of images
    img1 = img[:, :, 1]
    h, w = img1.shape
    # D = np.zeros(img1.shape)
    img1 = img1.astype(np.float32)
    d = img1
    block_y = h // 8
    block_x = w // 8
    d = self._dct(d, block_y, block_x)
    byte_s = payload.encode("utf-8")
    # Convert payload to binary (removes leading '0b') contains string length
    hex_bin = byte_s.hex()
    oct_bin = oct(int(hex_bin, 16))
    binary_bin = bin(int(oct_bin, 8))[2:].zfill(len(hex_bin) * 4)
    lenth_bin = bin(len(binary_bin))[2:].zfill(12)
    binary_bin = lenth_bin + binary_bin
    data_lenth = len(binary_bin)
    stego = d
    num = 0
    a = np.zeros((15, 1))
    k = 0
    sit = np.zeros((15, 2))

    for i in range(block_y * 8):
        for j in range(block_x * 8):
            if (d[i, j] > 0 and (d[i, j] % 2) == 1) or (
                d[i, j] < 0 and (d[i, j] % 2) == 0
            ):
                a[k] = 1
                sit[k][0] = i
                sit[k][1] = j
                k = k + 1
            elif (d[i, j] < 0 and (d[i, j] % 2) == 1) or (
                d[i, j] > 0 and (d[i, j] % 2) == 0
            ):
                a[k] = 0
                sit[k][0] = i
                sit[k][1] = j
                k = k + 1
            if k >= 15:
                data_bit = np.zeros((4, 1))
                data_bit[0][0] = binary_bin[num]
                num = num + 1
                data_bit[1][0] = binary_bin[num]
                num = num + 1
                data_bit[2][0] = binary_bin[num]
                num = num + 1
                data_bit[3][0] = binary_bin[num]
                temp = np.dot(self.M, a)
                temp = temp % 2
                ntemp = np.logical_xor(data_bit, temp)
                n = (
                    ntemp[0][0] * 8
                    + ntemp[1][0] * 4
                    + ntemp[2][0] * 2
                    + ntemp[3][0]
                    - 1
                )
                if n >= 0:
                    if d[int(sit[n][0])][int(sit[n][1])] < 0:
                        stego[int(sit[n][0])][int(sit[n][1])] = (
                            d[int(sit[n][0])][int(sit[n][1])] + 1
                        )
                    elif d[int(sit[n][0])][int(sit[n][1])] > 0:
                        stego[int(sit[n][0])][int(sit[n][1])] = (
                            d[int(sit[n][0])][int(sit[n][1])] - 1
                        )
                    if stego[int(sit[n][0])][int(sit[n][1])] == 0:
                        k = k - 1
                        if n < 14:
                            sit[n:k, :] = sit[n + 1 : k + 1, :]
                            a[n:k] = a[n + 1 : k + 1]
                        if n == 14:
                            ...
                        num = num - 3
                        continue
                num = num + 1
                k = 0
            if num >= data_lenth:
                break
        if num >= data_lenth:
            break
    stego = self._idct(stego, block_y, block_x)
    for i in range(h):
        for j in range(w):
            if stego[i][j] > 255:
                stego[i][j] = 255
            if stego[i][j] < 0:
                stego[i][j] = 0
    stego = np.round(stego)
    stego = np.uint8(stego)
    img[:, :, 1] = stego
    new_img = Image.fromarray(np.uint8(img))
    return new_img