219

Possible Duplicate:
PHP 2-way encryption: I need to store passwords that can be retrieved

I plan to store foreign account information for my users on my website, aka rapidshare username and passwords, etc... I want to keep information secure, but I know that if I hash their information, I can't retrieve it for later use.

Base64 is decrypt-able so there's no point using that just plain off. My idea is to scramble the user and pass before and after it gets base64ed that way even after you decrypt it, you get some funny looking text if you try to decrypt. Is there a php function that accepts values that will make an unique scramble of a string and de-scramble it later when the value is reinputed?

Any suggestions?

Community
  • 1
  • 1
jiexi
  • 3,019
  • 7
  • 26
  • 28
  • 7
    Base64?! Ahahaha, no. Encrypt with AES and use a master password like PassPack does. http://www.phpaes.com/ – mcandre Aug 17 '09 at 16:56
  • 1
    How will the webserver be able to get to the encrypted user credentials information? Yes, by knowing the decryption password. And if I were an attacker with access to your box, how would your scheme hinder me in getting this information? The only thing you get here is a "scrambling", thereby not having this laying around on your box in total plaintext. But it is security-through-obscurity at best. – stolsvik Nov 09 '11 at 12:27
  • 2
    @stolsvik This is an extra layer against SQL injection attacks. In the case where your database is corrupted and not your server, you would still need the key to get sensitive info back. – haknick Feb 06 '12 at 14:28

8 Answers8

303

You should not encrypt passwords, instead you should hash them using an algorithm like bcrypt. This answer explains how to properly implement password hashing in PHP. Still, here is how you would encrypt/decrypt:

$key = 'password to (en/de)crypt';
$string = ' string to be encrypted '; // note the spaces

To Encrypt:

$iv = mcrypt_create_iv(
    mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC),
    MCRYPT_DEV_URANDOM
);

$encrypted = base64_encode(
    $iv .
    mcrypt_encrypt(
        MCRYPT_RIJNDAEL_128,
        hash('sha256', $key, true),
        $string,
        MCRYPT_MODE_CBC,
        $iv
    )
);

To Decrypt:

$data = base64_decode($encrypted);
$iv = substr($data, 0, mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));

$decrypted = rtrim(
    mcrypt_decrypt(
        MCRYPT_RIJNDAEL_128,
        hash('sha256', $key, true),
        substr($data, mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC)),
        MCRYPT_MODE_CBC,
        $iv
    ),
    "\0"
);

Warning: The above example encrypts information, but it does not authenticate the ciphertext to prevent tampering. You should not rely on unauthenticated encryption for security, especially since the code as provided is vulnerable to padding oracle attacks.

See also:

Also, don't just use a "password" for an encryption key. Encryption keys are random strings.


Demo at 3v4l.org:

echo 'Encrypted:' . "\n";
var_dump($encrypted); // "m1DSXVlAKJnLm7k3WrVd51omGL/05JJrPluBonO9W+9ohkNuw8rWdJW6NeLNc688="

echo "\n";

echo 'Decrypted:' . "\n";
var_dump($decrypted); // " string to be encrypted "
Community
  • 1
  • 1
