8

I want to store some data encrypted, for example like a password manager where your master password unlocks all the underlying app/site passwords.

Looking around I found some examples like this, but they seem to use the password as a part of the encryption, similar to a salt in hashing. This means that to decrypt you need the exact same password, so you cannot ever change the password. This doesn't seem great from a security/usability standpoint; if a PW gets compromised, you'd have to remake the whole database under a different PW.

How would you make a system where you can change the master password? Do you you do a simple login check, and then use a string to encrypt/decrypt? Wouldn't the static nature plus storage of that string be unsafe?

I know some PHP and a smidge of Javascript, so if you have examples in those languages that would be nice, but a more general high level explanation is also very much appreciated.

PixelSnader
  • 552
  • 1
  • 4
  • 9
  • 1
    `This means that to decrypt you need the exact same ` - well, yes, how else would you decrypt if not with the same key? You're after a system where you can have N keys to decrypt the same text? That's only doable if you encrypt the same text N times with N keys so if one key gets lost, you got N - 1 keys to decrypt stuff back. I'd argue that it's actually **good** for security because you are supposed to have only one key ever. If you change the key, simply re-encrypt everything with that key, where's the issue? Encryption is a fickle beast, what exactly are you trying to solve? – Mjh Jul 21 '17 at 09:11
  • Intuition says that you could use the master password to encrypt the actual encryption key (just like you password-protect your SSH private keys) but getting encryption right is really hard and intuition often gets on the way (here, it's very likely that having encryption key actually stored somewhere is a bad idea). In any case, I agree with Mjh in that you could edit the question and with some specific details about why exactly you fear that re-encrypting everything is not an option. Are we talking about very large data? Are you worried about data integrity if batch re-encryption crashes? – Álvaro González Jul 21 '17 at 10:38

3 Answers3

4

There are a couple of approaches that work. Jannes's answer alludes to a workable solution (although beware of vulnerabilities in openssl_private_decrypt()).

If you're using Defuse Security's PHP encryption library, password-protected keys are abstracted away. (There's currently an open pull request to tackle making "change password" operations seamless and easy-to-use.)

See also: https://github.com/defuse/php-encryption/blob/master/docs/classes/KeyProtectedByPassword.md

How would you make a system where you can change the master password?

Something like this:

  1. Generate a strong random key. We'll call this secret.
  2. Derive a separate key from the user's master password and a static salt (generated once from a cryptographically secure random number generator). We'll call this passwordKey.
    • Argon2id(password, salt) => passwordKey
  3. Encrypt secret with the passwordKey, using a secure AEAD mode with a random nonce, and store the result alongside the salt.
    • $saved = $salt . $nonce . sodium_crypto_secretbox($secret, $nonce, $passwordKey);
  4. The actual data itself will be encrypted with secret, not passwordKey.

If you need to change the password, just repeats step 2 and 3 with the new password (and a different salt).

For Argon2id, you can use sodium_crypto_pwhash() on PHP 7.2 and newer.

Scott Arciszewski
  • 33,610
  • 16
  • 89
  • 206
1

You could use public-key cryptography, using the public key to encrypt data and with a password in your private key, that can be changed.

One solution would be: 1) Generate rsa private and public keys (on Ubuntu):

openssl genrsa -des3 -out private.key 1024
openssl rsa -in private.key -pubout > public.key

2) Use the public key to encrypt:

$key = file_get_contents('/path/to/public.key');
openssl_public_encrypt("password", $encryptedData, $key);

Save $encryptedData to your database(you cannot use this string as password hash to match for login, as the $encryptedData have random bits added before encryption, you will still need to use a hash function for the passwords).

3) Use the private key to decrypt, providing the password:

$key = openssl_pkey_get_private(file_get_contents('/path/to/private.key'), $password);

if($key === false) {
    // false password
    die;
}
openssl_private_decrypt($encryptedData, $decryptedData, $key);

4) Change the password:

openssl rsa -des3 -in private.key -out private.key

This gives you 2 advantages:

  • Separate encryption & decryption applications, encryption does not require private key or password.
  • Password does not need to be saved inside the application.

This will serve your main requirement of being able to change the password and not re-encrypt the data.

If you want to further secure your private key(to not allow php to directly access your private key, which is useful in case your app is hacked), you can create a decrypt service in your system, that you can use to send the encrypted data with the password and get the decrypted data.

Jannes Botis
  • 11,154
  • 3
  • 21
  • 39
  • Just so you know, your protocol here is vulnerable to a padding oracle attack, because `openssl_private_decrypt()` defaults to PKCS1v1.5 padding. – Scott Arciszewski Dec 29 '17 at 22:54
-1

Have you thought about making use of GPG?

http://php.net/manual/en/ref.gnupg.php

Or the libsoduim PECL extension

https://pecl.php.net/package/libsodium

Using tried and tested crypto vs rolling your own will save you not just time but protect your accounts as desired

jas-
  • 1,801
  • 1
  • 18
  • 30