Live data from Hacker News

End-to-end encryption in the browser

blog.excalidraw.com

71–80 of 112 posts

Re: End-to-end encryption in the browser

#71

const encrypted = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv: new Uint8Array(12) /* don't reuse key! */ }, key, new TextEncoder().encode(JSON.stringify(content)) ); This looks wrong. The iv should be a randomly generated string that is only used once. I'm not super familiar with modern Javascript, but I think you're just initializing a bunch of null bytes. Honestly, I feel like whoever designed AES int…

Well on the site it was said

"We encrypt the content with that random key. In this case, we only encrypt the content once with the random key so we don’t need an iv and can leave it filled with 0 (I hope…)."

Anyone think that is a good idea?

Re: End-to-end encryption in the browser

#72
post #36
post #17

I appreciate the effort, but this is one of those cases in which there isn't much security added by encryption. If I assume the website owner is malicious (or subverted by powers that be), I cannot trust the JS code provided by the website. Key disclosure is as trivial as one line hidden somewhere in megabytes of JS code delievered from the server. Which makes "end-to-end" part nearly meaningless.

How does that differ from a native app? In any E2E context you have to trust the client code. Sure, there are a couple extra steps needed here: - The website author needs to avoid casually throwing in dependencies (unlike perhaps the average JS project) - HTTPS for loading code resources is crucial; maybe even CDNs need to be avoided - The user needs to avoid having browser extensions enabled But the fundamental prob…

> In any E2E context you have to trust the client code.

You have to trust it not to exfiltrate your local plaintext data, sure; but encryption and key management in a native app might be outsourced to a TPM chip, in a way where the native app can't steal the keys, nor decrypt anything "behind your back", in practical terms meaning there's a smaller surface-area of code to audit.

Re: End-to-end encryption in the browser

#73
post #36
post #17

I appreciate the effort, but this is one of those cases in which there isn't much security added by encryption. If I assume the website owner is malicious (or subverted by powers that be), I cannot trust the JS code provided by the website. Key disclosure is as trivial as one line hidden somewhere in megabytes of JS code delievered from the server. Which makes "end-to-end" part nearly meaningless.

How does that differ from a native app? In any E2E context you have to trust the client code. Sure, there are a couple extra steps needed here: - The website author needs to avoid casually throwing in dependencies (unlike perhaps the average JS project) - HTTPS for loading code resources is crucial; maybe even CDNs need to be avoided - The user needs to avoid having browser extensions enabled But the fundamental prob…

It differs from a native app in that every time they change the JS code behind the website, you would not know. You could have thoroughly inspected the source, and then when you go to use it tomorrow, that one line of spyware could be inserted.

For native apps, you can install a particular version from source and not change it.

Re: End-to-end encryption in the browser

#74
post #56
post #46

Earlier quoted context omitted.

It's using end-to-end encryption to address a slightly different threat model than the usual one: the website operator doesn't want the liability/danger of holding cleartext data. E2E does solve this even in the browser scenario. Now an attacker who dumps the server's disks doesn't compromise user data, they'd have to activate modify the website. This raises the bar of a successful targeted attack and aldosterone bas…

> It's true that this doesn't let you avoid trusting the provider, but you're not going to get that anyway - and this scheme is certainly no worse. (Arguably you're not going to get that on native apps either these days, thanks to closed-source app stores and automatic updates, and automatic updates are a very good thing.) There is no guarantee, but security researchers often check the contents of apps like WhatsApp,…

> For app stores like Google Play or apple app store, there is no such feature to push a special version to a subset of the population specified by name. So suddenly Google, Apple, etc. have to be in on the attack...

With the Android ecosystem specifically, because the OS base-image is customized by the device OEM, an interested state actor can inject a rootkit into devices in their own local market merely by suborning their domestic OEMs, and then manipulating trade tariffs to ensure that domestic citizens are incentivized to buy domestic OEM phone brands.

(Thankfully, this doesn't apply to devices sold into foreign markets, as nobody can predict what brand of phone an arbitrary foreign-citizen person-of-interest is going to choose to buy. Even if they contain the rootkit, there's low likelihood of there being a foreign surveillance system set up specifically with the hopes of seeing what foreign buyers of domestic-OEM devices are up to.)

Re: End-to-end encryption in the browser

#75
post #21
post #19

Earlier quoted context omitted.

It's open source :)

And how can I be sure that the code delivered by the server is the same code as in the public codebase? Nothing stops the owner of the service to run arbitrary JavaScript in Users' browser.

