Skip to content

LSBHidePNGInNNModel

stegobox.codec.LSBHidePNGInNNModel

Bases: BaseCodec

LSBHideStrInNNModel - steganography algorithm that hides PNG image in neural network models.

Originally implemented in gaborvecsei/Neural-Network-Steganography

Source code in stegobox/codec/hide_png_in_nn_model/lsb_hide_png_in_nnmodel.py
class LSBHidePNGInNNModel(BaseCodec):
    """LSBHideStrInNNModel - steganography algorithm that hides PNG image in neural
    network models.

    Originally implemented in
    [gaborvecsei/Neural-Network-Steganography](https://github.com/gaborvecsei/Neural-Network-Steganography)
    """

    def __init__(self, num_bits: int = 8) -> None:
        """To initialize the class and specify the number of bits to use.

        Args:
            num_bits: The number of bits to hide secret information.
        """
        super().__init__()
        self.bit_use = num_bits

    def encode(
        self, carrier: collections.OrderedDict, payload: np.ndarray
    ) -> tuple[collections.OrderedDict, tuple[int, ...]]:
        """Encodes payload string into an NN model by LSB.

        Args:
            carrier: The carrier NN model, read with `io.nnmodel.read()`.
            payload: The secret image in format PNG. read with `io.image.read_cv2()`.

        Returns:
            The encoded NN model, write with `io.nnmodel.write()`.
            size of secret image, you will need this when decoding.
        """
        image_size = payload.shape
        secret_bits = payload.reshape(-1)
        secret_bits = np.unpackbits(secret_bits)

        length = len(secret_bits)

        # This dict holds the original weights for the selected layers
        original_weights_dict: dict = {}

        for te in carrier:

            # if re.match(".*conv.*", te):

            original_weights_dict[te] = deepcopy(
                carrier[te].clone().detach().cpu().numpy()
            )

        layer_name = list(original_weights_dict.keys())
        layer_name_num = {}
        pre_layer_name = {}
        layer_shape = {}
        for name in layer_name:
            layer_name_num[name] = np.prod(original_weights_dict[name].shape)

        for i in range(len(layer_name)):
            name = layer_name[i]
            if i == 0:
                pre_layer_name[name] = 0
            else:
                pre_layer_name[name] = (
                    layer_name_num[layer_name[i - 1]]
                    + pre_layer_name[layer_name[i - 1]]
                )

        carrier_data = []
        for name in layer_name:
            layer_shape[name] = original_weights_dict[name].shape
            carrier_data.extend(original_weights_dict[name].reshape(-1))

        for i in range(0, length, self.bit_use):
            _from_index = i
            _to_index = _from_index + self.bit_use
            bits_to_hide = secret_bits[_from_index:_to_index]
            bits_to_hide = list(map(bool, bits_to_hide))  # type: ignore

            j = math.ceil(i / self.bit_use)
            x = FloatBinary(carrier_data[j])
            fraction_modified = list(x.fraction)
            if len(bits_to_hide) > 0:
                fraction_modified[-self.bit_use :] = bits_to_hide

            x_modified = x.modify_clone(fraction=fraction_modified)
            carrier_data[j] = x_modified.v

        outdata = {}
        for name in layer_name:

            temp = np.array(
                carrier_data[
                    int(pre_layer_name[name]) : int(pre_layer_name[name])
                    + int(layer_name_num[name])
                ]
            )
            outdata[name] = temp.reshape(layer_shape[name])

        for name in layer_name:

            carrier.pop(name)

            carrier[name] = torch.from_numpy(outdata[name])
            # carrier[name] = torch.nn.parameter.Parameter(

            #     torch.from_numpy(outdata[name]), requires_grad= False
            # )

        return carrier, image_size

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

    def decodewithsize(
        self, carrier: collections.OrderedDict, image_size: tuple[int, ...]
    ) -> np.ndarray:
        """Try to decode secret image from an NN model by LSB.

        Args:
            carrier: the carrier NN model,read with `io.nnmodel.read()`.
            image_size: the size of secret image.

        Returns:
            np.ndarray: The decode secret image.
        """
        hidden_data: List[bool] = []
        original_weights_dict: dict = {}
        for te in carrier:

            # if re.match(".*conv.*", te):
            original_weights_dict[te] = deepcopy(
                carrier[te].clone().detach().cpu().numpy()
            )

        layer_name = list(original_weights_dict.keys())
        carrier_data = []
        for name in layer_name:
            carrier_data.extend(original_weights_dict[name].reshape(-1))
        length = image_size[0] * image_size[1] * image_size[2] * 8
        for i in range(0, length, self.bit_use):

            j = math.ceil(i / self.bit_use)

            x = FloatBinary(carrier_data[j])
            hidden_bits = x.fraction[-self.bit_use :]
            hidden_data.extend(hidden_bits)
        hidden_data = np.array(hidden_data).astype(int)  # type: ignore
        recovered_image = np.array(np.packbits(hidden_data)).reshape(image_size)

        return recovered_image

__init__(num_bits=8)

To initialize the class and specify the number of bits to use.

Parameters:

Name Type Description Default
num_bits int

The number of bits to hide secret information.