Alix Axel
  • 151,645
  • 95
  • 393
  • 500
  • 1
    by salt you mean adding some random bull inside it right? – jiexi Aug 17 '09 at 16:53
  • 1
    Yes, like sha1('salt' . $password . 'pepper'); – Alix Axel Aug 17 '09 at 17:01
  • 20
    Actually, not "some random", but "random and stored after that".) – BasTaller Jun 23 '11 at 10:10
  • 2
    I was just wondering on the significance of the spaces around `$string`. Are they added artificially before encrypting to increase difficulty of cracking? Sorry if I missed the point on that ;) – wired00 Dec 13 '11 at 00:45
  • 5
    @wired00: No, not at all! If you decrypt an encrypted string you'll notice that some NULL bytes are added at the end of the string. Usually people just use `[r]trim()` to get rid of those NULL bytes, but in the process spaces are also removed. I added the spaces so that people could test and verify that the right way to trim a decrypted string is: `$original = rtrim($decrypted, "\0");` - note the `\0`. =) – Alix Axel Dec 13 '11 at 12:17
  • @Alix Ahh righto thanks for that. Well i got this all working yesterday for passing some non critical variables around. working well thank you – wired00 Dec 15 '11 at 01:41
  • 1
    @OmeidHerat: That statement is a bit far-fetched, but I won't argue that. Either way, the `md5()` is only used to come up with a constant 128 bit IV that `RIJNDAEL_256` in CBC mode requires (which by the way, is way safer than ECB). The algorithm used by `mcrypt` to encrypt/decrypt is still AES (Rijndael). – Alix Axel Jan 09 '12 at 21:48
  • @AlixAxel Agree, Now that I see, it's no big deal, even if IV is disclosed nothing is lost in terms of security. –  Jan 10 '12 at 03:38
  • I do random things like: original password: mypassword salted password: saltMsaltYsaltPsaltAsaltSsaltSsaltWsaltOsaltRsaltD (And each salt being a random character, then on top of that I encrypt the first password, i encrypt each salt, then i encrypt the entire salted string. Perhaps there isnt really much if any gain from all of those, but it makes me sleep better at night – Dylan Cross Jan 15 '12 at 10:29
  • @Alix: The salt should be different for every user. That way, a brute-force attack will break only one user's insecure-password, rather than all the insecure passwords. – BlueRaja - Danny Pflughoeft Jun 11 '12 at 02:40
  • @BlueRaja-DannyPflughoeft: [I know](https://github.com/alixaxel/phunction/blob/43f649321c10f630ba62a051b59f4632f884fe3b/phunction/Text.php#L124). I was just pointing out the basics since the question seems to induce encryption, not hashing. =) – Alix Axel Jun 11 '12 at 09:41
  • 1
    I'm confused; if the OP hashes the foreign account information, how would they use the information later? I realize you did cover how to perform the encryption, but recommended against it. Are you recommending against this practice (using foreign account information) altogether? – Gary Jun 29 '12 at 17:06
  • @Gary: The edit isn't mine, originally I just wanted to emphasize that passwords (common ones) shouldn't be encrypted. Of course, if you need to deal with foreign accounts and you can't use more secure means (such as OAuth) you'll always need to encrypt that sensitive data. =) – Alix Axel Jun 30 '12 at 00:09
  • THIS EXAMPLE IS DANGEROUS. md5() as used returns hex: a 16byte (128bit) key would have only 16 possible characters = 2^64 possibilities = easily broken w/modern graphics card; it has no security. Always pass the 2nd parameter TRUE to MD5($a, TRUE) and SHA1($a, TRUE) calls: w/16bytes the keyspace becomes 2^128 = too large for brute force. Also, RIJNDAEL_256 sets block size, not cipher strength. Using 16byte keys = only 128bit encryption (AES128), not AES256. RIJNDAEL_128 w/32byte keys is (roughly) AES256. Finally, NEVER reuse the same key/IV pair (this example makes IV reliant on key = reused). – Jay Dansand Sep 04 '12 at 19:33
  • @JayDansand: First of all, thanks for the input. =) I'm pretty sure however that you're wrong about the hash digest base: a 16-byte long binary string or a 32-byte long hexadecimal digest are equivalent (128-bit) in terms of security. Furthermore, `mcrypt` requires you to set a fixed length key and IV for the `RIJNDAEL_256` algorithm and CBC mode. I'm also pretty sure that `RIJNDAEL_256` sets block size **and** cipher strength. If you could explain your bit/byte calculations and rationale in more detail I would appreciate it. – Alix Axel Sep 05 '12 at 08:20
  • @JayDansand: Also, I'm pretty interested in your argument for not reusing the IV. I mean, assuming we'll always use the same key (and we don't reuse the IV), we need to (pre/a)ppend the IV to the encrypted data. The only way to successfully decrypt the data would be to find the original key string (or a collision) that yields the same MD5 hash. By reusing the hash of the hash of the key as the IV, one would also need to find the original (or a collision) that yields the very same MD5 hash, so I'm not sure what's the advantage of not reusing the IV... Maybe you can shed some light into this. – Alix Axel Sep 05 '12 at 08:26
  • 1
    RIJNDAEL_256 specifies only block size: http://linux.die.net/man/3/mcrypt The key size (e.g. 128-, 256-bit) determines cipher strength and # of rounds: http://en.wikipedia.org/wiki/Advanced_Encryption_Standard#Description_of_the_cipher The proper use of CBC is to generate a new IV and prepend it to the output. It does not need to be secret, it just needs to be different. If the pair (key, iv) is used for multiple messages, then your security falls apart to cryptanalysis/chosen plaintext attacks to recover the reused key. IVs are for seeding s.t. even repeated blocks have different ciphertexts. – Jay Dansand Sep 05 '12 at 15:50
  • My original numbers were for 16-byte keys from hex vs. binary, in case the example was combined w/substr() & mcrypt_get_key_size() as docs suggest, not 32-byte hex keys vs. 16-byte binary keys. As you say, a 32-byte string of hex is equal in strength to a 16-byte string of binary (which although technically 128 bits, is actually only 2^112 strength). The important note is it's subtle and you can't get 256-bit security that way, even with 32-byte (256-bit) keys. Use binary output of two md5() calls = 256-bit security. Sorry for brevity/terseness, comments are so very limited in length :) – Jay Dansand Sep 05 '12 at 16:02
  • When would you ever need to decrypt the string? If a user is logging in, wouldn't you just encrypt the `$_POST` password and see if it equals the stored encrypted password? – Norse Sep 12 '12 at 03:48
  • 2
    Because it's not his password! Hashing makes sense in a situation where the user enters the password to register, then enters it again to login - you merely hash both, compare the hashes on login and if they're the same, the password was the same. The OP is talking about storing passwords for OTHER SITES - essentially he's saving passwords for other sites. The hash is no use to him, he needs to login with the plaintext password, but he doesn't want to store it as plaintext... so it needs to be a reversible encryption. – Jon Story Sep 14 '12 at 15:13
  • 1
    "You should not encrypt passwords, instead you should hash them using an algorithm like bcrypt." Good luck accessing third part services with a hashed password. That said this is correct, but the communication between his/her server and rapidshare should also be encrypted (a SSL connection) or they could be sniffed. –  Oct 06 '12 at 20:32
  • @ScottHerbert: Once again, that edit isn't mine. Although, I think it's good it's there since the question title / body can be perceived in different ways. – Alix Axel Oct 06 '12 at 23:05
  • My Appoligies Alix. this really highlights the need for protocols like OAuth i guess –  Oct 07 '12 at 17:20
  • 13
    Holy hell. This is bad even as a suggestion for how to store passwords. It's not even reversible (so why are you using encryption in the first place)... And it doesn't use bcrypt (which you mention in the first line). As far as how to encrypt data, take a look at this [SO Answer](http://stackoverflow.com/questions/5089841/php-2-way-encryption-i-need-to-store-passwords-that-can-be-retrieved/5093422#5093422) which includes the proper tools to set it up correctly... Example: you're missing padding (which CBC requires)... – ircmaxell Dec 11 '12 at 17:11
  • 11
    Well, and after looking at the code deeper, you're not storing the password at all. You're using the password as a cipher key to store other data. **So this doesn't even answer the question.** If you're doing that, you **need** to use a key derivation function on the password first. Something like `PBKDF2`... – ircmaxell Dec 11 '12 at 17:24
  • @ircmaxell: I've been meaning to add PKCS-7 padding and a key derivation function such as S2K or PBKDF (which I believe you have a implementation for in one of your GitHub repos), but I always ended up forgetting. The question was somewhat misleading and my answer has been edited lots of times (including the reference to use bcrypt - if you read the question that's not what the OP is looking for). – Alix Axel Dec 11 '12 at 17:58
  • @ircmaxell: As for the flaws you mentioned, `mcrypt_encrypt` automatically pads with NUL bytes, it's not as good as PKCS-7 but IMHO is sufficient for password storage. Key stretching is not implemented here but `hash('sha256', $key, true)` adds a bit more entropy to it. As for "not storing the password at all" the password here is `$string` and the server key is `$key`, so I'm kinda lost on that one. – Alix Axel Dec 11 '12 at 18:02
  • 1
    https://twitter.com/DefuseSec/status/431307906882240512 – Francois G Feb 06 '14 at 10:17
  • @huitseeker: I'm aware. Why is *block* bolded though, like it's a bad thing? – Alix Axel Feb 06 '14 at 10:21
  • @AlixAxel It's bad *in combination with no padding*. Since a block cipher encrypts data in chunks of a given size, whenever the data to be encrypted does not fit exactly in a number of chunks it needs to be padded. Or you face a spectacular loss of entropy in output. – Francois G Feb 06 '14 at 10:43
  • Can we change this to `MCRYPT_RIJNDAEL_128` to make it use AES and then demonstrate an Encrypt-Then-MAC construct? And by "we" I of course mean "I", if no one else has the time to do it. – Scott Arciszewski Jun 09 '15 at 00:30
  • This is deprecated. Use openssl_encrypt. – Ashiqur Rahman May 16 '19 at 15:11
