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.
- 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.
- 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.
- Steganography password protect
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.
- 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.
- Calls
- 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.
- 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.
- 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.
- Calls
- 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.
- 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.
- Calls the
- 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.
- Encodes the message into bytes using
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"
- Decrypt Message:
f = Fernet(get_key())
: Creates a Fernet decryption object using the key generated by theget_key()
function.message = f.decrypt(base64.urlsafe_b64decode(message)).decode()
: Decrypts the message using the Fernet object and decodes the base64 encoded decrypted data.
Steganography password protect