I am creating an application that will store passwords, which the user can retrieve and see. The passwords are for a hardware device, so checking against hashes are out of the question.
What I need to know is:
How do I encrypt and decrypt a password in PHP?
What is the safest algorithm to encrypt the passwords with?
Where do I store the private key?
Instead of storing the private key, is it a good idea to require users to enter the private key any time they need a password decrypted? (Users of this application can be trusted)
In what ways can the password be stolen and decrypted? What do I need to be aware of?
Eh? I don't understand. Do you just mean that password must be recoverable?
As others have said, the mcrypt extension provides access to lots of cryptographic functions - however you are inviting your users to put all their eggs in one basket - one which will be potentially be a target for attackers - and if you don't even know how to start solving the problem then you are doing your users a disservice. You are not in a position to understand how to protect the data.
Most security vulnerabilities come about not because the underlying algorithm is flawed or insecure - but because of problems with the way the algorithm is used within the application code.
Having said that, it is possible to build a reasonably secure system.
You should only consider asymmetric encryption if you have a requirement for a user to create a secure message which is readable by another (specific) user. The reason being that its computationally expensive. If you just want to provide a repository for users to enter and retrieve their own data, symmetric encryption is adequate.
If, however, you store the key for decrypting the message in the same place as the encrypted message (or where the encrypted message is stored) then the system is not secure. Use the same token for authenticating the user as for the decryption key (or in the case of assymetric encryption, use the token as the private key pass phrase). Since you will need to store the token on the server where the decryption takes place at least temporarily, you might want to consider using a non-searchable session storage substrate, or passing the token directly to a daemon associated with the session which would store the token in memory and perform the decryption of messages on demand.
Personally, I would use
mcrypt
like others posted. But there is much more to note...How do I encrypt and decrypt a password in PHP?
See below for a strong class that takes care of everything for you:
What is the safest algorithm to encrypt the passwords with?
safest? any of them. The safest method if you're going to encrypt is to protect against information disclosure vulnerabilities (XSS, remote inclusion, etc). If it gets out, the attacker can eventually crack the encryption (no encryption is 100% un-reversible without the key - As @NullUserException points out this is not entirely true. There are some encryption schemes that are impossible to crack such as OneTimePad).
Where do I store the private key?
What I would do is use 3 keys. One is user supplied, one is application specific and the other is user specific (like a salt). The application specific key can be stored anywhere (in a config file outside of the web-root, in an environmental variable, etc). The user specific one would be stored in a column in the db next to the encrypted password. The user supplied one would not be stored. Then, you'd do something like this:
The benefit there, is that any 2 of the keys can be compromised without the data being compromised. If there's a SQL Injection attack, they can get the
$userKey
, but not the other 2. If there's a local server exploit, they can get$userKey
and$serverKey
, but not the third$userSuppliedKey
. If they go beat the user with a wrench, they can get the$userSuppliedKey
, but not the other 2 (but then again, if the user is beaten with a wrench, you're too late anyway).Instead of storing the private key, is it a good idea to require users to enter the private key any time they need a password decrypted? (Users of this application can be trusted)
Absolutely. In fact, that's the only way I would do it. Otherwise you'd need to store an unencrypted version in a durable storage format (shared memory such as APC or memcached, or in a session file). That's exposing yourself to additional compromises. Never store the unencrypted version of the password in anything except a local variable.
In what ways can the password be stolen and decrypted? What do I need to be aware of?
Any form of compromise of your systems will let them view encrypted data. If they can inject code or get to your filesystem, they can view decrypted data (since they can edit the files that decrypt the data). Any form of Replay or MITM attack will also give them full access to the keys involved. Sniffing the raw HTTP traffic will also give them the keys.
Use SSL for all traffic. And make sure nothing on the server has any kind of vulnerabilities (CSRF, XSS, SQL Injection, Privilege Escalation, Remote Code Execution, etc).
Edit: Here's a PHP class implementation of a strong encryption method:
Note that I'm using a function added in PHP 5.6:
hash_equals
. If you're on lower than 5.6, you can use this substitute function which implements a timing-safe comparison function using double HMAC verification:Usage:
Then, to decrypt:
Note that I used
$e2
the second time to show you different instances will still properly decrypt the data.Now, how does it work/why use it over another solution:
Keys
The keys are not directly used. Instead, the key is stretched by a standard PBKDF2 derivation.
The key used for encryption is unique for every encrypted block of text. The supplied key therefore becomes a "master key". This class therefore provides key rotation for cipher and auth keys.
IMPORTANT NOTE, the
$rounds
parameter is configured for true random keys of sufficient strength (128 bits of Cryptographically Secure random at a minimum). If you are going to use a password, or non-random key (or less random then 128 bits of CS random), you must increase this parameter. I would suggest a minimum of 10000 for passwords (the more you can afford, the better, but it will add to the runtime)...Data Integrity
Encryption:
MCRYPT_BLOWFISH
orMCRYPT_RIJNDAEL_128
cyphers andMCRYPT_MODE_CBC
for the mode. It's strong enough, and still fairly fast (an encryption and decryption cycle takes about 1/2 second on my machine).Now, as to point 3 from the first list, what that would give you is a function like this:
You could stretch it in the
makeKey()
function, but since it's going to be stretched later, there's not really a huge point to doing so.As far as the storage size, it depends on the plain text. Blowfish uses a 8 byte block size, so you'll have:
So for a 16 character data source, there will be 16 characters of data to be encrypted. So that means the actual encrypted data size is 16 bytes due to padding. Then add the 16 bytes for the salt and 64 bytes for the hmac and the total stored size is 96 bytes. So there's at best a 80 character overhead, and at worst a 87 character overhead...
I hope that helps...
Note: 12/11/12: I just updated this class with a MUCH better encryption method, using better derived keys, and fixing the MAC generation...
The example from the manual is slightly edited for this example):
You would use mcrypt_decrypt to decrypt your password.
The best algorithm is rather subjective - ask 5 people, get 5 answers. Personally if the the default (Blowfish) isn't good enough for you, you probably have bigger problems!
Given that it is needed by PHP to encrypt - not sure you can hide it anywhere - welcome comments on this. Standard PHP best coding practices apply of course!
Given that the encryption key will be in your code anyway, not sure what you will gain, providing the rest of your application is secure.
Obviously, if the encrypted password and the encryption key are stolen, then game over.
I'd put a rider on my answer - I'm not a PHP crypto expert, but, I think what I have answered is standard practice - I welcome comments other may have.
Use password_hash and password_verify
And to decrypt:
I tried something like this but please note that I am not cryptographer nor I hold in-depth knowledge about
php
or any programming language. It's just an idea. My idea is to storekey
in some file ordatabase
(or enter manually) which(location) cannot be easily predicted(And of course anything will be decrypted someday, the concept is to lengthen the decryption time) and encrypt sensitive information.Please note that it is just a concept. Any improvement on this code would be highly appreciable.