In my previous post, we explored CIHMSB, a way to hide data in images without changing a single pixel by using the average value of image fragments. While it offers perfect invisibility, the main challenge is hiding capacity.
Today, we are looking at the next evolution: CIHLHF. By targeting the "Extremes"βthe lowest and highest pixel values in a fragmentβwe can pack three times more information into the same image while maintaining uncrackable security.
Academic Attribution π
This article is based on the 2023 research paper:
"A High-Capacity Coverless Information Hiding Based on the Lowest and Highest Image Fragments" by Kurnia Anggriani, Shu-Fen Chiou, Nan-I Wu, and Min-Shiang Hwang.
Published in Electronics 2023, 12, 395.
DOI: 10.3390/electronics12020395It also references the foundational CIHMSB method:
"A Novel Coverless Information Hiding Method Based on the Most Significant Bit of the Cover Image" by Lina Yang, Haiyu Deng, and Xiaocui Dang (IEEE Access, 2020).
DOI: 10.1109/ACCESS.2020.3000993
π§ Why CIHLHF?
The original CIHMSB method maps bits to the Average Intensity of a fragment. CIHLHF introduces two major upgrades:
- Dual-Value Mapping: Instead of a single average, it extracts the Minimum (L) and Maximum (H) values from each fragment.
-
Increased Payload: By extracting multiple MSB bits from both the
LandHvalues, the total bit pool per image is significantly larger. -
The Z-Key (Secret Mapping): Both the sender and receiver use a shared secret key (
Z) to randomize the fragment sequence, ensuring that even if an attacker intercepts the image, the data remains scrambled.
Like its predecessor, it remains Coverless, meaning the stego image is identical to the cover image.
π» The Implementation
This Python implementation handles the extraction of extreme values and the random Z-key permutation logic.
import numpy as np
class CIHLHF:
def __init__(self, fragment_size=8, first_msb_bits=1, second_msb_bits=1, seed=None):
self.fragment_size = fragment_size
self.first_msb_bits = first_msb_bits
self.second_msb_bits = second_msb_bits
self.seed = seed
def _text_to_bin(self, text):
binary_list = []
for char in text:
bin_char = format(ord(char), '07b')
for bit in bin_char:
binary_list.append(int(bit))
return binary_list
def _bin_to_text(self, binary_list):
text = ""
for i in range(0, len(binary_list), 7):
chunk = binary_list[i:i+7]
if len(chunk) < 7: break
chunk_str = "".join(map(str, chunk))
text += chr(int(chunk_str, 2))
return text
def _extract_min_max_msbs(self, image_array):
h, w = image_array.shape
h_trunc = h - (h % self.fragment_size)
w_trunc = w - (w % self.fragment_size)
msb_pool = []
for i in range(0, h_trunc, self.fragment_size):
for j in range(0, w_trunc, self.fragment_size):
fragment = image_array[i:i+self.fragment_size, j:j+self.fragment_size]
min_val = int(np.min(fragment))
max_val = int(np.max(fragment))
bin_min = format(min_val, '08b')
for b in range(self.first_msb_bits):
msb_pool.append(int(bin_min[b]))
bin_max = format(max_val, '08b')
for b in range(self.second_msb_bits):
msb_pool.append(int(bin_max[b]))
return msb_pool
def _generate_z_key(self, capacity):
if self.seed is None: return list(range(1, capacity + 1))
state = np.random.get_state()
np.random.seed(self.seed)
z_key = np.random.permutation(np.arange(1, capacity + 1))
np.random.set_state(state)
return z_key.tolist()
def embed(self, image_array, secret_text):
secret_bits = self._text_to_bin(secret_text)
msb_pool = self._extract_min_max_msbs(image_array)
if len(secret_bits) > len(msb_pool):
raise ValueError(f"Capacity {len(msb_pool)} bits is not enough.")
z_key = self._generate_z_key(len(msb_pool))
mapping_flag = [0] * len(secret_bits)
for i in range(len(secret_bits)):
target_index = z_key[i] - 1
mapping_flag[i] = 1 if secret_bits[i] == msb_pool[target_index] else 0
return mapping_flag
def extract(self, image_array, mapping_flag):
msb_pool = self._extract_min_max_msbs(image_array)
z_key = self._generate_z_key(len(msb_pool))
secret_bits = []
for i in range(len(mapping_flag)):
target_index = z_key[i] - 1
t_i = 1 if mapping_flag[i] == msb_pool[target_index] else 0
secret_bits.append(t_i)
return self._bin_to_text(secret_bits)
π§ͺ Replicating the Paper: Benchmark Results
To verify the effectiveness of CIHLHF, I ran a full replication suite based on the experiments conducted by Anggriani et al. Here are the results from the Python test suite:
[1] CIHLHF EXPERIMENT DEMO
Original Message: CIHLHF_REPLICATION_2026
Extraction Match: True
[2] HIDING CAPACITY ANALYSIS
Total Fragments : 4096
CIHMSB Capacity : 16384 bits
CIHLHF Capacity : 49152 bits
Improvement Factor: 3.0x
[3] SECURITY ANALYSIS (Brute-Force Complexity)
Total Fragments (Ci) : 4096
Secret Bits (Ti) : 49152
Complexity (Years) : 2.69 x 10^209255
[4] IMAGE QUALITY ASSESSMENT (IQA)
MSE : 0.0
PSNR : inf dB
SSIM : 1.0
Qi : 1.0
Analysis of Results:
- 3.0x Capacity Leap: By utilizing 12 bits per fragment (6 from the minimum value and 6 from the maximum), the capacity jumps from 16,384 bits to a massive 49,152 bits for a 512 x 512 carrier image.
Optimal Visual Quality: Since no pixels are modified, the PSNR remains infinite and the SSIM is a perfect 1.0. The image is mathematically identical to the original.
Extreme Security: The brute-force complexity for a 512x512 image reaches an astronomical 10^209255 combinations, making it physically impossible to crack without the secret mapping key
Z.
π― Conclusion
CIHLHF proves that we don't have to sacrifice capacity for security. By shifting the mapping logic from the fragment's average value to its extreme values, we can hide massive amounts of data in plain sightβunseen by both the human eye and digital steganalysis tools.
Source code and replication scripts are available on my GitHub! β
Top comments (0)