Skip to main content
  1. Posts/

Beyond the Visible: Exploring the Depths of Steganography

Sven Ruppert
Author
Sven Ruppert
20+ years of Java, specialised in Security, Vaadin and Developer Relations. When not coding, you’ll find me in the woods with an axe.
Table of Contents

Steganography is the practice of concealing a message, file, image, or video within another message, file, image, or video. Unlike cryptography, which focuses on making a message unreadable to unauthorised parties, steganography aims to hide the message’s existence. The word “steganography " is derived from the Greek words “steganos ,” meaning “covered ,” and “graphein ,” meaning “to write.”

  1. An example from history
  2. Example of using Steganography in Cybersecurity
  3. How to do it practically in Java
    1. Text Steganography:
    2. Image Steganography:
    3. Audio Steganography:
    4. Video Steganography:
    5. File Steganography:
  4. Conclusion:

An example from history
#

One notable historical example of steganography involves using invisible ink during the American Revolutionary War. The British and American forces employed various forms of steganography to conceal messages and strategic information.

In particular, the Culper Spy Ring, a clandestine network of American spies operating during the Revolutionary War, extensively used invisible ink to communicate covertly. One member of the ring, invisible ink expert James Jay, developed a secret method for creating invisible ink using simple household ingredients such as lemon juice or milk.

The Culper Spy Ring would write messages with this invisible ink between the lines of innocent-looking letters or on the blank spaces of documents. To reveal the hidden messages, the recipient would apply heat or special chemicals, causing the invisible ink to darken and become visible.

This use of steganography allowed the Culper Spy Ring to transmit crucial intelligence about British troop movements, plans, and other sensitive information without detection by British authorities. The effective use of invisible ink by the Culper Spy Ring played a significant role in aiding the American cause during the Revolutionary War and exemplifies the historical importance of steganography in espionage and covert operations.

https://youtu.be/tYeC3A57wgc

Example of using Steganography in Cybersecurity
#

A modern example of steganography in cybersecurity involves concealing malicious code within seemingly innocuous digital files to bypass security measures and deliver malware to targeted systems. Cybercriminals often employ this technique to evade detection by traditional antivirus software and intrusion detection systems.

For instance, attackers may embed malicious payloads, such as Trojans or ransomware, within images, audio files, or documents using steganography techniques. The carrier files appear unchanged to the naked eye or standard file analysis tools, making it difficult for security solutions to detect hidden malware.

Once the steganographically encoded file reaches the target system, the attacker can extract and execute the concealed payload, thereby compromising the system and initiating malicious activities.

To combat this threat, cybersecurity professionals utilise advanced threat detection technologies capable of analysing files for signs of steganographic manipulation. These tools employ various techniques, such as statistical analysis, anomaly detection, and signature-based detection, to identify suspicious patterns or deviations from expected file structures.

Additionally, cybersecurity awareness training programs educate users about the risks associated with opening files from untrusted sources and emphasise the importance of maintaining up-to-date security software to mitigate the threat of steganography-based attacks.

All Code Examples are on GitHub: https://github.com/svenruppert/Steganography

How to do it practically in Java
#

Steganography techniques can involve various methods, such as:

The source code you will find on GitHub under the following URL:

https://github.com/svenruppert/Steganography

Text Steganography:
#

Embedding secret messages within text documents by altering spacing, font styles, or using invisible characters.

Here’s a simple example of text steganography in Java using a basic technique called whitespace steganography. In this example, we’ll hide a secret message within a text document by manipulating the whitespace between words.

public class TextSteganographyExample {
4    
5      public static void main(String[] args) {
6        String binaryData = "101"; // Binary data to hide
7        String sourceText = "Hello\nWorld\nThis is a test\n"; // Source text to hide data in
8        String hiddenText = hideData(sourceText, binaryData);
9    
10       System.out.println("Text without hidden data:");
11       System.out.println(sourceText);
12       System.out.println("Text with hidden data:");
13       System.out.println(hiddenText);
14   
15       //extract the hidden message
16       String extractedData = extractData(hiddenText);
17       System.out.println("extractedData = " + extractedData);
18     }
19   
20     public static String hideData(String sourceText, String binaryData) {
21       Scanner scanner = new Scanner(sourceText);
22       StringBuilder hiddenTextBuilder = new StringBuilder();
23       int index = 0;
24       while (scanner.hasNextLine() && index < binaryData.length()) {
25         String line = scanner.nextLine();
26         // Append a space for '0', or a tab for '1'
27         char appendChar = binaryData.charAt(index) == '0' ? ' ' : '\t';
28         hiddenTextBuilder.append(line).append(appendChar).append("\n");
29         index++;
30       }
31       // If there's more of the source text, add it as is
32       while (scanner.hasNextLine()) {
33         hiddenTextBuilder.append(scanner.nextLine()).append("\n");
34       }
35       scanner.close();
36       return hiddenTextBuilder.toString();
37     }
38   
39     public static String extractData(String hiddenText) {
40       Scanner scanner = new Scanner(hiddenText);
41       StringBuilder binaryDataBuilder = new StringBuilder();
42       while (scanner.hasNextLine()) {
43         String line = scanner.nextLine();
44         if (line.endsWith("\t")) {
45           // If line ends with a tab, append '1'
46           binaryDataBuilder.append('1');
47         } else if (line.endsWith(" ")) {
48           // If line ends with a space, append '0'
49           binaryDataBuilder.append('0');
50         }
51       }
52       scanner.close();
53       return binaryDataBuilder.toString();
54     }
55   }