37

Security Warning: This class is not secure. It's using Rijndael256-ECB, which is not semantically secure. Just because "it works" doesn't mean "it's secure". Also, it strips tailing spaces afterwards due to not using proper padding.

Found this class recently, it works like a dream!

class Encryption {
    var $skey = "yourSecretKey"; // you can change it

    public  function safe_b64encode($string) {
        $data = base64_encode($string);
        $data = str_replace(array('+','/','='),array('-','_',''),$data);
        return $data;
    }

    public function safe_b64decode($string) {
        $data = str_replace(array('-','_'),array('+','/'),$string);
        $mod4 = strlen($data) % 4;
        if ($mod4) {
            $data .= substr('====', $mod4);
        }
        return base64_decode($data);
    }

    public  function encode($value){ 
        if(!$value){return false;}
        $text = $value;
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
        $crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $this->skey, $text, MCRYPT_MODE_ECB, $iv);
        return trim($this->safe_b64encode($crypttext)); 
    }

    public function decode($value){
        if(!$value){return false;}
        $crypttext = $this->safe_b64decode($value); 
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
        $decrypttext = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $this->skey, $crypttext, MCRYPT_MODE_ECB, $iv);
        return trim($decrypttext);
    }
}

And to call it:

$str = "My secret String";

$converter = new Encryption;
$encoded = $converter->encode($str );
$decoded = $converter->decode($encoded);    

