Encrypted Messages with Python

Secret Messages: Transmitting Encrypted Messages as Audio

Sending messages with end-to-end encryption is standard for many messaging apps like Signal, Threema, and WhatsApp. But how about adding some playful retro charm? In this project, you convert text messages into sounds and send them as audio file attachments in an email. The recipient can decrypt these audio files using a previously agreed keyword and read the message. To outsiders, it sounds like a series of tones.

Two Python scripts are used here—one for the sender and one for the recipient. Gmail serves as the email provider.

Setting up Gmail for Sending Emails

First, you need an email provider you can access from the sender’s Python script to use for sending emails. Gmail is a good choice because it’s easy to set up. Important: Even if you already have a Gmail address, set up a new one for this project. This ensures that errors like sending many emails in succession don’t temporarily block your account.

You’ll learn in this tutorial how to set up a Gmail address and configure it for sending from a Python script.

Required Python Libraries

You’ll use the numpy and scipy libraries. Numpy is used for numerical operations needed to analyze frequencies in audio data. Scipy’s wavfile.read function reads the created WAV file, extracting the audio data and sample rate. These libraries aren’t included by default in Python, so you’ll need to install them manually with:

pip install numpy scipy


Script for the Sender of Encrypted Messages

Once you’ve set up a Gmail address and installed the necessary libraries, proceed with the sender’s Python script. Here’s the complete code:

___STEADY_PAYWALL___

import numpy as np
from scipy.io.wavfile import write
import smtplib
from email.message import EmailMessage
import socket

# User-editable parameters
user_parameters = {
    "sample_rate": 44100,  # Sampling rate (Hz)
    "bit_duration": 0.1,  # Duration of a bit (in seconds)
    "freq_0": 1000,       # Frequency for "0" (Hz)
    "freq_1": 2000,       # Frequency for "1" (Hz)
    "encryption_key": "secret_key",  # Encryption key (both parties must use the same key)
    "sender_email": "Sender's address",  # Sender's email address
    "sender_password": "App password",   # App password for sender's email
    "receiver_email": "Recipient's address",  # Recipient's email address
    "email_subject": "Subject",          # Subject of the email
    "email_body": "Here is an encrypted message for you.",  # Email body content
    "wav_filename": "message.wav"        # Filename of the WAV file to be saved
}

# Function to create a tone for a bit
def generate_tone(frequency, duration, sample_rate):
    t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
    return np.sin(2 * np.pi * frequency * t)

# Convert message to binary data
def text_to_binary(text):
    binary_data = ''.join(format(ord(char), '08b') for char in text)
    return binary_data

# Encrypt message with the key
def encrypt_message(text, key):
    encrypted_message = ""
    for i in range(len(text)):
        encrypted_char = chr(ord(text[i]) ^ ord(key[i % len(key)]))
        encrypted_message += encrypted_char
    return encrypted_message

# Convert message to modulated audio data
def encode_to_audio(binary_data, bit_duration, sample_rate, freq_0, freq_1):
    audio = np.array([])
    for bit in binary_data:
        if bit == '0':
            audio = np.append(audio, generate_tone(freq_0, bit_duration, sample_rate))
        else:
            audio = np.append(audio, generate_tone(freq_1, bit_duration, sample_rate))
    return audio

# Function to send the WAV file via email
def send_email_with_attachment(receiver_email, subject, body, attachment_path):
    sender_email = user_parameters["sender_email"]
    sender_password = user_parameters["sender_password"]
    msg = EmailMessage()
    msg['From'] = sender_email
    msg['To'] = receiver_email
    msg['Subject'] = subject
    msg.set_content(body)

    with open(attachment_path, 'rb') as attachment:
        msg.add_attachment(attachment.read(), maintype='audio', subtype='wav', filename=attachment_path)

    try:
        with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
            smtp.login(sender_email, sender_password)
            smtp.send_message(msg)
    except socket.gaierror:
        print("Error: Could not reach the SMTP server. Please check the server address.")
    except smtplib.SMTPAuthenticationError:
        print("Error: Authentication failed. Please check your email address and password.")
    except Exception as e:
        print(f"An unexpected error occurred: {e}")

# Main program
if __name__ == "__main__":
    # Create message
    email_message = "Das Pferd frisst keinen Gurkensalat."
    # Encrypt message
    encrypted_message = encrypt_message(email_message, user_parameters["encryption_key"])
    # Convert encrypted message to binary data
    binary_data = text_to_binary(encrypted_message)
    print(f"Binary data of the message: {binary_data[:64]}...")

    # Convert binary data to audio signals
    audio_data = encode_to_audio(binary_data, user_parameters["bit_duration"], user_parameters["sample_rate"], user_parameters["freq_0"], user_parameters["freq_1"])

    # Save WAV file
    wav_filename = user_parameters["wav_filename"]
    write(wav_filename, user_parameters["sample_rate"], (audio_data * 32767).astype(np.int16))
    print(f"WAV file '{wav_filename}' created successfully!")

    # Send the WAV file via email
    send_email_with_attachment(user_parameters["receiver_email"], user_parameters["email_subject"], user_parameters["email_body"], wav_filename)
    print(f"WAV file '{wav_filename}' sent successfully by email!")

