/
2.html
465 lines (432 loc) · 20.1 KB
/
2.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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
<!DOCTYPE html>
<!-- Clone the source code at https://github.com/45678/minilock-file-formats -->
<head>
<meta charset="utf-8">
<title>miniLock File Format Version 2</title>
<link rel="stylesheet" href="stylesheets/base.css" charset="utf-8">
<link rel="stylesheet" href="stylesheets/byte_stream.css" charset="utf-8">
<link rel="stylesheet" href="stylesheets/camera.css" charset="utf-8">
<link rel="stylesheet" href="stylesheets/input_files.css" charset="utf-8">
<link rel="stylesheet" href="stylesheets/introduction.css" charset="utf-8">
<link rel="stylesheet" href="stylesheets/nav.css" charset="utf-8">
<link rel="stylesheet" href="stylesheet.css" charset="utf-8">
<link rel="stylesheet" href="stylesheets/colors.css" charset="utf-8">
<script src="compiled/miniLockLib.js" charset="utf-8"></script>
<script src="compiled/async.js" charset="utf-8"></script>
<script src="compiled/canvas-to-blob.js" charset="utf-8"></script>
<script src="compiled/d3.js" charset="utf-8"></script>
<script src="compiled/underscore.js" charset="utf-8"></script>
<script src="compiled/zepto.min.js" charset="utf-8"></script>
<script src="compiled/summary_of_decrypted_ciphertext.html.js" charset="utf-8"></script>
<script src="compiled/decrypt_keys.html.js" charset="utf-8"></script>
<script src="compiled/decrypted_file.html.js" charset="utf-8"></script>
<script src="compiled/margin_byte.html.js" charset="utf-8"></script>
<script src="compiled/unencrypted_summary_pre.html.js" charset="utf-8"></script>
<script src="compiled/parsed_header.html.js" charset="utf-8"></script>
<script src="compiled/summary_of_decrypted_header.html.js" charset="utf-8"></script>
<script src="compiled/camera.js" charset="utf-8"></script>
<script src="compiled/characters.js" charset="utf-8"></script>
<script src="compiled/input_files.js" charset="utf-8"></script>
<script src="compiled/demo.js" charset="utf-8"></script>
</head>
<body class="v2 loading"><a id="introduction"></a>
<div id="title">
<h1><strong>miniLock</strong></h1>
<h1>File Format</h1>
<h1 class="versions">
<div class="current version">
<b><a href="1.html">Version 1</a></b>
<br><label>Current</label>
</div>
<div class="draft version">
<b><a>Version 2</a></b>
<br><label>Draft</label>
</div>
</h1>
<img id="minilock_file_icon" src="minilock_file_icon_black.svg">
</div>
<p id="discuss">
<a href="https://github.com/kaepora/miniLock/pull/136">Discuss this proposal at miniLock HQ</a>
</p>
<section id="introduction_section">
<p>
This info is available when you inspect a miniLock file without a key:
</p>
<div id="minilock_file_summary">
<svg class="file_size x axis"></svg>
<dl class="minilock_file_summary">
<dt>File Size:</dt>
<dd class="size"><div class="file bar"></div><label class="file">? bytes</label></dd>
<dt>Header Size:</dt>
<dd class="size"><div class="header bar"></div><label class="header">? bytes</label></dd>
<dt>Ciphertext Size:</dt>
<dd class="size"><div class="ciphertext bar"></div><label class="ciphertext">? bytes</label></dd>
<dt>miniLock Version:</dt>
<dd><div id="minilock_file_version">?</div></dd>
<dt>Ephemeral Key:</dt>
<dd><div id="minilock_file_empemeral">?</div></dd>
<dt>Decrypt Info:</dt>
<dd class="decrypt_info"><div id="minilock_file_decrypt_info">?</div></dd>
</dl>
</div>
<p>
A secret key is required to see more details and decrypt the file.
</p>
<p>
Lets attempt to open <code id="introduction_minilock_filename">Secret.minilock</code> with one of these keys:
</p>
<div id="decrypt_keys"></div>
<div id="decrypt_status"><div class="empty_placeholder"></div></div>
<div id="decrypt_summary">
<div id="summary_of_decrypted_ciphertext" class="ciphertext"></div>
<div id="summary_of_decrypted_header" class="header"></div>
<div id="decrypted_file_container"></div>
</div>
<p>Now imagine you are a computer. Still here? OK, proceed:</p>
</section>
<article class="blob">
<section id="magic_bytes_section"><div class="margin_bytes"></div>
<h1 class="invisible">Magic Bytes<a id="magic_bytes"></a></h1>
<p>
The first eight slots of the file are the miniLock magic bytes.
Please use this unique sequence of bytes to identify miniLock encrypted files in your own computer programs.
</p>
<p>
Magic Bytes in Base10: <code id="magic_bytes_in_base10"></code><br>
Magic Bytes in Base16: <code id="magic_bytes_in_base16"></code><br>
</p>
<p>
The bytes spell <code>miniLock</code> in UTF-8. They are magic!
</p>
</section>
<section id="size_of_header_section"><div class="margin_bytes"></div>
<h1 class="invisible">Size of Header<a id="size_of_header"></a></h1>
<p>
The next four slots specify the size of the header in bytes.
Interpret this <a href="http://en.wikipedia.org/wiki/Endianness#Little-endian">four-byte little-endian word</a> to get the size as a decimal number.
The word in this file indicates the header is <code id="size_of_header_bytes">?</code> bytes long.
</p>
</section>
<section id="header_section"><div class="margin_bytes"></div>
<h1 class="invisible">Header<a id="header"></a></h1>
<p>
The <code>header</code> of a miniLock file is a UTF-8 encoded JSON string.
The header bytes always start at <code>slot 12</code>.
In this file the header bytes end at <code id="end_slot_of_header_bytes">slot 645</code>.
Read, encode and parse the header bytes like this:
</p>
<pre>
headerBytes = file.read(12, <span id="end_of_header_bytes">645</span>)
serializedHeader = <a href="https://github.com/dchest/tweetnacl-js#naclutilencodeutf8array">NaCl.util.encodeUTF8</a>(headerBytes)
header = <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse">JSON.parse</a>(serializedHeader)
</pre>
<p>
The parsed <code>header</code> of this file looks like this:
</p>
<pre id="parsed_header"></pre>
<p>
<code>version</code> specifies the miniLock file format version number.
Usually it is <code>1</code> right now. Someday soon it might be <code>2</code> more often.
</p>
<p>
<code>ephemeral</code> is a Base64 encoded 32-byte public key.
It is one of two keys that is required to <a href="#decrypt_a_permit">decrypt a permit</a>.
</p>
<p>
<code>decryptInfo</code> contains a <code>nonce:encryptedPermit</code> pair for each recipient.
Each <code>nonce</code> is a Base64 encoded 16-byte unique nonce that is required to decrypt the <code>encryptedPermit</code>.
Each <code>encryptedPermit</code> is a Base64 encoded string of encrypted and serialized JSON bytes.
</p>
<h1>Decoding the Header<a id="decoding_the_header"></a></h1>
<p>
Since most members of <code>header</code> are Base64 encoded
we will need to decode them before we go any further.
First lets decode the <code>ephemeral</code> key:
</p>
<pre>
ephemeral = <a href="https://github.com/dchest/tweetnacl-js#naclutildecodebase64string">NaCl.util.decodeBase64</a>(header.ephemeral)
</pre>
<p>
OK, <code>ephemeral</code> key is <span id="decoded_ephemeral_key"></span>.
We will use it later when we attempt to <a href="#decrypt_a_permit">decrypt a permit</a>.
</p>
<p>
Next we’ll decode each <code>nonce</code> and <code>encryptedPermit</code> in <code>header.decryptInfo</code>:
</p>
<pre>
encryptedPermits = {}
for encodedNonce, encodedEncryptedPermit of header.decryptInfo
nonce = <a href="https://github.com/dchest/tweetnacl-js#naclutildecodebase64string">NaCl.util.decodeBase64</a>(encodedNonce)
encryptedPermit = <a href="https://github.com/dchest/tweetnacl-js#naclutildecodebase64string">NaCl.util.decodeBase64</a>(encodedEncryptedPermit)
encryptedPermits[nonce] = encryptedPermit
</pre>
<p>
Now we have <code id="number_of_permits"></code> encrypted permits that we can attempt to unlock with our keys.
</p>
<h1>Decrypt a Permit<a id="decrypt_a_permit"></a></h1>
<p>
miniLock permits are encrypted with the <a href="http://nacl.cr.yp.to/box.html">public-key authenticated encryption scheme</a> defined in the <a href="http://nacl.cr.yp.to/">Networking and Cryptography library</a>.
Ports of this popular library are available for many programming languages.
This document features <a href="https://dchest.github.io/tweetnacl-js/">TweetNaCl.js</a> for ECMAScript.
</p>
<p>
Two keys and a <code>nonce</code> are required to decrypt a permit.
The first key is the <code>ephemeral</code> key.
The second key is the permit recipient’s <code>secretKey</code>.
Loop through the <code>encryptedPermits</code> until you find one that matches your keys:
</p>
<pre>
for nonce, encryptedPermit of encryptedPermits
decryptedPermit = <a href="https://github.com/dchest/tweetnacl-js/blob/master/README.md#naclboxopenbox-nonce-theirpublickey-mysecretkey">NaCl.box.open</a>(encryptedPermit, nonce, ephemeral, secretKey)
if decryptedPermit
permit = <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse">JSON.parse</a>(decryptedPermit)
</pre>
<p>
Here is the <code>permit</code> that was decrypted with <span class="keyholder">?</span>’s <code>secretKey</code>:
</p>
<pre id="permit_with_encoded_file_info"></pre>
<p>
<code>permit.senderID</code> identifies the person who created the miniLock file.
</p>
<p>
<code>permit.recipientID</code> identifies the recipient of the <code>permit</code>.
Readers can use this to verify the <code>recipientID</code> matches the miniLock ID bound to their <code>secretKey</code>.
</p>
<p>
<code>permit.fileInfo</code> consists of three members: <code>fileKey</code>, <code>fileNonce</code> and <code>fileHash</code>.
All are Base64 encoded strings.
Lets decode them to get a look at their bytes:
<pre id="permit"></pre>
<p>
<code>fileKey</code> is the 32-byte secret key for the ciphertext.
<code>fileNonce</code> is the 16-byte nonce for the ciphertext.
We will use these in a moment when we prepare to decrypt the ciphertext stream.
</p>
<p>
</p>
<p>
<code>fileHash</code> is a 32-byte <a href="https://blake2.net/">BLAKE2</a> hash digest of the original cleartext.
Use it to <a href="">verify the integrity of the decrypted bytes</a> when you have finished decrypting.
</p>
</section>
<section id="ciphertext_section"><div class="margin_bytes"></div>
<h1 class="invisible">Ciphertext<a id="ciphertext"></a></h1>
<p>
The ciphertext in a miniLock file starts at the end of the header and it continues until the end of the file.
Calculate the ciphertext size with:
</p>
<pre>
Take file size in bytes + <span id="ciphertext_file_size_in_bytes"></span>
Subtract magic bytes - 8
Subtract size of header bytes - 4
Subtract header bytes - <span id="ciphertext_header_size_in_bytes"></span>
Ciphertext size in bytes = <span id="ciphertext_size_in_bytes"></span>
</pre>
<p>
The ciphertext in this file starts at <code id="start_of_ciphertext">slot #</code> and ends at <code id="end_of_ciphertext">slot #</code>.
</p>
<p>
miniLock relies on a <a href="https://github.com/dchest/nacl-stream-js#nacl-stream-streaming-encryption-based-on-tweetnacljs">streaming encryption scheme based on TweetNaCl</a> to encrypt and decrypt its ciphertext.
<span class="ok">
The <code>fileKey</code> and <code>fileNonce</code> from <a href="#decrypt_a_permit"><span class="keyholder">?</span>’s permit</a> are required to setup the decryption scheme:
</span>
<span class="failed">
We don’t have the <code>fileKey</code> or the <code>fileNonce</code> because <span class="keyholder">?</span>’s keys didn’t unlock any permits.
</span>
</p>
<pre>
fileKey: <span id="ciphertext_file_key"></span>
fileNonce: <span id="ciphertext_file_nonce"></span>
</pre>
<p>
With these keys on hand we are ready to construct a stream decryptor.
Configure it to process the ciphertext with a maximum chunk size of 1MB:
</p>
<pre>
maxChunkSize = 1024 * 1024
decryptor = <a href="https://github.com/dchest/nacl-stream-js#streamcreateencryptorkey-nonce-maxchunklength">NaCl.stream.createDecryptor</a>(fileKey, fileNonce, maxChunkSize)
</pre>
<p>
Also construct a 32-byte BLAKE2 hash to record the decrypted bytestream so that it can be compared with <code>fileHash</code> during <a href="">integrity verification</a>.
</p>
<pre>
hash = new BLAKE2(32)
</pre>
<h1>The First Chunk <em>152-bytes longer in Version 2</em><a id="the_first_chunk"></a></h1>
<p>
In version 2, the first chunk of ciphertext is reserved for the file <code>name</code>, <code>type</code> and <code>time</code> attributes.
The decrypted chunk is always <code>408</code> bytes long.
The encrypted chunk is <code>428</code> bytes because the encryption scheme adds <code>20</code> bytes.
</p>
<p>Read and decrypt the first chunk of ciphertext like this:</p>
<pre>
startOfCiphertext = <span id="start_of_ciphertext_for_first_chunk"></span>
encryptedChunk = file.read(startOfCiphertext, startOfCiphertext+428)
decryptedChunk = <a href="https://github.com/dchest/nacl-stream-js#decryptordecryptchunkencryptedchunk-islast">decryptor.decryptChunk</a>(encryptedChunk, false)
hash.update(decryptedChunk)
</pre>
<h1>File Name<a id="file_name"></a></h1>
<p>
The first <code>256</code> slots in the chunk are reserved for the <code>name</code> of the file.
To get it, slice off the first <code>256</code> bytes, filter out any <code>null</code> bytes and then encode the remaing bytes in UTF-8.
Don’t be surprised if the <code>name</code> is blank because that is a possibility.
</p>
<pre>
sliceOfBytes = decryptedChunk.slice(0, 256)
filteredBytes = (b for b in sliceOfBytes when b isnt 0)
name = <a href="https://github.com/dchest/tweetnacl-js#naclutilencodeutf8array">NaCl.util.encodeUTF8</a>(filteredBytes)
</pre>
<p>
<code id="decrypted_name"></code> is the decrypted <code>name</code> of this file.
</p>
<h1>Media Type <em>Introduced in Version 2</em><a id="media_type"></a></h1>
<p>
The next <code>128</code> slots in the chunk are reserved to record the file <code>type</code>.
If present, <code>type</code> is expected to be a registered media type such as <code>text/plain</code> or <code>image/jpeg</code>.
It may also be blank.
The fixed size of <code>128</code> slots was informed by the media type <a href="http://tools.ietf.org/html/rfc6838#section-4.2">naming requirements defined in RFC6838</a>.
Slice, filter and then encode the bytes like so:
</p>
<pre>
sliceOfBytes = decryptedChunk.slice(256, 384)
filteredBytes = (b for b in sliceOfBytes when b isnt 0)
type = <a href="https://github.com/dchest/tweetnacl-js#naclutilencodeutf8array">NaCl.util.encodeUTF8</a>(filteredBytes)
</pre>
<p>
<code id="decrypted_type"></code> is the decrypted <code>type</code> of this file.
</p>
<h1>Encrypt Time <em>Introduced in Version 2</em><a id="encrypt_time"></a></h1>
<p>
The last <code>24</code> slots in the chunk are reserved to record the <code>time</code> when the file was encrypted.
<code>time</code> is expected to be a <a href="http://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations">ISO 8601 extended format timestamp</a> or a blank string.
The <code>time</code> parsing routine is the same as the <code>name</code> and <code>type</code> routines: slice, filter and then encode:
</p>
<pre>
sliceOfBytes = decryptedChunk.slice(384, 408)
filteredBytes = (b for b in sliceOfBytes when b isnt 0)
time = <a href="https://github.com/dchest/tweetnacl-js#naclutilencodeutf8array">NaCl.util.encodeUTF8</a>(filteredBytes)
</pre>
<p>
The <code>time</code> says this file was encrypted on <code id="decrypted_time"></code>.
</p>
<h1>Decrypt Data Chunks<a id="decrypt_data_chunks"></a></h1>
<p>
The ciphertext chunks after <a href="#the_first_chunk">the first chunk</a> contain the complete byte stream of <code>Simple.txt</code>.
Each chunk is decrypted in squential order so that it can be reassembled into a coherent stream of cleartext at the end of the routine.
</p>
<p>
First, construct an empty array to collect the <code>decryptedChunks</code>:
</p>
<pre>
decryptedChunks = []
</pre>
<p>
The encrypted data chunks in this file start at <code id="start_position_of_data_chunks">428</code>
and end at <code id="end_position_of_data_chunks">slot ?</code>.
</p>
<pre>
startOfChunk = 1620
endOfChunk = Math.min(startOfChunk + maxChunkSize + 20, file.size)
isLastChunk = endOfChunk is file.size
encryptedChunk = file.read(startOfChunk, endOfChunk)
decryptedChunk = <a href="https://github.com/dchest/nacl-stream-js#decryptordecryptchunkencryptedchunk-islast">decryptor.decryptChunk</a>(encryptedChunk, isLastChunk)
decryptedChunks.push(decryptedChunk)
hash.update(decryptedChunk)
# Repeat from endOfChunk if isLastChunk is false
</pre>
<h1>Cleartext Integrity</h1>
<p>
Create a digest of <code>hash</code> and compare it with the <code>fileHash</code> that was received in <a href="">Alice’s permit</a>
after the last chunk has been processed.
If the digests are not identical the integrity of the decrypted bytes has been compromised.
</p>
<pre>
if isLastChunk
if hash.digest() is fileHash
# The integrity of the name, type, time and data were verified.
else
# The integrity of this file has been compromised.
</pre>
<pre>
data = new Blob(decryptedChunks)
if type is "text/plain"
text = NaCl.util.encodeUTF8(data)
</pre>
</section>
</article>
<nav>
<div><a href="#introduction">Introduction</a></div>
<div><a href="#magic_bytes">Magic Bytes</a></div>
<div><a href="#size_of_header">Size of Header</a></div>
<div><a href="#header" class="link_to_header">Header</a></div>
<div><a href="#ciphertext" class="link_to_ciphertext">Ciphertext</a></div>
</nav>
<div id="input_files" class="text_input_is_selected">
<div class="selected unencrypted text input file">
<img src="black_arrow.svg" class="arrow">
<img src="input_file_icon.svg" class="background">
<textarea autofocus maxlength="128">miniLock is file encryption software that does more with less.</textarea>
<input type="text" name="name" value="Simple.txt">
<input type="hidden" name="type" value="text/plain">
</div>
<div class="encrypted input file">
<img src="black_arrow.svg" class="second arrow">
<img src="input_file_icon.svg" class="background">
<div class="author">
<label>Author:</label>
<select class="author" name="keys">
<option>Alice</option>
<option>Bobby</option>
<option>Sarah</option>
</select>
</div>
<fieldset class="permits">
<legend>Permits:</legend>
<div><label><input type="checkbox" name="minilock_ids" value="CeF5fM7SEdphjktdUbAXaMGm13m6mTZtbprtghvsMRYgw" checked> Alice</label></div>
<div><label><input type="checkbox" name="minilock_ids" value="2CtUp8U3iGykxaqyEDkGJjgZTsEtzzYQCd8NVmLspM4i2b" checked> Bobby</label></div>
<div><label><input type="checkbox" name="minilock_ids" value="2H6TvBdYdWxy5z6KW6Ba5ZBb3B4XFAGVPsCHKmoYyJijCK"> Sarah</label></div>
</fieldset>
<input type="text" name="name" value="Secret.minilock">
<input type="hidden" name="version" value="2">
</div>
<div class="blank unencrypted image input file">
<img src="black_arrow.svg" class="arrow">
<img src="input_file_icon.svg" class="background">
<div id="camera" class="closed">
<div class="status_indicator"></div>
<div class="cover"><img src="camera.svg"></div>
<div class="countdown"><a>3</a> <a>2</a> <a>1</a></div>
<button class="start" tabindex="-1"></button>
<button class="erase" tabindex="-1"><img src="clear_button.svg"></button>
<div id="viewfinder">
<img class="snap">
<video id="video" autoplay></video>
<div class="curtain"><img src="test_pattern.png"></div>
<div class="flash"></div>
</div>
<div id="notice_request_to_use_camera"></div>
</div>
<footer>
<select name="type" disabled>
<option value="image/jpeg" selected>JPEG</option>
<option value="image/png">PNG</option>
</select>
</footer>
</div>
</div>
<div id="section_sizes_graphic" hidden>
<div class="magic"></div>
<div class="size_of_header"></div>
<div class="header"></div>
<div class="ciphertext"></div>
</div>
<div id="scrollgraph">
<div class="margin_top"></div>
<div class="introduction"></div>
<div class="magic"></div>
<div class="size_of_header"></div>
<div class="header"></div>
<div class="ciphertext"></div>
<div class="margin_bottom"></div>
</div>
</body>