LSB Steganography with Encryption in Python using PNG Files

Introduction

To read more about LSB Steganography visit: LSB Steganography in Python using PNG Files

This software implements LSB Steganography, as described and demonstrated in the link above, and in addition, message encryption. This way a user can encrypt their hidden message using Fernet, with a passphrase.

  1. Hiding:
    • Takes an input image and a message to be hidden.
    • Encrypts the message for enhanced security.
    • Converts the encrypted message into a binary string.
    • Embeds the binary message into the least significant bits (LSB) of the image’s pixels.
    • Saves the modified image as the output.
  2. Retrieving:
    • Takes the modified image as input.
    • Extracts the binary message hidden within the image’s LSBs.
    • Converts the binary string back to text.
    • Decrypts the message to reveal the original secret.

The first addition to the original LSB Steganography program is the ability to generate an encryption key from a passphrase. The salt is set to a static value within the software. This is because we do not store the salt anywhere else that it can be retrieved from during decryption and encryption. This can be changed but you’ll need to use the correct salt which is whatever you chose at the time of original encryption.

def get_key() -> bytes:
    passphrase = getpass.getpass(prompt="ENTER PASSPHRASE: ", stream=None)

    salt = b'1234567812345678'
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt,
        iterations=480000,
    )

    key = base64.urlsafe_b64encode(kdf.derive(passphrase.encode()))
    return key

This is done in the “get_key()” function, above.

  1. Get User Passphrase:
    • CallsĀ getpass.getpass(prompt="ENTER PASSPHRASE: ", stream=None)
      • Displays the prompt “ENTER PASSPHRASE: ” to the user.
      • Securely reads the user’s inputted passphrase without echoing it to the screen.
      • Stores the input in the passphrase variable.
  2. Define Salt:
    • salt = b'1234567812345678'
    • Creates a byte string to use as the salt. Salts are random data added during key derivation to make the output less predictable and protect against attacks like rainbow tables.
  3. Initialize Key Derivation Function (KDF):
    • kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=480000)
      • Creates a KDF object using the PBKDF2HMAC algorithm.
      • Specifies the SHA-256 hash function.
      • Sets the desired output key length to 32 bytes.
      • Uses the previously defined salt.
      • Sets a high number of iterations (480000) to deliberately slow down the key derivation process, making brute-force attacks more difficult.
  4. Derive Key:
    • key = base64.urlsafe_b64encode(kdf.derive(passphrase.encode()))
      • Calls kdf.derive(passphrase.encode()) to generate the key material from the user’s passphrase using the configured KDF settings.
      • Encodes the derived key using base64 (urlsafe variant) to make it easier to transmit or store as text.
  5. Return Key:
    • return key
      • Returns the base64-encoded key to be used for encryption or decryption elsewhere in the code.

The second addition to the code is in the text_to_binary function.

def text_to_binary(message: str) -> str:
    binary_string = ''

    f = Fernet(get_key())

    message = base64.urlsafe_b64encode(f.encrypt(message.encode())).decode()

    for char in message:
        binary_char = format(ord(char), '08b')
        binary_string += binary_char

    return binary_string

Notice the bolded code lines.

  1. Generate an encryption key:f = Fernet(get_key())
    • Calls the get_key() function (explained earlier) to get a key for encryption.
    • Creates a Fernet encryption object, ready to encrypt your message.
  2. Encrypt the message:message = base64.urlsafe_b64encode(f.encrypt(message.encode())).decode()
    • Encodes the message into bytes using message.encode().
    • Encrypts the encoded message using the Fernet object f.
    • Base64 encodes the encrypted data (urlsafe variant) for easier text representation.
    • Decodes the base64 encoded string back to regular text.

The third addition to the original tuckerlsb code is in the reveal_message method.

    def reveal_message(self, input_image: str) -> str:

        image = Image.open(input_image)
        pixels = image.load()

        binary_message = ''

        for row in range(image.size[0]):
            for column in range(image.size[1]):
                pixel = pixels[column, row]
                least_significant_bit = pixel[0] & 1
                binary_message += str(least_significant_bit)

                if binary_message.endswith(self.delimiter):
                    binary_message = binary_message[:-16]  # Remove the delimiter
                    message = binary_to_text(binary_message)

                    f = Fernet(get_key())
                    message = f.decrypt(base64.urlsafe_b64decode(message)).decode()

                    return message

        return "ERROR"
  1. Decrypt Message:
    • f = Fernet(get_key()): Creates a Fernet decryption object using the key generated by the get_key() function.
    • message = f.decrypt(base64.urlsafe_b64decode(message)).decode(): Decrypts the message using the Fernet object and decodes the base64 encoded decrypted data.

Downloads

LSB Steganography GitHub

tuckerenc x86_64 Windows exe

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisement -spot_img

Latest article