How the Script Works

The sender script encrypts a message, converts it to binary data, and generates an audio file that is then emailed:

User-editable variables: At the beginning and in a part lower in the code, there are settings you must configure before sending. These include the secret key (the recipient needs the same key in their script), email addresses, Gmail password, and the message to encrypt.

Encrypting the message: The encrypt_message() function uses a simple XOR encryption to encrypt the message. It combines each character of the message with a character of the key.

Converting message to binary data: The encrypted message is converted to binary data.

Creating a tone for each bit: The generate_tone() function creates a sine wave for each bit (“0” or “1”).

Encoding the message to audio data: The encode_to_audio() function converts the binary message to audio data by creating corresponding tones for each bit.

Creating and sending the audio file: The audio data is saved as a 16-bit integer WAV file and sent via email using the send_email_with_attachment() function.

Add your data to the script and include the message you want to send. Then run the script once—in the terminal, you should see a message indicating that the email with the audio file has been sent to your recipient.

Secret message sent notification in the Terminal

The Script for the Recipient

The recipient needs a script to convert the encrypted audio data back into text. Here’s the complete Python script:

import numpy as np
from scipy.io.wavfile import read

# User-editable parameters
user_parameters = {
    "sample_rate": 44100,  # Sampling rate (Hz)
    "bit_duration": 0.1,   # Duration of a bit (seconds)
    "freq_0": 1000,        # Frequency for "0" (Hz)
    "freq_1": 2000,        # Frequency for "1" (Hz)
    "encryption_key": "secret_key",  # Encryption key (both parties must use the same key)
    "wav_filename": "message.wav"    # Name of the WAV file to be read
}

# Decode audio data to binary data
def decode_audio_to_binary(audio_data, bit_duration, sample_rate, freq_0, freq_1):
    bit_length = int(sample_rate * bit_duration)
    binary_data = ""
    for i in range(0, len(audio_data), bit_length):
        segment = audio_data[i:i + bit_length]
        fft_result = np.fft.fft(segment)
        freqs = np.fft.fftfreq(len(segment), 1 / sample_rate)
        peak_freq = abs(freqs[np.argmax(np.abs(fft_result))])
        if abs(peak_freq - freq_0) < abs(peak_freq - freq_1):
            binary_data += "0"
        else:
            binary_data += "1"
    return binary_data

# Convert binary data to text
def binary_to_text(binary_data):
    text = ""
    for i in range(0, len(binary_data), 8):
        byte = binary_data[i:i + 8]
        if len(byte) == 8:
            text += chr(int(byte, 2))
    return text

# Decrypt message
def decrypt_message(encrypted_text, key):
    decrypted_message = ""
    for i in range(len(encrypted_text)):
        decrypted_char = chr(ord(encrypted_text[i]) ^ ord(key[i % len(key)]))
        decrypted_message += decrypted_char
    return decrypted_message

if __name__ == "__main__":
    # Read WAV file
    sample_rate, audio_data = read(user_parameters["wav_filename"])
    if audio_data.ndim > 1:
        audio_data = audio_data[:, 0]  # Use only one channel if stereo
    audio_data = audio_data / 32767.0  # Normalize to the range [-1, 1]

    # Decode audio data to binary
    binary_data = decode_audio_to_binary(audio_data, user_parameters["bit_duration"], user_parameters["sample_rate"], user_parameters["freq_0"], user_parameters["freq_1"])
    print(f"Binary data of the message: {binary_data[:64]}...")  # Show first 64 bits for debugging

    # Convert binary data to encrypted text
    encrypted_message = binary_to_text(binary_data)

    # Decrypt the message
    decrypted_message = decrypt_message(encrypted_message, user_parameters["encryption_key"])
    print(f"Decrypted message: {decrypted_message}")

Explanation

Parameters: sample_rate, bit_duration, and frequencies must match what the sender used. The encryption_key must also be the same.


Process: The script reads the audio file, decodes to binary, converts binary to text, and decrypts using the key.


Improvements

You can now send messages encrypted as audio and be fairly certain that only someone with the appropriate script and the right key can decrypt them. How could this be improved? You may have noticed that the generated WAV files are quite large—the relatively short message in the example above is already 2.6 MB. Converting to MP3 could help ensure your message is not too large for an email attachment.

We don't track you. Enjoy your cookies while making awsome electronic projects!