This code demonstrates a basic form of text steganography where a secret message is hidden within the whitespace of a text document. Note that this method needs to be revised and may not be suitable for high-security applications. Advanced techniques can involve more sophisticated manipulation of text or embedding data within specific patterns.

Image Steganography:
#

Data is concealed within digital images by modifying the least significant bits of pixel values or by embedding data within specific regions of the image where slight alterations are less noticeable.

Certainly! Here’s a simple example of image steganography in Java using LSB (Least Significant Bit) embedding. In this example, we’ll hide a secret message within the least significant bits of an image’s pixels.

This code hides a secret message within the least significant bit of each pixel in the image. The original image is saved as a new steganographic image when the message is hidden. Later, the hidden message is extracted by reading the steganographic image’s pixels and retrieving the least significant bit of the red component of each pixel. Finally, the binary message is converted back into a readable string. Replace “input_image.png” with the path to your input image.

public class ImageSteganography {
8    
9      // Method to encode a message into an image
10     public static BufferedImage encodeMessage(BufferedImage image, String message) {
11       int messageLength = message.length();
12       int imageWidth = image.getWidth();
13       int imageHeight = image.getHeight();
14       int[] imagePixels = new int[imageWidth * imageHeight];
15       image.getRGB(0, 0, imageWidth, imageHeight, imagePixels, 0, imageWidth);
16   
17       // Convert the message into a binary string
18       StringBuilder binaryMessage = new StringBuilder();
19       for (char character : message.toCharArray()) {
20         binaryMessage.append(String.format("%8s", Integer.toBinaryString(character)).replaceAll(" ", "0"));
21       }
22   
23       // Encode the message length at the beginning
24       String messageLengthBinary = String.format("%32s", Integer.toBinaryString(messageLength)).replace(' ', '0');
25       binaryMessage.insert(0, messageLengthBinary);
26   
27       // Encode the binary message into the image
28       for (int i = 0; i < binaryMessage.length(); i++) {
29         int pixel = imagePixels[i];
30         int blue = pixel & 0xFF;
31         int green = (pixel >> 8) & 0xFF;
32         int red = (pixel >> 16) & 0xFF;
33         int alpha = (pixel >> 24) & 0xFF;
34   
35         // Modify the LSB of the blue part of the pixel to match the current bit of the message
36         blue = (blue & 0xFE) | (binaryMessage.charAt(i) - '0');
37         int newPixel = (alpha << 24) | (red << 16) | (green << 8) | blue;
38   
39         imagePixels[i] = newPixel;
40       }
41   
42       BufferedImage newImage = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
43       newImage.setRGB(0, 0, imageWidth, imageHeight, imagePixels, 0, imageWidth);
44       return newImage;
45     }
46     // Method to decode the hidden message from an image
47     public static String decodeMessage(BufferedImage image) {
48       int imageWidth = image.getWidth();
49       int imageHeight = image.getHeight();
50       int[] imagePixels = new int[imageWidth * imageHeight];
51       image.getRGB(0, 0, imageWidth, imageHeight, imagePixels, 0, imageWidth);
52   
53       // Extract the length of the message
54       StringBuilder messageLengthBinary = new StringBuilder();
55       for (int i = 0; i < 32; i++) {
56         int pixel = imagePixels[i];
57         int blue = pixel & 0xFF;
58         messageLengthBinary.append(blue & 1);
59       }
60       int messageLength = Integer.parseInt(messageLengthBinary.toString(), 2);
61   
62       // Extract the binary message from the image
63       StringBuilder binaryMessage = new StringBuilder();
64       for (int i = 32; i < 32 + messageLength * 8; i++) {
65         int pixel = imagePixels[i];
66         int blue = pixel & 0xFF;
67         binaryMessage.append(blue & 1);
68       }
69   
70       // Convert the binary message to string
71       StringBuilder message = new StringBuilder();
72       for (int i = 0; i < binaryMessage.length(); i += 8) {
73         String byteString = binaryMessage.substring(i, i + 8);
74         int charCode = Integer.parseInt(byteString, 2);
75         message.append((char) charCode);
76       }
77   
78       return message.toString();
79     }
80     public static void main(String[] args) throws IOException {
81       File originalImageFile = new File("_data/_DSC1259.png"); // Specify the path to the input image
82       BufferedImage originalImage = ImageIO.read(originalImageFile);
83       String secretMessage = "Secret message goes here";
84   
85       // Encode the message into the image
86       BufferedImage encodedImage = encodeMessage(originalImage, secretMessage);
87   
88       // Save the encoded image
89       File outputImageFile = new File("_data/_DSC1259_with-data.png"); // Specify the path to the output image
90       ImageIO.write(encodedImage, "png", outputImageFile);
91       System.out.println("The message has been encoded into the image.");
92   
93   
94       File imageFile = new File("_data/_DSC1259_with-data.png"); // Specify the path to the encoded image
95       BufferedImage image = ImageIO.read(imageFile);
96   
97       // Decode the message from the image
98       String decodedMessage = decodeMessage(image);
99       System.out.println("The hidden message is: " + decodedMessage);
100      calculatingMaxInfoAmount("_data/_DSC1259_with-data.png");
101    }
102    private static void calculatingMaxInfoAmount(String pathname){
103      try {
104        File imageFile = new File(pathname); // Specify the path to your image
105        BufferedImage image = ImageIO.read(imageFile);
106        int imageWidth = image.getWidth();
107        int imageHeight = image.getHeight();
108        long maxBits = (long) imageWidth * imageHeight; // Maximum bits that can be stored
109        long maxBytes = maxBits / 8; // Convert bits to bytes
110  
111        System.out.println("Maximum information that can be stored in bytes: " + maxBytes);
112      } catch (IOException e) {
113        throw new RuntimeException(e);
114      }
115    }
116  }

