I want to know if my usage of the web crypto API is sensible.
I want to use it for symmetric and public-key encryption and decryption, and signatures, but as a non-expert I am scared by the warning on the MDN page:
Warning: The Web Crypto API provides a number of low-level cryptographic primitives. It's very easy to misuse them, and the pitfalls involved can be very subtle.
Even assuming you use the basic cryptographic functions correctly, secure key management and overall security system design are extremely hard to get right, and are generally the domain of specialist security experts.
Errors in security system design and implementation can make the security of the system completely ineffective.
If you're not sure you know what you are doing, you probably shouldn't be using this API.
But having read the docs and these examples, it seems to me that the following usage is sensible.
Symmetric
Generate keys
function symmetricMakeKeys() {
return window.crypto.subtle.generateKey(
{name: "AES-GCM", length: 128},
false,
["encrypt", "decrypt"])
}
Make IV
function makeIv() {
return window.crypto.getRandomValues(new Uint8Array(12))
}
Encrypt
function symmetricEncrypt(key, data, iv) {
return window.crypto.subtle.encrypt({name: "AES-GCM", iv: iv}, key, data)
}
Decrypt
function symmetricDecrypt(key, data, iv) {
return window.crypto.subtle.decrypt({name: "AES-GCM", iv: iv}, key, data)
}
Public-key
Generate keys
function publicKeyGenerateKeys() {
return window.crypto.subtle.generateKey(
{name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"},
false,
["encrypt", "decrypt"])
}
Encrypt
function publicKeyEncrypt(theirPublicKey, data) {
return window.crypto.subtle.encrypt({name: "RSA-OAEP"}, theirPublicKey, data)
}
Decrypt
function publicKeyDecrypt(myPrivateKey, data) {
return window.crypto.subtle.decrypt({name: "RSA-OAEP"}, myPrivateKey, data)
}
Signatures
Generate keys
function signatureGenerateKeys() {
return window.crypto.subtle.generateKey(
{name: "ECDSA", namedCurve: "P-256"},
false,
["sign", "verify"])
}
Sign
function signatureSign(myPrivateKey, data) {
return window.crypto.subtle.sign(
{name: "ECDSA", hash: {name: "SHA-256"}},
myPrivateKey,
data)
}
Verify
function signatureVerify(theirPublicKey, signature, data) {
return window.crypto.subtle.verify(
{name: "ECDSA", hash: {name: "SHA-256"}},
theirPublicKey,
signature,
data)
}
Edit:
After the first answer and comment, I think it is helpful if I give a bit of context about what I need this for.
I'm building a messaging website, where the users' public keys are stored with their usernames on the server. The secret keys are stored only in the browser, along with the plain text of all their messages. Loss of the browser cache causes the loss of the keys and messages.
So the point of the encryption is to protect the messages while they are at rest on the server, waiting to be picked up by the recipient.
I know that a web app cannot provide proper separation between the server and the client, and that the user is trusting the server to serve up safe Javascript and to protect the key database from malicious modification. But the encryption does provide protection if an attacker got read-only access to the server.
Edit 2:
The signatures could be used instead of session tokens.
I don't want there to be any passwords or manual account creation. The account dies when the browser cache dies. So my options are:
When someone visits the site, the server generates a session key, which is dumped in a long-lived cookie on the browser, and the server stores its hash. Subsequent requests, like message uploads and downloads, will use this token.
When someone visits the site for the first time, their signing keys are generated and stored in the browser, and the public key uploaded to the server and stored with their username. Subsequent requests must contain the signature of a random use-once key downloaded from the server.
There seems to be a slight advantage to (2), because although the session tokens in (1) are stored hashed, the plain-text session tokens do exist on the server between coming out of TLS and being hashed, whereas with (1), there is never any secret information existing on the server. It is only a smallish advantage though, and there is the downside that each request will require an extra round trip to get the use-once key to sign.