echo "$encoded<p>$decoded";
Scott Arciszewski
  • 33,610
  • 16
  • 89
  • 206
wilsonpage
  • 17,341
  • 23
  • 103
  • 147
  • 1
    Thanks!! I use this class to store sensitive data in sessions, and I modified a little bit, I added a constructor: function __construct() { $this->skey = md5(session_name()); }. I think in this way it's a little bit more secure :) – BCsongor May 13 '12 at 08:11
  • 6
    THIS ANSWER DOES NOT GENERATE SECURE CIPHER TEXT, PLEASE DO NOT USE. It only works in the sense that it encrypts/decrypts. – Maarten Bodewes Jun 30 '12 at 11:36
  • Your safe_b64**code functions works perfect! – Jevgeni Smirnov Jul 24 '12 at 10:09
  • 7
    `// you can change it` should be `// you MUST change it` – Francisco Presencia Oct 11 '12 at 13:53
  • 15
    No, no no. This is not good. First, it uses ECB mode, which is NOT good. Then, it uses an insecure method for generating IVs. It won't work in a secure way at all. Instead, take a look at something like [this answer provides](http://stackoverflow.com/questions/5089841/php-2-way-encryption-i-need-to-store-passwords-that-can-be-retrieved/5093422#5093422) or just use a library. **THIS IS NOT SECURE** – ircmaxell Dec 11 '12 at 17:27
  • Note that this doesn't work with trailing spaces - if you have a password 'abcde ' it will be decrypted as 'abcde' (without trailing space. – Sherif Buzz Dec 13 '12 at 04:40
  • 1
    Error `Warning: mcrypt_encrypt(): Key of size 4 not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported in` for PHP 5.6.x – Editor Sep 21 '16 at 10:12
  • Error **Call to undefined function mcrypt_get_iv_size()** – Azhar Uddin Sheikh Jul 24 '21 at 17:31
19

