Skip to content

Commit

Permalink
Update steganographr.php
Browse files Browse the repository at this point in the history
  • Loading branch information
newbold committed Jan 17, 2021
1 parent b2c0f69 commit 7ab5bb2
Showing 1 changed file with 127 additions and 227 deletions.
354 changes: 127 additions & 227 deletions steganographr.php
Original file line number Diff line number Diff line change
@@ -1,267 +1,167 @@
<?php

/*
___ _ _
/ __| |_ ___ __ _ __ _ _ _ ___ __ _ _ _ __ _ _ __| |_ _ _
\__ \ _/ -_) _` / _` | ' \/ _ \/ _` | '_/ _` | '_ \ ' \| '_|
|___/\__\___\__, \__,_|_||_\___/\__, |_| \__,_| .__/_||_|_|
|___/ |___/ |_|
Hide messages within other messages using invisible characters
by Neatnik LLC
https://neatnik.net
MIT License
Copyright (c) 2020 Neatnik LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

// Prepare variables
$public = isset($_POST['public']) ? $_POST['public'] : null;
$private = isset($_POST['private']) ? $_POST['private'] : null;
$encoded = isset($_POST['encoded']) ? $_POST['encoded'] : null;
#error_reporting(E_ALL);
#ini_set('display_errors', '1');

// Convert a string into binary data
function str2bin($text){
$bin = array();
for($i=0; strlen($text)>$i; $i++)
$bin[] = decbin(ord($text[$i]));
return implode(' ', $bin);
$bin = array();
for($i=0; strlen($text)>$i; $i++)
$bin[] = decbin(ord($text[$i]));
return implode(' ', $bin);
}

// Wrap a string with a distinct boundary
function wrap($string) {
return "\xEF\xBB\xBF".$string."\xEF\xBB\xBF"; // Unicode Character 'ZERO WIDTH NON-BREAKING SPACE' (U+FEFF) 0xEF 0xBB 0xBF
return "\xEF\xBB\xBF".$string."\xEF\xBB\xBF"; // Unicode Character 'ZERO WIDTH NON-BREAKING SPACE' (U+FEFF) 0xEF 0xBB 0xBF
}

// Unwrap a string if the distinct boundary exists
function unwrap($string) {
$tmp = explode("\xEF\xBB\xBF", $string);
if(count($tmp) == 1) return false; // If the string doesn't contain the boundary, return false
return $tmp[1]; // Otherwise, return the unwrapped string
$tmp = explode("\xEF\xBB\xBF", $string);
if(count($tmp) == 1) return false; // If the string doesn't contain the boundary, return false
return $tmp[1]; // Otherwise, return the unwrapped string
}

// Convert binary data into a string
function bin2str($bin){
$text = array();
$bin = explode(' ', $bin);
for($i=0; count($bin)>$i; $i++)
$text[] = chr(@bindec($bin[$i]));
return implode($text);
$text = array();
$bin = explode(' ', $bin);
for($i=0; count($bin)>$i; $i++)
$text[] = chr(@bindec($bin[$i]));
return implode($text);
}

// Convert the ones, zeros, and spaces of the hidden binary data to their respective zero-width characters
function bin2hidden($str) {
$str = str_replace(' ', "\xE2\x81\xA0", $str); // Unicode Character 'WORD JOINER' (U+2060) 0xE2 0x81 0xA0
$str = str_replace('0', "\xE2\x80\x8B", $str); // Unicode Character 'ZERO WIDTH SPACE' (U+200B) 0xE2 0x80 0x8B
$str = str_replace('1', "\xE2\x80\x8C", $str); // Unicode Character 'ZERO WIDTH NON-JOINER' (U+200C) 0xE2 0x80 0x8C
return $str;
$str = str_replace(' ', "\xE2\x81\xA0", $str); // Unicode Character 'WORD JOINER' (U+2060) 0xE2 0x81 0xA0
$str = str_replace('0', "\xE2\x80\x8B", $str); // Unicode Character 'ZERO WIDTH SPACE' (U+200B) 0xE2 0x80 0x8B
$str = str_replace('1', "\xE2\x80\x8C", $str); // Unicode Character 'ZERO WIDTH NON-JOINER' (U+200C) 0xE2 0x80 0x8C
return $str;
}

// Convert zero-width characters to hidden binary data
function hidden2bin($str) {
$str = str_replace("\xE2\x81\xA0", ' ', $str); // Unicode Character 'WORD JOINER' (U+2060) 0xE2 0x81 0xA0
$str = str_replace("\xE2\x80\x8B", '0', $str); // Unicode Character 'ZERO WIDTH SPACE' (U+200B) 0xE2 0x80 0x8B
$str = str_replace("\xE2\x80\x8C", '1', $str); // Unicode Character 'ZERO WIDTH NON-JOINER' (U+200C) 0xE2 0x80
return $str;
$str = str_replace("\xE2\x81\xA0", ' ', $str); // Unicode Character 'WORD JOINER' (U+2060) 0xE2 0x81 0xA0
$str = str_replace("\xE2\x80\x8B", '0', $str); // Unicode Character 'ZERO WIDTH SPACE' (U+200B) 0xE2 0x80 0x8B
$str = str_replace("\xE2\x80\x8C", '1', $str); // Unicode Character 'ZERO WIDTH NON-JOINER' (U+200C) 0xE2 0x80
return $str;
}

?><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta property="og:title" content="Steganographr">
<meta property="og:url" content="https://neatnik.net/steganographr/">
<meta property="og:description" content="Hide text in plain sight using invisible zero-width characters. Digital steganography made simple.">
<title>Steganographr</title>
<style>
@import url('https://rsms.me/inter/inter.css');
html { font-family: 'Inter', sans-serif; }
@supports (font-variation-settings: normal) {
html { font-family: 'Inter var', sans-serif; }
}
body {
margin: 2em 4em;
}
label {
font-weight: bold;
display: block;
}
form {
margin: 2em 0;
}
fieldset {
border: 1px solid #777;
padding: 1em 2em;
border-radius: 0.2em;
}
legend {
font-size: 150%;
font-weight: bold;
padding: 0 .5em;
}
textarea {
width: 100%;
height: 4.4em;
margin-bottom: 1em;
}
</style>
</head>
<body>

<main>

<h1>Steganographr</h1>

<p>Hide text in plain sight using invisible zero-width characters. It’s digital steganography made simple. Inspired by <a href="https://www.zachaysan.com/writing/2017-12-30-zero-width-characters">Zach Aysan</a>.</p>

<p>Enter a public message, then a private message, and then click the button to hide your private message within your public message. If you’ve received a public message, you can reveal the private message here as well.</p>

<section>

<div style="display: grid; grid-auto-rows: 1fr; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); grid-gap: 2em;">
// Prepare variables
$public = isset($_POST['public']) ? $_POST['public'] : null;
$private = isset($_POST['private']) ? $_POST['private'] : null;
$encoded = isset($_POST['encoded']) ? $_POST['encoded'] : null;

<form action="?" method="post">
<fieldset>
<legend>Hide</legend>
<div class="group">
<label for="public">Public message</label>
<textarea name="public"><?php echo $public; ?></textarea>
</div>
<div class="group">
<label for="private">Private message</label>
<textarea name="private"><?php echo $private; ?></textarea>
</div>
<p><button type="submit"><i class="fas fa-pencil-alt"></i> Steganographize</button></p>
</fieldset>
/* Main forms */

