forked from oakes/PixelJihad
-
Notifications
You must be signed in to change notification settings - Fork 0
/
about.html
222 lines (214 loc) · 10.3 KB
/
about.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
<!DOCTYPE html>
<html lang='en'>
<head>
<title>Steganography in JavaScript</title>
<meta name='viewport'
content='width=device-width, initial-scale=1, maximum-scale=1'>
<link href='main.css' type='text/css' rel='stylesheet'></link>
</head>
<body>
<a href="https://github.com/oakes/PixelJihad">
<img style="position: absolute; top: 0; right: 0; border: 0;"
src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png"
alt="Fork me on GitHub"></a>
<div class='section'>
<h1>Steganography in JavaScript</h1>
<h4>by Zach Oakes on June 25th, 2012</h4>
(<a href='index.html'>See it in action</a>)
</div>
<div class='section'>
<div class='step'>Overview</div>
<div class='sectionbody'>
<p>Image steganography is an effective way to exchange hidden
messages without raising suspicion. It works by encoding the
message into the color values of the image's pixels.</p>
<p>With recent browser features like the File API
and Canvas, we can implement this technique in a browser without
any server-side code at all.</p>
</div>
</div>
<div class='section'>
<div class='step'>Step 1: Load the image</div>
<div class='sectionbody'>
<p>Doing this in the past used to require uploading the image to a
server, but with the <code>FileReader</code> object we can now load
the image into the DOM without any round trip to the server.
For a tool focused on privacy, this is huge.</p>
<code>
var reader = new FileReader();<br/>
reader.onload = function(event) {<br/>
var dataUrl = event.target.result;<br/>
// ...<br/>
};<br/>
reader.readAsDataURL(e.target.files[0]);
</code>
<p>With our nifty data URL we can now load it into an
<code>Image</code> object, which we'll then feed into our
<code>canvas</code> element to do the pixel manipulation.</p>
<code>
var img = new Image();<br/>
img.onload = function() {<br/>
var canvas = document.getElementById('canvas');<br/>
var ctx = canvas.getContext('2d');<br/>
ctx.canvas.width = img.width;<br/>
ctx.canvas.height = img.height;<br/>
ctx.drawImage(img, 0, 0);<br/>
// ...<br/>
};<br/>
img.src = dataUrl;
</code>
</div>
</div>
<div class='section'>
<div class='step'>Step 2 (if encoding): Prepare the message</div>
<div class='sectionbody'>
<p>For extra security, it's good to provide the option of
encrypting the message before hiding it in the image. To do this,
we'll use the <a href='http://crypto.stanford.edu/sjcl/'>SJCL</a>
library. Its <code>encrypt</code> function uses reasonable defaults
(AES-128 in CCM mode and PBKDF2 with 1000 iterations).</p>
<code>
if (password.length > 0) {<br/>
message = sjcl.encrypt(password, message);<br/>
} else {<br/>
message = JSON.stringify({'text': message});<br/>
}
</code>
<p>To encode the message, we'll need to break it up into its
constituent 1s and 0s. We do this by getting the numerical value
of each letter in the message using <code>charCodeAt</code>. This
returns a 2-byte unicode value, and we can then do bitwise
operations to get the individual bits.</p>
<code>
var getBit = function(number, location) {<br/>
return ((number >> location) & 1);<br/>
};<br/>
<br/>
var getBitsFromNumber = function(number) {<br/>
var bits = [];<br/>
for (var i = 0; i < 16; i++) {<br/>
bits.push(getBit(number, i));<br/>
}<br/>
return bits;<br/>
};<br/>
<br/>
var messageBits = [];<br/>
for (var i = 0; i < message.length; i++) {<br/>
var code = message.charCodeAt(i);<br/>
var bits = getBitsFromNumber(code);<br/>
messageBits = messageBits.concat(bits);<br/>
}
</code>
</div>
</div>
<div class='section'>
<div class='step'>Step 3 (if encoding): Encode the message</div>
<div class='sectionbody'>
<p>The <code>canvas</code> element makes it very easy to retrieve
the pixels of an image.</p>
<code>
var imgData = ctx.getImageData(0, 0, width, height);<br/>
var colors = imgData.data;
</code>
<p>We end up with an array called <code>colors</code>.
It contains each of the four color values from each pixel
(red, green, blue, alpha). So, <code>colors[0]</code> is the red
color value of the first pixel, and <code>colors[4]</code> is the
red color value of the second pixel.</p>
<p>The easiest approach to encoding is to start at the top left
pixel and encode the message linearly. This, however, will make it
easier to detect, both programatically and with the naked eye.</p>
<p>Instead, we'll use a simple technique to scatter the message to
seemingly random pixels. We'll hash the user's password (or a blank
string) to get "random" locations from <code>colors</code>.</p>
<code>
var hash = sjcl.hash.sha256.hash(password);</br>
var pos = 0;<br/>
while (pos < messageBits.length) {<br/>
var rand = hash[pos % hash.length] * (pos + 1);<br/>
var loc = Math.abs(rand) % colors.length;<br/>
// ...<br/>
pos++;<br/>
}
</code>
<p>With the location in hand, we can use bitwise operations to
set the 0th bit (the least significant bit) to a bit from the
message.</p>
<code>
var setBit = function(number, location, bit) {<br/>
return (number & ~(1 << location)) | (bit << location);<br/>
};<br/>
// ...<br/>
colors[loc] = setBit(colors[loc], 0, messageBits[pos]);
</code>
</div>
</div>
<div class='section'>
<div class='step'>Step 2 (if decoding): Get the message</div>
<div class='sectionbody'>
<p>To decode, we use the same scattering code we wrote above. One
obvious problem is that, unlike during encoding, we don't know
when to stop!</p>
<p>The solution, which wasn't mentioned above for
simplicity, is to encode the message length before the message
itself. We encoded it as a 2-byte number (16 bits).</p>
<code>
var hash = sjcl.hash.sha256.hash(password);</br>
var messageSize = 0, pos = 0;<br/>
while (pos < 16) {<br/>
// use the same code as above to get "loc"<br/>
// ...<br/>
var bit = getBit(bytes[loc], 0);<br/>
messageSize = setBit(messageSize, pos, bit);<br/>
pos++;<br/>
}<br/>
</code>
<p>The same exact code can now be used to retrieve each individual
character of the message.</p>
</div>
</div>
<div class='section'>
<div class='step'>Step 3 (if decoding): Parse the message</div>
<div class='sectionbody'>
<p>As you may have noticed above, we encoded the message as JSON.
This allowed us to include all the various bits of information
necessary for decrypting (like the salt and iteration count).</p>
<p>With our message in hand, we merely need to parse it and, if
necessary, decrypt it. With luck, we'll have the original plain
text in all its glory.</p>
<code>
var obj = null;<br/>
try {<br/>
obj = JSON.parse(message);<br/>
} catch (e) {<br/>
// message is invalid<br/>
}<br/>
if (obj) {<br/>
// decrypt if necessary, then display the text!<br/>
}
</code>
</div>
</div>
<div class='section'>
<div class='step'>One minor nit in all this...</div>
<div class='sectionbody'>
<p>Most, if not all, <code>canvas</code> implementations use a
process called <a href='http://stackoverflow.com/q/4309364'>
premultiplied alpha</a>. This means that after setting the pixels,
the browser will actually modify the red, green, and blue values
to reflect the alpha value. This lets them render it faster.</p>
<p>Unfortunately, this is not good for steganography, because it
destroys the information we inserted. We can't "reverse" the
calculation, because there's a lot of rounding going on.</p>
<p>The solution, I found, is to make sure the pixels I store data
in have no transparency. I do this by setting the alpha value to
255. This protects the other three color values from being
modified.</p>
<p>Of course, this means I can't store anything in the alpha
portion of a pixel, so I have 25% less space to store in a given
image. While unfortunate, it's a price I'm willing to pay for the
convenience of browser-based steganography.</p>
</div>
</div>
</body>
</html>