8
Source code in stegobox/codec/hide_png_in_nn_model/lsb_hide_png_in_nnmodel.py
def __init__(self, num_bits: int = 8) -> None:
    """To initialize the class and specify the number of bits to use.

    Args:
        num_bits: The number of bits to hide secret information.
    """
    super().__init__()
    self.bit_use = num_bits

encode(carrier, payload)

Encodes payload string into an NN model by LSB.

Parameters:

Name Type Description Default
carrier OrderedDict

The carrier NN model, read with io.nnmodel.read().

required
payload ndarray

The secret image in format PNG. read with io.image.read_cv2().

required

Returns:

Type Description
OrderedDict

The encoded NN model, write with io.nnmodel.write().

tuple[int, ...]

size of secret image, you will need this when decoding.

Source code in stegobox/codec/hide_png_in_nn_model/lsb_hide_png_in_nnmodel.py
def encode(
    self, carrier: collections.OrderedDict, payload: np.ndarray
) -> tuple[collections.OrderedDict, tuple[int, ...]]:
    """Encodes payload string into an NN model by LSB.

    Args:
        carrier: The carrier NN model, read with `io.nnmodel.read()`.
        payload: The secret image in format PNG. read with `io.image.read_cv2()`.

    Returns:
        The encoded NN model, write with `io.nnmodel.write()`.
        size of secret image, you will need this when decoding.
    """
    image_size = payload.shape
    secret_bits = payload.reshape(-1)
    secret_bits = np.unpackbits(secret_bits)

    length = len(secret_bits)

    # This dict holds the original weights for the selected layers
    original_weights_dict: dict = {}

    for te in carrier:

        # if re.match(".*conv.*", te):

        original_weights_dict[te] = deepcopy(
            carrier[te].clone().detach().cpu().numpy()
        )

    layer_name = list(original_weights_dict.keys())
    layer_name_num = {}
    pre_layer_name = {}
    layer_shape = {}
    for name in layer_name:
        layer_name_num[name] = np.prod(original_weights_dict[name].shape)

    for i in range(len(layer_name)):
        name = layer_name[i]
        if i == 0:
            pre_layer_name[name] = 0
        else:
            pre_layer_name[name] = (
                layer_name_num[layer_name[i - 1]]
                + pre_layer_name[layer_name[i - 1]]
            )

    carrier_data = []
    for name in layer_name:
        layer_shape[name] = original_weights_dict[name].shape
        carrier_data.extend(original_weights_dict[name].reshape(-1))

    for i in range(0, length, self.bit_use):
        _from_index = i
        _to_index = _from_index + self.bit_use
        bits_to_hide = secret_bits[_from_index:_to_index]
        bits_to_hide = list(map(bool, bits_to_hide))  # type: ignore

        j = math.ceil(i / self.bit_use)
        x = FloatBinary(carrier_data[j])
        fraction_modified = list(x.fraction)
        if len(bits_to_hide) > 0:
            fraction_modified[-self.bit_use :] = bits_to_hide

        x_modified = x.modify_clone(fraction=fraction_modified)
        carrier_data[j] = x_modified.v

    outdata = {}
    for name in layer_name:

        temp = np.array(
            carrier_data[
                int(pre_layer_name[name]) : int(pre_layer_name[name])
                + int(layer_name_num[name])
            ]
        )
        outdata[name] = temp.reshape(layer_shape[name])

    for name in layer_name:

        carrier.pop(name)

        carrier[name] = torch.from_numpy(outdata[name])
        # carrier[name] = torch.nn.parameter.Parameter(

        #     torch.from_numpy(outdata[name]), requires_grad= False
        # )

    return carrier, image_size

decodewithsize(carrier, image_size)

Try to decode secret image from an NN model by LSB.

Parameters:

Name Type Description Default
carrier OrderedDict

the carrier NN model,read with io.nnmodel.read().

required
image_size tuple[int, ...]

the size of secret image.

required

Returns:

Type Description
ndarray

np.ndarray: The decode secret image.

Source code in stegobox/codec/hide_png_in_nn_model/lsb_hide_png_in_nnmodel.py
def decodewithsize(
    self, carrier: collections.OrderedDict, image_size: tuple[int, ...]
) -> np.ndarray:
    """Try to decode secret image from an NN model by LSB.

    Args:
        carrier: the carrier NN model,read with `io.nnmodel.read()`.
        image_size: the size of secret image.

    Returns:
        np.ndarray: The decode secret image.
    """
    hidden_data: List[bool] = []
    original_weights_dict: dict = {}
    for te in carrier:

        # if re.match(".*conv.*", te):
        original_weights_dict[te] = deepcopy(
            carrier[te].clone().detach().cpu().numpy()
        )

    layer_name = list(original_weights_dict.keys())
    carrier_data = []
    for name in layer_name:
        carrier_data.extend(original_weights_dict[name].reshape(-1))
    length = image_size[0] * image_size[1] * image_size[2] * 8
    for i in range(0, length, self.bit_use):

        j = math.ceil(i / self.bit_use)

        x = FloatBinary(carrier_data[j])
        hidden_bits = x.fraction[-self.bit_use :]
        hidden_data.extend(hidden_bits)
    hidden_data = np.array(hidden_data).astype(int)  # type: ignore
    recovered_image = np.array(np.packbits(hidden_data)).reshape(image_size)

    return recovered_image