Skip to content

Commit

Permalink
Add optional encryption
Browse files Browse the repository at this point in the history
Cryptography
- Use AES-GCM-256 for encryption
- Use PBKDF2 with SHA-256 and 100000 iterations for key derivation

Behavior
- A human-readable password will be automatically be generated when not set
- The password will be appended to the URI as fragment #password
- The IV (12 bytes) is prepended to the ciphertext
- Everything is base64 encoded (33% overhead)
- Update is disabled when encryption fails to avoid overwriting

Fixes petercunha#12
  • Loading branch information
darkdragon-001 committed Jun 22, 2020
1 parent e7be2df commit 5c8546a
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 16 deletions.
25 changes: 25 additions & 0 deletions public/javascripts/crypt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const unambiguousChars = '23456789abcdefghjkmnpqrstwxyz'; // easily human-readable
async function generatePassword(length) {
let password = window.crypto.getRandomValues(new Uint32Array(length));
return password.reduce((str,chr)=>str+unambiguousChars[chr%unambiguousChars.length],''); // chars from unambiguousChars list
}
async function deriveKey(password, salt) {
let keyMaterial = await window.crypto.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveBits", "deriveKey"]);
return window.crypto.subtle.deriveKey(
{ name: "PBKDF2", salt: salt, iterations: 100000, hash: "SHA-256" },
keyMaterial,
{ name: "AES-GCM", length: 256},
true,
[ "encrypt", "decrypt" ]
);
}
const IV_LENGTH = 12; /// 96 bits
async function encrypt(plaintext, key) {
let iv = window.crypto.getRandomValues(new Uint8Array(IV_LENGTH));
let ciphertext = await window.crypto.subtle.encrypt({ name: "AES-GCM", iv: iv }, key, new TextEncoder().encode(plaintext));
return btoa(String.fromCharCode(...iv,...new Uint8Array(ciphertext))); // base64
}
async function decrypt(bufferEncoded, key) {
let buffer = Uint8Array.from(atob(bufferEncoded), c => c.charCodeAt(0)) //base64
return new TextDecoder().decode(await window.crypto.subtle.decrypt({ name: "AES-GCM", iv: buffer.subarray(0, IV_LENGTH) }, key, buffer.subarray(IV_LENGTH)));
}
55 changes: 39 additions & 16 deletions public/javascripts/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,47 @@ if (f.substr(f.length - 1) == '/') {
pathname = temp[temp.length - 1]
}

// Ask server latest data
socket.emit('sync', { path: pathname })

// Set TextArea as server response
socket.on('notify', function (data) {
if (data.path == pathname) {
document.getElementById('text').value = data.content
} else {
console.log('Recieved: ' + data.path + ' Have: ' + pathname)
// Run when DOM ready
window.onload = async function() {
var textElement = document.getElementById('text')

// Initialize encryption
const salt = new TextEncoder().encode('hcvDtp51dTiaLGFXKBWD6r7OhQMZ87ph')
if (!window.location.hash) {
window.location.hash = await generatePassword(16)
}
})
const key = await deriveKey(window.location.hash.substr(1), salt)

// Ask server latest data
socket.emit('sync', { path: pathname })

// Set TextArea as server response
socket.on('notify', async function (data) {
if (data.path == pathname) {
if (data.content) {
try {
data.content = await decrypt(data.content, key)
} catch(e) {
console.log('Unable to decrypt note. Disable update.')
window.processText = function() {} // avoid overwrite
textElement.disabled = true
textElement.value = 'Unable to decrypt note. Please append the correct password to the URL "#<password>" and reload.'
return
}
}
textElement.value = data.content
} else {
console.log('Recieved: ' + data.path + ' Have: ' + pathname)
}
})

// Send latest data to server
function processText () {
var x = document.getElementById('text').value
// Send latest data to server
window.processText = async function () {
var x = textElement.value

if (cache != x) {
socket.emit('data', { text: x, path: pathname })
cache = x
if (cache != x) {
socket.emit('data', { text: await encrypt(x, key), path: pathname })
cache = x
}
}
}
1 change: 1 addition & 0 deletions views/pad.pug
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ html

script(src="/socket.io/socket.io.js" type="text/javascript")
script(src="/javascripts/worker.js", type="text/javascript")
script(src="/javascripts/crypt.js", type="text/javascript")

body
textarea(id="text", autocomplete="off", onkeyup="processText()", placeholder=temp)

0 comments on commit 5c8546a

Please sign in to comment.