Audio Steganography:
#

Hiding information within audio files by modifying the least significant bits of audio samples or by exploiting imperceptible frequencies.

Here’s a basic example of audio steganography in Java using LSB (Least Significant Bit) embedding. In this example, we’ll hide a secret message within the least significant bits of the audio samples.

public class AudioSteganography {
6    
7      public static void main(String[] args) {
8        File inputFile = new File("_data/Security - 2024.06 - What is Steganography-16bit-single-track_A01_L.wav");
9        File outputFile = new File("_data/output.wav");
10       String message = "Secret Message";
11       hideMessage(inputFile, outputFile, message);
12   
13       File inputFileEncoded = new File("_data/output.wav"); // This is the file with the hidden message
14       String extractedMessage = extractMessage(inputFileEncoded, 14); // Assuming we know the message length
15       System.out.println("Extracted Message: " + extractedMessage);
16     }
17   
18     public static void hideMessage(File inputFile, File outputFile, String message) {
19       try {
20         // Convert the message to a binary string
21         byte[] messageBytes = message.getBytes();
22         StringBuilder binaryMessage = new StringBuilder();
23         for (byte b : messageBytes) {
24           binaryMessage.append(String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0'));
25         }
26         String messageBinary = binaryMessage.toString();
27   
28         // Load the audio file
29         AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(inputFile);
30         AudioFormat format = audioInputStream.getFormat();
31         byte[] audioBytes = audioInputStream.readAllBytes();
32   
33         // Hide the message in the LSB of the audio bytes
34         int messageIndex = 0;
35         for (int i = 0; i < audioBytes.length && messageIndex < messageBinary.length(); i += 2) { // Skip every other byte for 16-bit samples
36           if (messageBinary.charAt(messageIndex) == '1') {
37             audioBytes[i] = (byte) (audioBytes[i] | 1); // Set LSB to 1
38           } else {
39             audioBytes[i] = (byte) (audioBytes[i] & ~1); // Set LSB to 0
40           }
41           messageIndex++;
42         }
43   
44         // Write the modified samples to a new file
45         ByteArrayInputStream bais = new ByteArrayInputStream(audioBytes);
46         AudioInputStream outputAudioInputStream = new AudioInputStream(bais, format, audioBytes.length / format.getFrameSize());
47         AudioSystem.write(outputAudioInputStream, AudioFileFormat.Type.WAVE, outputFile);
48   
49         System.out.println("The message has been hidden in " + outputFile.getName());
50   
51       } catch (UnsupportedAudioFileException | IOException e) {
52         e.printStackTrace();
53       }
54     }
55   
56     public static String extractMessage(File inputFile, int messageLength) {
57       try {
58         // Load the audio file
59         AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(inputFile);
60         byte[] audioBytes = audioInputStream.readAllBytes();
61   
62         // Extract bits to reconstruct the message binary string
63         StringBuilder messageBinary = new StringBuilder();
64         for (int i = 0; i < messageLength * 8 * 2; i += 2) { // Assuming 16-bit samples, adjust for actual sample size
65           byte b = audioBytes[i];
66           int lsb = b & 1; // Extract the LSB
67           messageBinary.append(lsb);
68         }
69   
70         // Convert the binary string to text
71         StringBuilder message = new StringBuilder();
72         for (int i = 0; i < messageBinary.length(); i += 8) {
73           String byteString = messageBinary.substring(i, i + 8);
74           int charCode = Integer.parseInt(byteString, 2);
75           message.append((char) charCode);
76         }
77   
78         return message.toString();
79   
80       } catch (UnsupportedAudioFileException | IOException e) {
81         e.printStackTrace();
82       }
83   
84       return null;
85     }
86   }

This code hides a secret message within the input audio file’s least significant bit of each audio sample. The modified audio data is then saved as a new steganographic audio file. Later, the hidden message is extracted by reading the least significant bit of each audio sample in the steganographic audio file. Replace filename and path with the path to your input audio file.

Video Steganography:
#

Concealing data within video files by manipulating frames or embedding data in specific segments.

Below is a basic example of video steganography in Java using LSB (Least Significant Bit) embedding. In this example, we’ll hide a secret message within the least significant bits of the blue pixel values of video frames.

public class VideoSteganography {
14     //Should be extracted from the orig video
15     public static final int FPS = 50;
16     public static final String INPUT_FILE = "input.mp4";
17     public static final String OUTPUT_FILE = "output.mp4";
18   
19     public static void main(String[] args) throws Exception {
20       FileChannelWrapper fileChannelWrapperIN = NIOUtils.readableChannel(new File(INPUT_FILE));
21       File outputFile = new File(OUTPUT_FILE);
22       // Create a SequenceEncoder for the output file at 25 frames per second
23       SequenceEncoder encoder = SequenceEncoder.createSequenceEncoder(outputFile, FPS);
24       FrameGrab grab = FrameGrab.createFrameGrab(fileChannelWrapperIN);
25       Picture picture;
26       // to improve to process
27       // 0. detect the FPS from the input video
28       // 1. calculate the max amount of possible bytes that can be encoded
29       // 2. calculate the max amount of bytes that can be stored in each frame
30       // 3. split the message into chunks to fit into a frame
31       // 4. define a sequence to mark the end of the message
32       
33       while (null != (picture = grab.getNativeFrame())) {
34         // Here, convert the Picture to BufferedImage
35         BufferedImage frame = AWTUtil.toBufferedImage(picture);
36         // Modify the frame to encode part of the message
37         BufferedImage encodedMessageOnBlue = encodeMessageOnBlue(frame, toBinaryString("Hello Message"));
38         // Convert BufferedImage to Picture (required by JCodec)
39         Picture pic = AWTUtil.fromBufferedImage(encodedMessageOnBlue, ColorSpace.RGB);
40         // Encode the modified frame back into the video
41         encoder.encodeNativeFrame(pic);
42       }
43       // Finalize video encoding and clean up
44       NIOUtils.closeQuietly(fileChannelWrapperIN);
45       // Finalize and close the encoder (important!)
46       encoder.finish();
47     }
48   
49     private static String toBinaryString(String message) {
50       StringBuilder binary = new StringBuilder();
51       for (char character : message.toCharArray()) {
52         binary.append(String
53             .format("%8s", Integer.toBinaryString(character))
54             .replace(' ', '0'));
55       }
56       return binary.toString();
57     }
58   
59     private static BufferedImage encodeMessageOnBlue(BufferedImage image, String binaryMessage) {
60       int messageIndex = 0;
61       int messageLength = binaryMessage.length();
62   
63       for (int y = 0; y < image.getHeight() && messageIndex < messageLength; y++) {
64         for (int x = 0; x < image.getWidth() && messageIndex < messageLength; x++) {
65           int pixel = image.getRGB(x, y);
66           int alpha = (pixel >> 24) & 0xFF;
67           int red = (pixel >> 16) & 0xFF;
68           int green = (pixel >> 8) & 0xFF;
69           int blue = pixel & 0xFF;
70   
71           // Modify the LSB of the blue component
72           blue = (blue & 0xFE) | (binaryMessage.charAt(messageIndex) - '0');
73           messageIndex++;
74   
75           // Reconstruct the pixel and set it back
76           int newPixel = (alpha << 24) | (red << 16) | (green << 8) | blue;
77           image.setRGB(x, y, newPixel);
78         }
79       }
80       return image;
81     }
82   }

File Steganography:
#

Embedding files within other files, such as hiding a document within another document.

File steganography with PDF files involves hiding one PDF file within another PDF file. In this example, we’ll hide the contents of one PDF file within the metadata of another PDF file. Specifically, we’ll utilise the “Keywords " metadata field of PDFs to embed the content of a secret PDF file.

Here’s the Java code to achieve this using the Apache PDFBox library:

9    
10     public class HideMessageInPDF {
11       public static void main(String[] args) {
12         try {
13           // Load an existing PDF document
14           File file = new File("_data/ReadME.pdf");
15           PDDocument document = PDDocument.load(file);
16           // Retrieve the document's metadata
17           PDDocumentInformation info = document.getDocumentInformation();
18           // Add a hidden message to the metadata
19           // You could use a less obvious key than "HiddenMessage" to make it less detectable
20           info.setCustomMetadataValue("HiddenMessage", "This is a secret message");
21           // Save the modified document
22           document.save("_data/ReadME_with-data.pdf");
23           document.close();
24           System.out.println("Hidden message added to the PDF metadata.");
25         } catch (IOException e) {
26           e.printStackTrace();
27         }
28   
29         try {
30           // Load the PDF document
31           File file = new File("_data/ReadME_with-data.pdf");
32           PDDocument document = PDDocument.load(file);
33           // Access the document's metadata
34           PDDocumentInformation info = document.getDocumentInformation();
35           // Retrieve the hidden message from the metadata
36           String hiddenMessage = info.getCustomMetadataValue("HiddenMessage");
37           System.out.println("Hidden message: " + hiddenMessage);
38           document.close();
39         } catch (IOException e) {
40           e.printStackTrace();
41         }
42       }
43     }

In this code:

1. The “hidePDF " function loads the source PDF document, converts the content of the secret PDF file to a Base64 encoded string, and embeds it into the “Keywords " metadata field of the source PDF file.

2. The “extractPDF " function loads the steganographic PDF document, extracts the Base64 encoded content from the “Keywords " metadata field, decodes it, and writes it to an output PDF file.

Make sure to replace every path to files with the appropriate file paths in your system. Additionally, you’ll need to include the Apache PDFBox library in your project to use its functionalities.

Conclusion:
#

As explored in this paper, steganography is a fascinating field with a rich history and diverse applications. By concealing information from ordinary data, steganography provides a powerful means of covert communication and data protection. Steganography continues to evolve alongside technological advancements, from ancient techniques of hiding messages in wax tablets to modern digital methods embedded within multimedia files.

While steganography offers numerous benefits in fields like digital watermarking, copyright protection, and secure communication, it also presents challenges and ethical considerations. Due to its subtle nature, detecting steganographic content remains a significant challenge, requiring advanced algorithms and tools for effective analysis.

As technology continues to advance, the importance of understanding steganography becomes increasingly critical. It underscores the need for robust security measures balanced with respect for privacy rights. By delving into the principles and techniques of steganography, researchers and practitioners can develop more effective strategies for exploiting and defending against covert communication methods.

In conclusion, steganography represents a dynamic and intriguing aspect of information security, with implications spanning various domains. By embracing its complexities and exploring its potential applications, we can navigate the intricate landscape of digital communication with greater insight and resilience.

Related

Secure Coding Practices - Input Validation

What is - Input Validation? # Input validation is a process used to ensure that the data provided to a system or application meets specific criteria or constraints before it is accepted and processed. The primary goal of input validation is to improve the reliability and security of a system by preventing invalid or malicious data from causing errors or compromising the system’s integrity.

EclipseStore High-Performance-Serializer

I will introduce you to the serializer from the EclipseStore project and show you how to use it to take advantage of a new type of serialization. Since I learned Java over 20 years ago, I wanted to have a simple solution to serialize Java-Object-Graphs, but without the serialization security and performance issues Java brought us. It should be doable like the following…

Contextual Analysis in Cybersecurity

Contextual analysis in cybersecurity involves examining events, actions, or data within the broader context of an organization’s IT environment. It is a critical component of a proactive cybersecurity strategy, aiming to understand the significance of activities by considering various factors surrounding them. This multifaceted approach helps cybersecurity professionals identify and respond to potential threats effectively.