"Do you think this cannot possibly be secure? Great, prove it. This secret contains the recovery key for a Bitcoin wallet. Crack it and take my money!" Love it.
The first few times I shared this with (security professional) colleagues and friends, they'd dismiss it right away "this can't possibly work", but it was because they _assumed_ it didn't work (too simple). Attaching a challenge made a big difference, they'd spend 5 minutes trying to crack it and, in the process, realize it is actually sound (despite the simplicity).
That being said, I think most security professionals (myself included!) aren't equipped to outright "crack" this kind of thing in just a few minutes, and most should know better than to think that their inability to do so implies soundness.
With that in mind, here are some things I noticed (none of which represent an immediate break!)
* You're using SHA-1 in your KDF. That's probably fine since PBKDF2 doesn't rely on the properties of SHA-1 that have been broken, but the Web Crypto API gives you better alternatives. You could switch it out for SHA2-256 here without any breakage to the rest of the scheme.
* I'm not a JS expert, but I _think_ your encryption page might allow a confused user to reuse an IV[2]. Normally this wouldn't happen because the user would refresh or reload and trigger the `init` on page load, but it would probably be better to generate the IV on demand rather than having it wait in an HTML attribute.
Again, very cool work! The fact that people can make these kinds of self-containing encrypted applications with Web APIs is a serious testament to how far the standards have progressed.
[1]: https://github.com/mprimi/portable-secret/blob/3b22d2b42baf8...
[2]: https://github.com/mprimi/portable-secret/blob/4de5e958fe6f8...