$content = '<form action="/steganographr/#results" method="post">
<fieldset>
<legend>Hide</legend>
<p>Hide a private message within a public message.</p>
<p>
<label for="public">Public message</label>
<textarea id="public" name="public">'.$public.'</textarea>
</p>
<p>
<label for="private">Private message</label>
<textarea id="private" name="private">'.$private.'</textarea>
</p>
<p>
<button type="submit"><i class="fas fa-eye-slash"></i> Steganographize</button>
</p>
</fieldset>
</form>
<form action="?" method="post">
<fieldset>
<legend>Reveal</legend>
<div class="group">
<label for="encoded">Public message</label>
<textarea name="encoded" style="height: 11.5em;"><?php echo $encoded; ?></textarea>
</div>
<p><button type="submit"><i class="fas fa-eye"></i> Desteganographize</button></p>
<form action="/steganographr/#results" method="post">
<fieldset>
<legend>Reveal</legend>
<p>Reveal the private message hidden within a public message.</p>
<p>
<label for="encoded">Public message</label>
<textarea id="encoded" name="encoded">'.$encoded.'</textarea>
</p>
<p>
<button type="submit"><i class="fas fa-eye"></i> Desteganographize</button>
</p>
</fieldset>
</form>
</div>

</div>
</section>

<?php
';

