This is a guest post by Jane Doe, a security professional who was asked to do a security review of PfP: Pain-free Passwords. While publishing the results under her real name would have been preferable, Jane has good reasons to avoid producing content under her own name.
I reviewed the code of the Pain-free Passwords extension. It’s a stateless password manager that generates new passwords based on your master password, meaning that you don’t have to back your password database up (although, you also can import your old passwords, which do need backing up). For this kind of password managers, the most sensitive part is the password generation algorithm. Other possibly vulnerable components include those common for all password managers: autofill, storage and cloud sync.
Passwords generated by a password manager have to be unpredictable. For a stateless password manager, there’s another important requirement: it should be impossible to derive the master password back from one of the generated passwords.
PfP satisfies both requirements. Its password derivation algorithm is basically scrypt(N=32768, r=8, p=1), which is an industry standard for key derivation. It generates a binary key based on your master password, website address you’ll use that password for and your login there. (You can also specify revision, in case your password has been stolen and you need a new one.) PfP then translates the binary key to text, using characters from sets you choose. The translation algorithm is designed in a way to include symbols from each of character sets you choose.
The resulting password is completely random for a person who doesn’t know your master password, so they cannot predict what passwords you will use for other websites. Your master password cannot be computed either, as scrypt is a one-way hash function.
scrypt parameters (here we’re talking about N=32768, r=8, p=1) are explained well in this article. It is recommended to use such parameters that key derivation time on the author’s computer will be around 100ms (for cases where the key is used immediately, like in PfP). I run the test on my computer and found out that my computer derives a key in 158ms (average) with the parameters PfP uses, which means it is secure enough.
With this setup, the only attack possible would be brute-forcing the master password, i. e. trying to reverse the one-way hash function, which is prohibitively expensive and time-consuming (we’re talking about tens of years running very expensive computers here). So it’s highly unlikely someone would even try, a much more efficient method would be to install a keylogger on the victim’s computer.
PfP does a good job making sure a password for a specific website can only be filled on that website. I wasn’t able to trick it into autofilling password for one website on another.
But I have discovered a couple of non-security bugs. One of them caused autofill to fail in iframes in Chrome, the other one caused PfP to wait for the submit to appear forever, running code in an endless loop.
PfP uses the API browsers provide to extensions to store passwords (localStorage for the online version). It encrypts everything using AES256-GCM, using a 256-bit encryption key being derived from your master password and salt. Salt is a random 16-byte value stored together with the encrypted data. It is needed to protect you from rainbow table attacks.
Another thing important for encryption is using random initialization vectors. PfP generates 12 random bytes and stores them concatenated with the ciphertexts.
PfP uses HMAC-SHA256 digests for database keys. It needs the keys for retrieving encrypted data to be deterministic, so it cannot use random values. Just SHA256 hashes can be broken (the passwords could be computed based on them in that case), while using scrypt for each action involving storage is expensive performance-wise. With a random HMAC secret being stored encrypted (with a preset database key), it is completely impossible to calculate the password info only having a HMAC digest.
As wih password generation, there’s one way to break such encryption: brute force, which isn’t viable.
PfP allows storing data in Dropbox, Google Drive or remoteStorage. It uses the same encryption model as in local storage with clouds. (Salt value has to be the same across all synced instances, so when a new PfP instance connects to a non-empty cloud storage, it re-encrypts local data to use the salt value saved in cloud.)
In addition to that, PfP signs all cloud-saved data using HMAC, with another secret value, stored encrypted in the cloud. This means that if the cloud provider changes PfP data, the extension will detect it and produce an error.
So, there’s no way the cloud provider can tamper with stored data, as it would be easily detectable, nor it can see your passwords, as they’re encrypted. The only thing a malicious cloud provider could do is delete your files or modify them in a way that would result in sync failure.
HTTPS protocol is used for communicating with the cloud providers. Received data is parsed using JSON.parse() or simple string manipulation functions (e. g. for parsing HTTP headers). This way, no remote code execution attacks are possible.
PfP code doesn’t contain any common mistakes, like listening to keyboard events from a webpage context or otherwise executing sensitive code in an untrusted environment.
I found one minor security vulnerability, namely external links with a
target="_blank" attribute (only used in the “online” PfP version on its
website). Such links should have a
rel="noopener" attribute to prevent attacks
where the tab opened after clicking on one of those links could be able to
redirect the original tab to a different URL. (A more detailed description can
be found here.) It was fixed in
PfP 2.2.2 (it is a web-only release, as the issue didn’t affect browser