Least Significant Bit in Steganography web application

In the realm of data security and privacy, there exists a fascinating technique called steganography. Derived from the Greek words “steganos” meaning “covered” or “hidden,” and “graphein” meaning “writing,” steganography is the art and science of concealing secret information within seemingly innocuous carriers such as images, audio files, or even text. It allows individuals to exchange hidden messages in plain sight, thereby ensuring confidentiality and anonymity. In this blog post, we delve into the world of steganography, exploring its principles, techniques, and real-world applications.

On the image above we can see that there are use to least significant bits. The advantage is we can encode a larger message but in the same time we are raising the probability of revealing it but not about that much. The more significant bit we change, bigger influence it has on the pixel color.

Least Significant Bit

Each pixel is made is 24 bits which makes up 3 bytes. Each byte is allocated to one of the pixel colors which is always R or G or B that makes up a color when combined as shown on the image below. Since each byte consists of eight bits and PNG file format use Big Endian, also called a Network endian which means that the most significant bit starts on the left side and less significants follow up to least significant on the right. If we change change the first, most significant bit we would affect the pixel color which would be pretty obvious when someone looks on the image, therefore we have to change the least significant one so no-one can see that with naked eye.

Utilizing the Least Significant Bit

To embed information without noticeable visual changes, the LSB technique targets the least significant bit of each color component. As the LSB has the smallest impact on the color value, modifying it allows for subtle changes that are difficult to detect by the human eye. By replacing the LSB of each color component with bits of the secret data, the image can be used to transmit concealed information.

Preserving Visual Integrity

By altering only the LSB, the overall visual integrity of the image remains intact. The changes introduced by the modified LSB are minimal and imperceptible to casual observers. However, specialized steganalysis techniques may be employed to detect the presence of hidden data, highlighting the importance of using strong encryption and robust steganographic methods.

Each character of the message in case of text, we have to convert to its binary character so it has same form as image and then each birth replaces the least significant bit in the image as we mentioned earlier. PNG images provide a suitable platform for applying LSB steganography due to their Big Endian byte order and the 24-bit composition of each pixel. Through careful manipulation of the least significant bit in the RGB color components, secret information can be hidden within the image while maintaining its visual appearance. Understanding the principles and limitations of LSB steganography is crucial for both concealing and extracting information, ensuring secure communication in various contexts.


                    from PIL import Image
                    import base64
                    
                    class Key:
                        key = int
                        attr = 0
                    
                    def Embedder(image_name, message):
                        if len(message) < 5:
                            raise ValueError("Message must be at least 3 characters.")
                        
                        # Encode the message to base64 and convert to binary
                        encoded_message = base64.b64encode(message.encode("utf-8")).decode("utf-8")
                        message_bits = ''.join(format(ord(char), '08b') for char in encoded_message)
                        
                        # Append a unique padding sequence to mark the end of the message
                        end_padding = "10000000"  # 8-bit padding
                        message_bits += end_padding  # Append padding to the message bits
                        
                        Key.key = len(message_bits)  # Track the total number of bits
                        
                        print(f"[Embedder] Encoding '{message}' with bit length: {Key.key}")
                        
                        # Open the image and check capacity
                        image = Image.open('images/' + image_name).convert('RGB')
                        if len(message_bits) > (image.size[0] * image.size[1] * 3):
                            raise ValueError("Message is larger than the image capacity!")
                        
                        # Embed message bits into the image
                        image_pixels = list(image.getdata())
                        bitindex = 0
                        for i in range(len(message_bits) // 3):
                            pixel = list(image_pixels[i])
                            for j in range(3):
                                pixel[j] = (pixel[j] & 0xFE) | int(message_bits[bitindex])
                                bitindex += 1
                            image_pixels[i] = tuple(pixel)
                        
                        modified_image = Image.new(image.mode, image.size)
                        modified_image.putdata(image_pixels)
                        
                        Key.attr += 1
                        modified_image.save("stegged/steg" + str(Key.attr) + image_name)
                        
                        return str(Key.key), "steg" + str(Key.attr) + image_name
                    
                    
                    
                            
                    def Extractor(picture, key):
                        modified_image = Image.open("to_decode/" + picture)
                        
                        extracted_bits = ""
                        target_bits = int(key)
                        key_index = 0
                        
                        print(f"[Extractor] Using key (bit length): {target_bits}")
                        
                        # Extract up to the target bits
                        for pixel in modified_image.getdata():
                            for channel in pixel:
                                if key_index < target_bits:
                                    extracted_bits += str(channel & 0x01)
                                    key_index += 1
                                else:
                                    break
                            if key_index >= target_bits:
                                break
                        
                        # Locate padding to determine end of the actual message
                        end_padding = "10000000"
                        padding_index = extracted_bits.find(end_padding)
                        if padding_index != -1:
                            extracted_bits = extracted_bits[:padding_index]  # Remove padding from bits
                        
                        print(f"[Extractor] Extracted bit length after removing padding: {len(extracted_bits)}")
                        
                        # Convert bits to characters
                        encoded_message = ""
                        for i in range(0, len(extracted_bits), 8):
                            byte = extracted_bits[i:i+8]
                            encoded_message += chr(int(byte, 2))
                        
                        try:
                            # Decode the base64 message
                            plainmessage = base64.b64decode(encoded_message).decode("utf-8")
                            return plainmessage
                        except Exception as e:
                            print(f"[Extractor Error] {e}")
                            return "Invalid key"