Steganography
"security- Hiding a secret message within a larger one in such a way that others can not discern the presence or contents of the hidden message. For a common example, a message might be hidden within an image by changing the least-significant bits to be the message bits."

A while back we discussed ideas of hiding a message in a posting or other text. I thought of the idea of using a hash of each sentence to encode one bit.

Since any change gives an essentially random hash, you manually make trivial changes to the text until it gives the correct result. Any trivial change has a 50% chance of giving the desired result.

You could use a keyed hash (HMAC) so you can't even detect the presence of the hidden message unless you knew the key! In this proof-of-concept, I'm just using SHA-1. Since the hidden message can itself be compressed and encoded first, the key is not needed. But, since the point is to hide the message, not just make it unreadable, hiding is good.

The main drawback is the large amount of material needed. It takes one whole sentence per bit! We could use two bit, but it would be harder to massage the text.

Many messages are short, though. "One (lamp) if by land, two if by sea" for a famous example.

This test program reads a file (one "unit" per line), and annotates the lines. You alter the text of those lines that don't match, and run it again. In a complete solution, the program would break the source text into units. I'm using each sentence as a unit. Chopping the text at the sentence boundaries is another problem. I'm also using literal \n's in the HTML where my units are, so you can test this easily. That is, you don't have to re-break it up into sentences to see what message I have hidden in this very posting. (The first 24 sentences (3 bytes) is significant. HTML markup are ignored.)

use strict; use warnings; use Carp; use Digest::SHA1 'sha1'; my $textfile= $ARGV[0]; my $messagefile= $ARGV[1]; # File::Slurp doesn't do binmode. sub read_file { my ($file, $discipline) = @_; my $F; my $r; my (@r); open($F, "<$file") || croak "open $file: $!"; binmode ($F, $discipline) if defined $discipline; @r = <$F>; close($F); return @r if wantarray; return join("",@r); } # read in the files. my @text= read_file ($textfile); my $message= read_file ($messagefile, ":raw") if $messagefile; sub digest { my ($line, $desired, $n)= @_; my $result= vec(sha1($line), 1, 1); my $indicator= $desired==$result ? "ok" : "**"; return "$desired $result $indicator"; } # figure each line open (my $output, ">$textfile") or die; my $n= 0; # count text lines / message bits foreach my $line (@text) { # if the line begins with '.[' it has been processed before. Strip + that stuff off. $line =~ s/^\.\[[^\]]+\]\s//; # don't count trailing/leading whitespace ($line) = $line =~ /^\s*(.*[^\s])\s*$/; if (!defined $line || $line eq '') { # blanks are not significant print {$output} "\n"; next; } if (defined $messagefile) { my $bit= vec ($message, $n, 1); my $report= digest ($line, $bit, $n); print {$output} ".[$report] $line\n"; } else { # I'm stripping the annotations print {$output} "$line\n"; } ++$n; }