-
-
Notifications
You must be signed in to change notification settings - Fork 104
/
index.html
342 lines (320 loc) · 17.1 KB
/
index.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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="canonical" href="https://pagecrypt.maxlaumeister.com/" />
<title>PageCrypt - Password Protect HTML</title>
<meta name="description" content="This tool lets you securely password-protect an HTML file without using .htaccess. It uses strong encryption, so the password-protection cannot be bypassed. Choose an HTML file and a password, and your page will be password-protected!">
<script>
// If there is no trailing slash after the path in the url, add it
// Workaround for Netlify trailing-slash rewrite issue
// https://community.netlify.com/t/bug-in-non-trailing-slash-rewrite/452/23
if (!window.location.pathname.endsWith('/') && !window.location.pathname.split("/").pop().includes(".")) {
var url = window.location.protocol + '//' +
window.location.host +
window.location.pathname + '/' +
window.location.search;
window.history.replaceState(null, document.title, url);
}
</script>
<style>
html, body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
line-height: 1.4;
}
h1 {
display: inline-block;
}
.githubLink {
display: inline-block;
padding: 6px 15px;
text-align: center;
text-decoration: none;
border-radius: 4px;
border: 1px solid #bbb;
margin-right: 10px;
background-image:linear-gradient(180deg, #fafbfc, #eff3f6 90%);
}
.githubLink:hover {
border: 1px solid #999;
background-image: linear-gradient(180deg, #f0f3f6, #e6ebf1 90%);
}
h1, .githubLink {
vertical-align: middle;
}
.linkBack {
margin: 1em 0;
}
#pageContainer {
margin: 0 auto;
padding: 10px;
padding-top: 0;
max-width: 1050px;
}
#mainContainer {
padding: 0 25px 25px 25px;
border: solid #CCC 1px;
}
#sectionTop {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
#projDescription {
max-width: 700px;
}
.steps {
color: green;
}
#fileselect {
max-width: 240px;
}
#passMismatch {
display: none;
color: red;
}
.demoButton {
display: inline-block;
font: bold 20px Arial;
text-decoration: none;
background-color: #EEEEEE;
color: #333333;
padding: 2px 6px 2px 6px;
border-top: 1px solid #CCCCCC;
border-right: 1px solid #333333;
border-bottom: 1px solid #333333;
border-left: 1px solid #CCCCCC;
}
#doneProtecting {
display: none;
color: blue;
font-weight: bold;
}
#disclaimer {
color: gray;
font-size: 0.75em;
margin-top: 30px;
}
.githubLogo {
position: relative;
height: 25px;
vertical-align: middle;
}
.githubLogo:hover {
opacity: 1;
}
.suggested {
max-width: 260px;
}
@media (min-width: 700px) {
#pageContainer {
padding: 50px;
padding-top: 0;
}
}
</style>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('set', 'forceSSL', true);
ga('create', 'UA-24440376-11', 'auto');
ga('send', 'pageview');
</script>
</head>
<body>
<div id="pageContainer">
<div class="linkBack">
<a class="linkBack" href="https://www.maxlaumeister.com/"><- home</a>
</div>
<div id="mainContainer">
<div id="sectionTop">
<div id="projDescription">
<a class="githubLink" href="https://github.com/MaxLaumeister/pagecrypt"><img class="githubLogo" src="github.svg" alt="GitHub Logo"></a>
<h1>PageCrypt - Password Protect HTML</h1>
<p>This tool lets you securely password-protect an HTML file. Unlike other password-protection tools, this tool:</p>
<ol>
<li>Has no server-side components (this tool and its password-protected pages run entirely in javascript).</li>
<li>Uses strong encryption, so the password-protection cannot be bypassed.</li>
</ol>
<p>All you need to do is choose an HTML file and a password, and your page will be password-protected.</p>
<p><a href="demopage/" class="demoButton">Live Demo</a> (pssst... the live demo password is "hunter2")</p>
</div>
<div id="widgetContainer">
<h2>Instructions</h2>
<p class="steps">Step 1. Choose HTML file</p><p><input type="file" id="fileselect" name="file" disabled/></p>
<p><span class="steps">Step 2. Choose password</span>
<div style="display: table;">
<div style="display: table-row;">
<div style="display: table-cell;">Password:</div><div style="display: table-cell;"><input id="pass" type="password" name="pass" disabled></div>
</div>
<div style="display: table-row;">
<div style="display: table-cell;">Confirm:</div><div style="display: table-cell;"><input id="passconf" type="password" name="passconf" disabled></div>
</div>
</div>
<p id="passMismatch">Passwords don't match, try again.</p>
<p class="steps">Step 3. Protect!</p><p><button id="submitFile" type="button" disabled>Submit</button></p>
<p id="doneProtecting">Done! Check your downloads!</p>
<p class="steps">Step 4. Tip me?</p><p><a href="https://www.maxlaumeister.com/tip/" class="demoButton">Donate!</a></p>
<p class="suggested">Suggested donation is <strong>$5</strong> for personal use, or <strong>$10-20</strong> for businesses. Honor system!</p>
</div>
</div>
<div style="clear: both;"></div>
<hr>
<div id="faq">
<h2>FAQ</h2>
<h3>Why do the resulting password-protected HTML documents only work when served via HTTPS?</h3>
<p>Due to browser limitations, PageCrypt protected pages only work if served over a secure HTTPS connection (or if the .html file is opened directly from disk). This is because of some technical stuff involving <a href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto">SubtleCrypto</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts">Secure Contexts</a>. To produce html files that work over insecure HTTP connections, please use <a href="legacy/">the legacy version of PageCrypt</a>, which produces larger, less strongly protected documents.</p>
<h3>How can this be secure if it's client-side? Can't people just bypass the password?</h3>
<p>The HTML gets encrypted using the password, so it is unreadable without the password. An attacker could extract the encrypted document, but it would be an unusable mess until they decrypt it, which can only be done with the original password.</p>
<h3>How do I know you're not keeping track of passwords I enter into this tool?</h3>
<p>View the source code for this tool (in your browser and/or <a href="https://github.com/MaxLaumeister/pagecrypt">on GitHub</a>) and you can see for yourself that the password never leaves your computer!</p>
<h3>Why would I want to use this instead of a .htaccess user/password prompt?</h3>
<p>Standard user/password prompts require you have some sort of privileged access to the server. With Apache for instance, you need to be able to add a .htaccess file to the directory you want to protect. Since this tool produces a standard HTML file, you can host it literally anywhere, even places that don't give you access to the server configuration.</p>
<p>This means you can use this tool to password-protect files without using .htaccess!</p>
<h3>Does this encrypt all the CSS/Javascript/Images or only the HTML itself?</h3>
<p>This tool only encrypts the HTML document itself. If you inline your CSS/JS, or if you <a href="http://www.abeautifulsite.net/convert-an-image-to-a-data-uri-with-your-browser/" target="_blank">convert your images to data uris</a>, then they will be encrypted too. Otherwise they will just be linked. Since the HTML itself is encrypted though, a visitor without the password will not be able see the URLs of any of the linked resources.</p>
<h3>What sort of crypto do you use?</h3>
<p>This tool uses the <a href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto">SubtleCrypto</a> JavaScript API for its encryption. First, an encryption key is derived from the password using PBKDF2 and a random salt with 100,000 rounds. Then the HTML is encrypted using AES256.</p>
<h3>Is there a way to automate this or use it as a software library?</h3>
<p>See <a href="https://github.com/Greenheart/pagecrypt">Samuel Plumppu's rewrite of PageCrypt</a>, which includes CLI and NPM automation, and NodeJS API support.</p>
<p>In addition, thanks to <a href="https://zoltan.xyz/">Zoltán Gálli</a> for <a href="https://github.com/MaxLaumeister/pagecrypt/tree/master/python">this tool's <strong>Python</strong> CLI</a>, and thanks to <a href="https://nialfrancis.tech/">Nial Francis</a> for <a href="https://github.com/MaxLaumeister/pagecrypt/tree/master/PowerShell">this tool's <strong>PowerShell</strong> CLI</a>. <a href="https://github.com/MaxLaumeister/pagecrypt/tree/master/CONTRIBUTING.md">Contributions welcome!</a></p>
<p>Finally, see <a href="https://github.com/brentscott93/pagecryptr">Brent Scott's <strong>R-wrapper</strong> for PageCrypt</a>. From the project page: "It is very common for R users to knit Rmarkdown documents to HTML docs. This provides an easy way to PageCrypt those documents without leaving R."</p>
</div>
<hr>
<div id="moreInfo">
<h2>More Info</h2>
<p>Project by <a href="https://www.maxlaumeister.com/">Maximillian Laumeister</a>. The source code and license are available <a href="https://github.com/MaxLaumeister/pagecrypt">on GitHub</a>.
</div>
</div>
<div id="disclaimer">
<p>DISCLAIMER</p>
<p>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.</p>
</div>
</div>
<script src="js/FileSaver.js"></script>
<script>
/**
* Encrypt a string and turn it into an encrypted payload.
*
* @param {string} content The data to encrypt
* @param {string} password The password which will be used to encrypt + decrypt the content.
* @returns an encrypted payload
*/
async function getEncryptedPayload(content, password) {
const encoder = new TextEncoder()
const salt = crypto.getRandomValues(new Uint8Array(32))
const baseKey = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveKey'],
)
const key = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' },
baseKey,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt'],
)
const iv = crypto.getRandomValues(new Uint8Array(16))
const ciphertext = new Uint8Array(
await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
encoder.encode(content),
),
)
const totalLength = salt.length + iv.length + ciphertext.length
const mergedData = new Uint8Array(totalLength)
mergedData.set(salt)
mergedData.set(iv, salt.length)
mergedData.set(ciphertext, salt.length + iv.length)
return mergedData;
}
function ab2str(buf) {
const arrayBuf = new Uint16Array(buf);
let out = "";
for (let i = 0; i < arrayBuf.length; i++) {
out += String.fromCharCode(arrayBuf[i]);
}
return btoa(out);
}
var fileSelect = document.getElementById('fileselect');
var submitFile = document.getElementById('submitFile');
var passEl = document.getElementById('pass');
var passConfEl = document.getElementById('passconf');
var passMismatch = document.getElementById('passMismatch');
var doneProtecting = document.getElementById('doneProtecting');
var templateHTML;
function doSubmit(evt) {
if (!passwordsMatch()) {
passMismatch.style.display = "block";
return;
}
var file = fileSelect.files[0];
var reader = new FileReader();
reader.onload = function(data) {
var fileConts = data.target.result;
var encryptedFilePr = getEncryptedPayload(fileConts, passEl.value);
submitFile.disabled = true;
encryptedFilePr.then((encryptedFile) => {
submitFile.disabled = false;
// Wrap template around encrypted data
var encryptedEncodedFile = ab2str(encryptedFile);
var encryptedDocument = templateHTML.replace("/*{{ENCRYPTED_PAYLOAD}}*/\"\"", "\"" + encryptedEncodedFile + "\"");
var fileBlob = new Blob([encryptedDocument], {type: "text/plain;charset=utf-8"});
// Create filename
var origPath = document.getElementById('fileselect').value;
var pathSplit = origPath.split("\\");
var origName = pathSplit[pathSplit.length - 1];
var origNameSplit = origName.split(".");
origNameSplit[origNameSplit.length - 2] += "-protected";
var finalName = origNameSplit.join(".");
saveAs(fileBlob, finalName);
doneProtecting.style.display = "block";
});
};
reader.readAsText(file);
}
submitFile.onclick = doSubmit;
// Load template
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE ) {
if(xmlhttp.status == 200){
templateHTML = xmlhttp.responseText;
fileSelect.disabled = false;
}
else {
console.log("Template not found.");
}
}
}
xmlhttp.open("GET", "decryptTemplate.html", true);
xmlhttp.send();
// Enable elements as they are filled out
fileSelect.onchange = function () {
passEl.disabled = false;
passConfEl.disabled = false;
submitFile.disabled = false;
doneProtecting.style.display = "none";
}
function passwordsMatch() {
if (passEl.value !== "" && passEl.value === passConfEl.value) {
return true;
} else {
return false;
}
}
passEl.onkeyup = passConfEl.onkeyup = function () {
passMismatch.style.display = "none";
}
if (window.location.protocol === "file:") alert("Please load this tool using a web server - it does not work when loaded directly from the index.html file. Thanks!");
</script>
</body>
</html>