if(isset($_POST['public'])) {
echo '<section class="notice"><h2>Steganographized Message</h2>';
// Grab the public message string and break it up into characters
$public = $_POST['public'];
$public = mb_str_split($public);
// Find the half-way point in the string
$half = round(count($public) / 2);
// Grab the private message
$private = $_POST['private'];
// Convert it to binary data
$private = str2bin($private);
// And convert that into a string of zero-width characters
$private = bin2hidden($private);
// Finally, wrap it with a distinct boundary character
$private = wrap($private);
// Inject the encoded private message into the approximate half-way point in the public string
$i = 0;
$tmp = array();
if(count($public) == 1) {
$tmp[0] = $public[0];
$tmp[1] = $private;
}
else {
foreach($public as $char) {
if($i == $half) {
$tmp[] = $private;
}
$tmp[] = $char;
$i++;
}
}
// Reassemble the public string
$public = implode('', $tmp);
// Display a <textarea> containing the public message with the hidden private embedded
echo '<textarea style="height: 5em;">'.$public.'</textarea>';
echo '<p>Copy the text above, and your private message will come along for the ride.</p>';
echo '</div>';
$content .= '<section id="results" class="bubble notice"><h2>Steganographized Message</h2>';
// Grab the public message string and break it up into characters
$public = $_POST['public'];
$public = mb_str_split($public);
// Find the half-way point in the string
$half = round(count($public) / 2);
// Grab the private message
$private = $_POST['private'];
// Convert it to binary data
$private = str2bin($private);
// And convert that into a string of zero-width characters
$private = bin2hidden($private);
// Finally, wrap it with a distinct boundary character
$private = wrap($private);
// Inject the encoded private message into the approximate half-way point in the public string
$i = 0;
$tmp = array();
if(count($public) == 1) {
$tmp[0] = $public[0];
$tmp[1] = $private;
}
else {
foreach($public as $char) {
if($i == $half) {
$tmp[] = $private;
}
$tmp[] = $char;
$i++;
}
}
// Reassemble the public string
$public = implode('', $tmp);
// Display a <textarea> containing the public message with the hidden private embedded
$content .= '<textarea style="height: 3em;">'.$public.'</textarea>';
$content .= '<p>Copy the text above, and your private message will come along for the ride.</p>';

}

if(isset($_POST['encoded'])) {

// Unhide the message
$unwrapped = unwrap($_POST['encoded']);

// If it's not wrapped, process the full string as received
if(!$unwrapped) {
$message = bin2str(hidden2bin($_POST['encoded']));
}
// Otherwise, process only the unwrapped string
else {
$message = bin2str(hidden2bin($unwrapped));
}

// Display the hidden private message
echo '<section class="notice"><h2>Private Message</h2>';
if(strlen($message) < 2) {
echo '<p class="alert"><i class="fas fa-exclamation-triangle"></i> No private message was found.</p>';
}
else {
echo '<p style="font-weight: bold;">'.htmlentities($message).'</p>';
}
// Unhide the message
$unwrapped = unwrap($_POST['encoded']);

// If it's not wrapped, process the full string as received
if(!$unwrapped) {
$message = bin2str(hidden2bin($_POST['encoded']));
}
// Otherwise, process only the unwrapped string
else {
$message = bin2str(hidden2bin($unwrapped));
}

// Display the hidden private message
$content .= '<h2 id="results">Private Message</h2>';
if(strlen($message) < 2) {
$content .= '<div class="message"><div class="message-icon"><i class="fas fa-fw fa-exclamation-circle"></i></div><div class="message-text">No private message was found in that text.</div>
</div></p>';
}
else {
$content .= '<div class="message"><div class="message-icon"><i class="fas fa-fw fa-check-circle"></i></div><div class="message-text">'.htmlentities($message).'</div>
</div></p>';
}
}

?>

</section>

<section id="about" class="attention">
<h2>About Steganographr</h2>
<p>Steganographr works by converting your private message into binary data, and then converting that binary data into zero-width characters (which can then be hidden in your public message). These characters are used:</p>
<ul>
<li>WORD JOINER (U+2060)</li>
<li>ZERO WIDTH SPACE (U+200B)</li>
<li>ZERO WIDTH NON-JOINER (U+200C)</li>
<li>ZERO WIDTH NON-BREAKING SPACE (U+FEFF)</li>
</ul>
</section>

</main>
end:

</body>
</html>
echo $content;

0 comments on commit 7ab5bb2

Please sign in to comment.