Compare commits
No commits in common. "master" and "rewrite" have entirely different histories.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
27
index.html
27
index.html
|
@ -5,49 +5,48 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>❄️</text></svg>">
|
||||
<link rel="stylesheet" href="theme.css">
|
||||
<title>Snow10 - text steganography</title>
|
||||
<script src="main.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container main">
|
||||
<h1>Snow10 ☃</h1>
|
||||
<p>Snow10 is a simple web app for converting text to zero width unicode characters, which can be hidden in normal messages.</p>
|
||||
<p>Snow10 is a simple web app for converting text to whitespace characters, which can be hidden in normal messages.</p>
|
||||
<p>It is inspired by the <a href="https://web.archive.org/web/20210117115615/http://darkside.com.au/snow/">original program</a> published in ~1998.</p>
|
||||
<p>Do not use alongside languages/emoji that use zero-width characters. Sorry, it's the way it works.</p>
|
||||
<pre>
|
||||
Threat model: person visually looking at message threads in an app such as Twitter, Matrix, Signal, documents, etc. E.g. abusive family
|
||||
Threat model: person visually looking at message threads in an app such as Twitter, Matrix, Signal, documents, etc. E.g. abusive family
|
||||
|
||||
This is steganography, not cryptography.
|
||||
Encrypt the secret message using something like pgp or age before using if encryption is needed.
|
||||
Encrypt the secret message using something like age or keybase before using if encryption is needed.
|
||||
|
||||
Will not resist forensic analysis. Don't use it over SMS.
|
||||
Will not resist forensic analysis.
|
||||
|
||||
Privacy: This works client-side and does not log any messages.
|
||||
Privacy: This works client-side and does not log any messages.
|
||||
</pre>
|
||||
<label>
|
||||
<input type="checkbox" name="hideMode" checked>
|
||||
<input type="radio" name="hideMode" value="hide" checked>
|
||||
Hide mode
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="hideMode" value="hide">
|
||||
Unhide mode
|
||||
</label>
|
||||
|
||||
<form class="encode">
|
||||
<label for="hideText" name="hideTextZone">Non-secret message (secret gets hidden inside): <input type="text" name="hideText" placeholder="Wonderful weather we're having"></label>
|
||||
<label for="hideText">Non-secret message: <input type="text" name="hideText" placeholder="Wonderful weather we're having"></label>
|
||||
<br>
|
||||
<h1>Secret message</h1>
|
||||
<textarea name="inputSecret" placeholder="Secret to hide" required></textarea>
|
||||
<h1>Output</h1>
|
||||
<textarea name="output" readonly></textarea>
|
||||
<br>
|
||||
<button id="copyResult">Copy result to clipboard</button>
|
||||
<br>
|
||||
<input type="submit" value="Hide">
|
||||
</form>
|
||||
|
||||
<form class="decode">
|
||||
<h1>Message containing secret</h1>
|
||||
<textarea name="inputSecret" placeholder="Non-secret message with secret inside" required></textarea>
|
||||
<textarea name="input" placeholder="Non-secret message with secret inside" required></textarea>
|
||||
<h1>Output</h1>
|
||||
<textarea name="output" readonly></textarea>
|
||||
<input type="submit" value="Reveal Message">
|
||||
<input type="submit" value="Hide">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
(function(){var c;c=jQuery;c.bootstrapGrowl=function(f,a){var b,e,d;a=c.extend({},c.bootstrapGrowl.default_options,a);b=c("<div>");b.attr("class","bootstrap-growl alert");a.type&&b.addClass("alert-"+a.type);a.allow_dismiss&&(b.addClass("alert-dismissible"),b.append('<button class="close" data-dismiss="alert" type="button"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>'));b.append(f);a.top_offset&&(a.offset={from:"top",amount:a.top_offset});d=a.offset.amount;c(".bootstrap-growl").each(function(){return d= Math.max(d,parseInt(c(this).css(a.offset.from))+c(this).outerHeight()+a.stackup_spacing)});e={position:"body"===a.ele?"fixed":"absolute",margin:0,"z-index":"9999",display:"none"};e[a.offset.from]=d+"px";b.css(e);"auto"!==a.width&&b.css("width",a.width+"px");c(a.ele).append(b);switch(a.align){case "center":b.css({left:"50%","margin-left":"-"+b.outerWidth()/2+"px"});break;case "left":b.css("left","20px");break;default:b.css("right","20px")}b.fadeIn();0<a.delay&&b.delay(a.delay).fadeOut(function(){return c(this).alert("close")}); return b};c.bootstrapGrowl.default_options={ele:"body",type:"info",offset:{from:"top",amount:20},align:"right",width:250,delay:4E3,allow_dismiss:!0,stackup_spacing:10}}).call(this);
|
File diff suppressed because one or more lines are too long
90
main.js
90
main.js
|
@ -1,90 +0,0 @@
|
|||
let zero_zwnj = ''
|
||||
let one_zwl = ''
|
||||
let two_zwsp = ''
|
||||
|
||||
|
||||
function doCheck(){
|
||||
if (document.getElementsByName('hideMode')[0].checked){
|
||||
document.getElementsByName('hideTextZone')[0].style.display = "block"
|
||||
document.getElementsByClassName('encode')[0].style.display = "block"
|
||||
document.getElementsByClassName('decode')[0].style.display = "none"
|
||||
}
|
||||
else{
|
||||
document.getElementsByName('hideTextZone')[0].style.display = "none"
|
||||
document.getElementsByClassName('encode')[0].style.display = "none"
|
||||
document.getElementsByClassName('decode')[0].style.display = "block"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
document.getElementsByName('hideMode')[0].onclick = function(){
|
||||
doCheck()
|
||||
}
|
||||
|
||||
document.getElementsByClassName('encode')[0].onsubmit = function(e){
|
||||
for (let i of document.getElementsByName('inputSecret')[0].value){
|
||||
if (i.charCodeAt(0) >= 729){
|
||||
alert("Unsupported character in message")
|
||||
return false
|
||||
}
|
||||
}
|
||||
e.preventDefault()
|
||||
let coverText = document.getElementsByName('hideText')[0].value
|
||||
let cover1 = ""
|
||||
let cover2 = ""
|
||||
if (coverText.length){
|
||||
cover1 = coverText.substring(0, coverText.length / 2)
|
||||
cover2 = coverText.substring(coverText.length / 2, coverText.length)
|
||||
}
|
||||
document.getElementsByName('output')[0].value = cover1 + textToTern(document.getElementsByName('inputSecret')[0].value) + cover2
|
||||
return false
|
||||
}
|
||||
|
||||
document.getElementsByClassName('decode')[0].onsubmit = function(e){
|
||||
let input = document.getElementsByName('inputSecret')[1].value
|
||||
|
||||
console.debug(input.length)
|
||||
console.debug(ternToText(input, true))
|
||||
document.getElementsByName('output')[1].value = ternToText(input)
|
||||
e.preventDefault()
|
||||
return false
|
||||
}
|
||||
|
||||
document.getElementById('copyResult').onclick = function(){
|
||||
navigator.clipboard.writeText(document.getElementsByName('output')[0].value).then(function() {
|
||||
/* clipboard successfully set */
|
||||
alert("Copied to clipboard")
|
||||
}
|
||||
).catch(function(err) {
|
||||
|
||||
alert("Failed to copy to clipboard")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
let ternToText = function(input){
|
||||
|
||||
input = input.replaceAll(zero_zwnj, '0')
|
||||
input = input.replaceAll(one_zwl, '1')
|
||||
input = input.replaceAll(two_zwsp, '2')
|
||||
|
||||
if(input.match(/[120]{6}/g)){
|
||||
let wFromTernary = input.match(/([120]{6}|\s+)/g).map(function(fromTernary){
|
||||
return String.fromCharCode(parseInt(fromTernary, 3) )
|
||||
}).join('')
|
||||
return wFromTernary.replaceAll("\u0000", "")
|
||||
}
|
||||
}
|
||||
|
||||
/* based on stackoverflow.com/questions/21354235/converting-binary-to-text-using-javascript */
|
||||
let textToTern = function(text) {
|
||||
let output = []
|
||||
let length = text.length
|
||||
for (var i = 0;i < length; i++) {
|
||||
let bin = text[i].charCodeAt().toString(3).replaceAll('0', zero_zwnj).replaceAll('1', one_zwl).replaceAll('2', two_zwsp)
|
||||
output.push(Array(6-bin.length+1).join(zero_zwnj) + bin)
|
||||
}
|
||||
return output.join('')
|
||||
}
|
||||
|
||||
doCheck()
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
Snow10 - Whitespace steganography. electronic invisible ink.
|
||||
Copyright (C) 2021 Kevin Froman https://ChaosWebs.net/
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
(
|
||||
|
||||
function(){
|
||||
|
||||
var characterSet = ['', '<27>', ""]
|
||||
let encodeForm = document.getElementsByTagName("form")[0]
|
||||
|
||||
encodeForm.onsubmit = function(e){
|
||||
|
||||
let msg: HTMLTextAreaElement = document.getElementsByTagName('textarea')[0]
|
||||
let msgText: string = msg.value;
|
||||
let encoded = new Uint16Array(msgText.length)
|
||||
|
||||
for (let i = 0; i < msgText.length; i++){
|
||||
|
||||
encoded[i] = msgText.charCodeAt(i)
|
||||
|
||||
}
|
||||
console.debug(encoded)
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
|
||||
}()
|
||||
|
||||
|
||||
)
|
Loading…
Reference in New Issue