Security warning: This code is not secure.

working example

define('SALT', 'whateveryouwant'); 

function encrypt($text) 
{ 
    return trim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, SALT, $text, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)))); 
} 

function decrypt($text) 
{ 
    return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, SALT, base64_decode($text), MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND))); 
} 

$encryptedmessage = encrypt("your message"); 
echo decrypt($encryptedmessage); 
Scott Arciszewski
  • 33,610
  • 16
  • 89
  • 206
stasl
  • 953
  • 7
  • 6
  • 9
    Bad sample; two times random for IV, not using IV at all because of ECB, using ECB encoding in the first place, not using AES compatible code, not padding the message correctly. Yes, it works in the same sense that the [random number generator on xkcd](http://xkcd.com/221/) works. – Maarten Bodewes Jun 30 '12 at 11:35
  • 12
    THIS ANSWER DOES NOT GENERATE SECURE CIPHER TEXT, PLEASE DO NOT USE. It only works in the sense that it encrypts/decrypts. – Maarten Bodewes Jun 30 '12 at 11:36
13

One thing you should be very aware of when dealing with encryption:

Trying to be clever and inventing your own thing usually will leave you with something insecure.

You'd probably be best off using one of the cryptography extensions that come with PHP.

Sebastian Paaske Tørholm
  • 49,493
  • 11
  • 100
  • 118
  • These are not always intuitive to use, or even to know which one to use. Perhaps some code examples would prove valuable here. – Simon East Aug 23 '17 at 08:11
  • 1
    Also, Mcrypt is not recommended anymore. See also https://paragonie.com/blog/2015/05/if-you-re-typing-word-mcrypt-into-your-code-you-re-doing-it-wrong – Simon East Aug 23 '17 at 08:13
4

Securiy Warning: This code is insecure. In addition to being vulnerable to chosen-ciphertext attacks, its reliance on unserialize() makes it vulnerable to PHP Object Injection.

To handle a string / array I use these two functions:

function encryptStringArray ($stringArray, $key = "Your secret salt thingie") {
 $s = strtr(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), serialize($stringArray), MCRYPT_MODE_CBC, md5(md5($key)))), '+/=', '-_,');
 return $s;
}

function decryptStringArray ($stringArray, $key = "Your secret salt thingie") {
 $s = unserialize(rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($key), base64_decode(strtr($stringArray, '-_,', '+/=')), MCRYPT_MODE_CBC, md5(md5($key))), "\0"));
 return $s;
}

It's flexible as in you can store/send via URL a string or array because the string/array is serialzed before encryption.

Scott Arciszewski
  • 33,610
  • 16
  • 89
  • 206
Martin
  • 65
  • 1
  • 1
3

This will only give you marginal protection. If the attacker can run arbitrary code in your application they can get at the passwords in exactly the same way your application can. You could still get some protection from some SQL injection attacks and misplaced db backups if you store a secret key in a file and use that to encrypt on the way to the db and decrypt on the way out. But you should use bindparams to completely avoid the issue of SQL injection.

If decide to encrypt, you should use some high level crypto library for this, or you will get it wrong. You'll have to get the key-setup, message padding and integrity checks correct, or all your encryption effort is of little use. GPGME is a good choice for one example. Mcrypt is too low level and you will probably get it wrong.

Ants Aasma
  • 53,288
  • 15
  • 90
  • 97
2

Check out mycrypt(): http://us.php.net/manual/en/book.mcrypt.php

And if you're using postgres there's pgcrypto for database level encryption. (makes it easier to search and sort)

Josh Rice
  • 37
  • 1
1

The best idea to encrypt/decrypt your data in the database even if you have access to the code is to use 2 different passes a private password (user-pass) for each user and a private code for all users (system-pass).

Scenario

  1. user-pass is stored with md5 in the database and is being used to validate each user to login to the system. This user-pass is different for each user.
  2. Each user entry in the database has in md5 a system-pass for the encryption/decryption of the data. This system-pass is the same for each user.
  3. Any time a user is being removed from the system all data that are encrypted under the old system-pass have to be encrypted again under a new system-pass to avoid security issues.
Nikos Tsirakis
  • 709
  • 4
  • 8