Compare commits
No commits in common. "master" and "rewrite" have entirely different histories.
7
clipboard.min.js
vendored
Normal file
7
clipboard.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
font-awesome.min.css
vendored
Normal file
4
font-awesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
fonts/FontAwesome.otf
Normal file
BIN
fonts/FontAwesome.otf
Normal file
Binary file not shown.
BIN
fonts/fontawesome-webfont.eot
Normal file
BIN
fonts/fontawesome-webfont.eot
Normal file
Binary file not shown.
2671
fonts/fontawesome-webfont.svg
Normal file
2671
fonts/fontawesome-webfont.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 434 KiB |
BIN
fonts/fontawesome-webfont.ttf
Normal file
BIN
fonts/fontawesome-webfont.ttf
Normal file
Binary file not shown.
BIN
fonts/fontawesome-webfont.woff
Normal file
BIN
fonts/fontawesome-webfont.woff
Normal file
Binary file not shown.
BIN
fonts/fontawesome-webfont.woff2
Normal file
BIN
fonts/fontawesome-webfont.woff2
Normal file
Binary file not shown.
23
index.html
23
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
|
||||
|
||||
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.
|
||||
</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>
|
||||
|
1
jquery.bootstrap-growl.min.js
vendored
Normal file
1
jquery.bootstrap-growl.min.js
vendored
Normal file
@ -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);
|
5
jquery.min.js
vendored
Normal file
5
jquery.min.js
vendored
Normal file
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()
|
47
main.ts
Executable file
47
main.ts
Executable file
@ -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
Block a user