Subresource Integrity allows the HTML file to insist that the js file hasn't changed. I guess it would be possible to download and run the html file from your own machine.

Alternatively, it would be possible to create a service worker that uses a local copy and makes much more of a deal about files changing - it could always confirm changes with the user before allowing a change. Security sensitive apps should probably be doing this.

Re: End-to-end encryption in the browser

#76
post #41

As with every in-browser encryption deployment - what's the threat model here ? > I now sleep much better at night. If the hosting service gets compromised, it doesn’t really matter as none of the content can be decrypted without the key. Which can be easily exfiltrated by the compromiser as they are now in a position to deliver and run arbitrary javascript in your users browser where the keys reside Also, why can't…

Let's say someone vacuums up all the packets going over the wire passively and has some way of breaking TLS. Redundant encryption would be a further obstacle to reading the contents. TLS gets MITM'd in some environments, so it's useful there.

MITM'd == not passive, so can inject malicious js.

Re: End-to-end encryption in the browser

#77

const encrypted = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv: new Uint8Array(12) /* don't reuse key! */ }, key, new TextEncoder().encode(JSON.stringify(content)) ); This looks wrong. The iv should be a randomly generated string that is only used once. I'm not super familiar with modern Javascript, but I think you're just initializing a bunch of null bytes. Honestly, I feel like whoever designed AES int…

> I think you're just initializing a bunch of null bytes.

You're right. From MDN:

> The Uint8Array typed array represents an array of 8-bit unsigned integers. The contents are initialized to 0.

..And the function signature for crypto.subtle.encrypt() describes the iv parameter for the algorithm:

> iv - A BufferSource — the initialization vector. This must be unique for every encryption operation carried out with a given key.

> Put another way: never reuse an IV with the same key.

https://developer.mozilla.org/en-US/docs/Web/API/AesGcmParam...

The code comment implies that the author knew the iv parameter should be unique - and yet passed an array of zeros every time.

Re: End-to-end encryption in the browser

#78

const encrypted = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv: new Uint8Array(12) /* don't reuse key! */ }, key, new TextEncoder().encode(JSON.stringify(content)) ); This looks wrong. The iv should be a randomly generated string that is only used once. I'm not super familiar with modern Javascript, but I think you're just initializing a bunch of null bytes. Honestly, I feel like whoever designed AES int…

It is not wrong. In AES-GCM, the "iv" is a nonce — any value (random, counter, cat picture) that is different for different encryptions with the same key. If you use the key only for one encryption, as it is used here, it can be static (e.g. all zeros).

BTW, regarding the AES comment: the authors of AES designed the block cipher, they didn't design the mode (GCM, which is CTR and GMAC). Nonce is a standard requirement for a stream cipher/stream mode of a block cipher/AEAD. In WebCrypto it's actually hard to skip setting the iv. And again, the author didn't make a mistake here.

Re: End-to-end encryption in the browser

#79

const encrypted = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv: new Uint8Array(12) /* don't reuse key! */ }, key, new TextEncoder().encode(JSON.stringify(content)) ); This looks wrong. The iv should be a randomly generated string that is only used once. I'm not super familiar with modern Javascript, but I think you're just initializing a bunch of null bytes. Honestly, I feel like whoever designed AES int…

Yeah, the WebCrypto API has a built-in function for this. I hope the author fixes the code, because this is otherwise a dangerous tutorial. window.crypto.getRandomValues(new Uint8Array(12)); If the author followed the MDN guides for it, they would have noticed that the example code also uses a random array. https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypt...

No, it doesn't need to be random if the key is used only once.

PS The author didn't just copy something from a tutorial without knowing what he was doing, he actually asked for advice https://github.com/excalidraw/excalidraw/issues/610

Re: End-to-end encryption in the browser

#80

const encrypted = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv: new Uint8Array(12) /* don't reuse key! */ }, key, new TextEncoder().encode(JSON.stringify(content)) ); This looks wrong. The iv should be a randomly generated string that is only used once. I'm not super familiar with modern Javascript, but I think you're just initializing a bunch of null bytes. Honestly, I feel like whoever designed AES int…

> I think you're just initializing a bunch of null bytes. You're right. From MDN: > The Uint8Array typed array represents an array of 8-bit unsigned integers. The contents are initialized to 0 . ..And the function signature for crypto.subtle.encrypt() describes the iv parameter for the algorithm: > iv - A BufferSource — the initialization vector. This must be unique for every encryption operation carried out with a g…

never reuse an IV with the same key

Exactly, the key is always different.

Post reply on HN