A journey through client-side encryption.
It recently became time to tackle a feature in my serverless ledger project that I’d wanted for some time, but had been putting off… encryption.
Concords Ledger doesn’t have a centralized database, each document is stored only in the browser as a cryptographically signed record of transactions. The ledger is downloadable and also has an integration with Solid for remote storage.
To protect this raw data from prying eyes outside the app, I need to consider an encryption solution, and I want this baked into the product.
Password Encryption
My first dive into encryption was password protection. I’d offer the user a UI to enter a password and encrypt the ledger before downloading or saving.
Admission: I didn’t learn too much about this. I followed an excellent example and explanation for building password encryption using the WebCryptoAPI, and ran with it.
OpenPGP
Next up, I wanted a way to encrypt for an identity. Identity is a core part of the Ledger application. All transactions on the ledger are signed using an ECDSA signing key, and can be verified through its public counterpart. So I was keen to discover a way to encrypt directly for an identity from within the app.
OpenPGP seemed to suit this use case perfectly. It’s an open standard, has fair adoption, and importantly for me… has a Javascript implementation.
Using the OpenPGP Javascript library, I replaced the ECDSA identity in the solution with OpenPGP signatures and added key based encryption.
I found OpenPGP pretty complex to get a decent implementation that would be flexible. As I was now trying to reuse PGP identities, it introduced key management as a requirement to my solution. I now needed to upload existing OpenPGP keys and keep the user session. Which being serverless was less than ideal.
So I dived a bit deeper, looking at why I should use OpenPGP, and found plenty of negativity surrounding PGP — for example: https://moxie.org/2015/02/24/gpg-and-me.html.
So I took to the /crypto Reddit to ask: Is OpenPGP relevant when building encryption in software? Where a response led me to look at Age for encryption.
Age
Age looks great, it’s a popular library that seems straightforward in concept for implementing interoperable encryption using a well defined standard.
A small problem here though, Age is written in Go — and my app is serverless, just Javascript running in a browser.
But linked in the Age GitHub repository is a Rust implementation, Rage. This is encouraging. I haven’t had the chance to use WebAssembly yet, but I know Rust is supported, which led me to rage-wasm.
Perfect! A WASM wrapper, of the Rust implementation, of the Age encryption standard (written in Go). Which was surprisingly easy to integrate. I already had my application designed to support both key based, and password encryption — which are the 2 options exported through the rage-wasm library. A few tweaks to my encryption composable function (I like VueJS) and my password and key based encryption are now using Rage.
As I’m no longer using OpenPGP for encryption, I reverted the changes I made for transaction signatures back to using the WebCryptoAPI with generated ECDSA keys.
Conclusion
Encryption is a rabbit hole, and interoperability is tough. I set out to add secure encryption to my client-side application, and I think I achieved this goal.
I’m going to stick with Age as the sole first-class integration for encryption within the product. If Age isn’t for you, the ledger can always be downloaded as raw JSON and encrypted as you see fit… You’ll just need to decrypt it manually before opening the ledger in the app.

What’s Next?
I’d like to expand the encryption feature by exploring transactional based encryption. Introducing granular permissions for sensitive data, while maintaining the ledger's immutable integrity, through the use of public Age keys.