I'm making a new project in Zend 3 that requires me to have a unique ID or HASH which I can use in several places later.
I looked at many examples on Google, and could not find a function that can satisfy my requirements because this needs to be 99% unique all the time, and it needs to be able to generate hundreds, millions of "hashes" unique all the time.
The following function caught my attention:
function uniqidReal($lenght = 13) {
// uniqid gives 13 chars, but you could adjust it to your needs.
if (function_exists("random_bytes")) {
$bytes = random_bytes(ceil($lenght / 2));
} elseif (function_exists("openssl_random_pseudo_bytes")) {
$bytes = openssl_random_pseudo_bytes(ceil($lenght / 2));
} else {
throw new Exception("no cryptographically secure random function available");
}
return substr(bin2hex($bytes), 0, $lenght);
}
A simple test:
echo "<pre>";
for($i = 0; $i < 100; $i++)
{
echo $this->uniqidReal(25) .PHP_EOL ;
}
The result:
a8ba1942ad99d09f496d3d564
5b24746d09cada4b2dc9816bd
c6630c35bc9b4ed0907c803e0
48e04958b633e8a5ead137bb1
643a4ce1bcbca66cea397e85e
d2cd4c6f8dc7054dd0636075f
d9c78bae38720b7e0cc6361f2
54e5f852862adad2ad7bc3349
16c4e42e4f63f62bf9653f96e
c63d64af261e601e4b124e38f
29a3efa07a4d77406349e3020
107d78fdfca13571c152441f2
591b25ebdb695c8259ccc7fe9
105c4f2cc5266bb82222480ba
84e9ad8fd76226f86c89c1ac1
39381d31f494d320abc538a8e
7f8141db50a41b15a85599548
7b15055f6d9fb1228b7438d2a
659182c7bcd5b050befd3fc4c
06f70d134a3839677caa0d246
600b15c9dc53ef7a4551b8a90
a9c8af631c5361e8e1e1b8d9d
4b4b0aca3bbf15d35dd7d1050
f77024a07ee0dcee358dc1f5e
408c007b9d771718263b536e1
2de08e01684805a189224db75
c3838c034ae22d21f27e5d040
b15e9b0bab6ef6a56225a5983
251809396beb9d24b384f5fe8
cec6d262803311152db31b723
95d271ffdfe9df5861eefbaa4
7c11f3401530790b9ef510e55
e363390e2829097e7762bddc4
7ef34c69d9b8e38d72c6db29f
309a84490a7e387aaff1817ca
c214af2927c683954894365df
9f70859880b7ffa4b28265dbb
608e2f2f9e38025d92a1a4f03
c457a54d2da30a4a517edf14c
8670acbded737b1d2febdd954
99899b74b6469e366122b658c
3066408f5b4e86ef84bdb3fb9
010715f4955f66da3402bfa7b
fa01675690435b914631b46e1
2c5e234c5868799f31a6c983c
8345da31809ab2d9714a01d05
7b4e0e507dd0a8b6d7170a265
5aa71aded9fe7afa9a93a98c5
3714fb9f061398d4bb6af909d
165dd0af233cce64cefec12ed
849dda54070b868b50f356068
fe5f6e408eda6e9d429fa34ed
cd13f8da95c5b92b16d9d2781
65d0f69b41ea996ae2f8783a5
5742caf7a922eb3aaa270df30
f381ac4b84f3315e9163f169e
8c2afa1ab32b6fe402bf97ba3
a9f431efe6fc98aa64dbecbc2
8f0746e4e9529326d087f828b
bfc3cbea4d7f5c4495a14fc49
e4bf2d1468c6482570612360e
f1c7238766acdb7f199049487
60ae8a1ffd6784f7bbbc7b437
30afd67f207de6e893f7c9f42
dfa151daccb0e8d64d100f719
07be6a7d4aab21ccd9942401b
73ca1a54fcc40f7a46f46afbd
94ed2888fb93cb65d819d9d52
b7317773c6a15aa0bdf25fa01
edbb7f20f7523d9d941f3ebce
99a3c204b9f2036d3c38342bb
a0585424b8ab2ffcabee299d5
64e669fe2490522451cf10f85
18b8be34d4c560cda5280a103
9524d1f024b3c9864a3fccf75
0e7e94e7974894c98442241bc
4a17cc5e3d2baabaa338f592e
b070eaf38f390516f5cf61aa7
cc7832ea327b7426d8d2b8c2b
0df0a1d4833ebbb5d463c56bf
1bb610a8bb4e241996c9c756a
34ac2fdeb4b88fe6321a1d9c3
f0b20f8e79090dcb65195524c
307252efdd2b833228e0c301f
3908e63b405501782e629ac0b
29e66717adf14fb30c626103d
c8abd48af5f9332b322dffad0
80cd4e162bc7e8fb3a756b48c
825c00cec2294061eb328dd97
106205a2e24609652d149bc17
f1f896657fbc6f6287e7dee20
0fbd16ade658e24d69f76a225
4ab3b5eeeda86fa81afba796a
11d34f3d2ffb61d55da560ddb
013d6151bad187906fcc579a4
4509279a28f34bcf5327dd4c0
3c0eb47b3f9dc5a2f794bb9ad
1e6506906f23542c889330836
e7b1c5012390f3c7c48def9f3
d86caa695cb5fa1e0a2ead4cc
But I cannot confirm that this does guarantee me a 99% success rate for my production environment.
If someone can advise me, or provide me an example I would much appreciate it!
Function random_bytes generates cryptographically secure random bytes
For openssl_random_pseudo_bytes add the crypto_strong paramdeter to ensure the algorithm used is cryptographically strong.
Since your requirement is only 99% unique cryptographically secure random bytes will meet your requirement.
This should be a comment, but its a bit long.
There is some confusion over your use of "unique" and "all the time". A token is either unique or it is not. Using a random number generator to create tokens alone is not sufficient to guarantee uniqueness - the whole point of a random number generator is that you don't know what the next value to be generated will be - meaning you also don't know that the next number won't be the same as a previous number. OTOH, using random_bytes() or openssl_random_pseudo_bytes() to generate a token which is "99% unique all the time" seems like a massive overkill.
To work out how unique this is likely to be we would need to know how many tokens will be considered within the populations at any one time (or to be able to calculate this from the expected rate of creation and the TTL).
That you are using large numbers rather implies you have a very good reason for not using the simplest and most obvious unique identifier - i.e. an incrementing integer. Hence the resistance to guessing an existing identifier is clearly critical to the implementation - but again you've told us nothing about that.
Pasting the title of your post into Google turns up your post as the top result - with PHP's uniqid() function immediately after it - yet for some reason you've either not found uniqid() or have rejected it for some reason.
The title of your post is also an oxymoron - In order to define an infinite set of identifiers, the identifiers would need to be of infinite length.
it needs to be able to generate hundreds, millions of "hashes"
....and you want it all to run within the Zend Framework? - LOL.
But I cannot confirm that this does guarantee me a 99% success rate for my production environment.
Why not? You have sufficient information here to confirm that the bitwise entropy is evenly distributed and should know the planned capacity of the production environment. The rest is basic arithmetic.
We are about 8x10⁹ people. Imagine all us access your site once each second needing a unique identifier during a year. You need about 2,52288×10²³ identifiers. If you think your site will be in production about 1000 years, and population get bigger by a 1000 factor you need about 10²⁹ identifiers; so a 32 bytes auto-incremental string is good enough. Add as suffix a pseudo-random 32 bytes string to get a secure 64 bytes identifier. Doing a bit plus you can hash identifiers to create tokens.
Then is easy to write a function to get them.
Edited 2017/04/13
A small sample:
The first thing you need is a pseudo-random strong keys generator. I'll post the function I'm using currently:
<?php
function pseudoRandomBytes($count = 32){
static $random_state, $bytes, $has_openssl, $has_hash;
$missing_bytes = $count - strlen($bytes);
if ($missing_bytes > 0) {
// If you are using a Php version before 5.3.4 avoid using
// openssl_random_pseudo_bytes()
if (!isset($has_openssl)) {
$has_openssl = version_compare(PHP_VERSION, '5.3.4', '>=')
&& function_exists('openssl_random_pseudo_bytes');
}
// to get entropy
if ($has_openssl) {
$bytes .= openssl_random_pseudo_bytes($missing_bytes);
} elseif ($fh = #fopen('/dev/urandom', 'rb')) {
// avoiding openssl_random_pseudo_bytes()
// you find entropy at /dev/urandom usually available in most
// *nix systems
$bytes .= fread($fh, max(4096, $missing_bytes));
fclose($fh);
}
// If it fails you must create enough entropy
if (strlen($bytes) < $count) {
// Initialize on the first call. The contents of $_SERVER
// includes a mix of user-specific and system information
// that varies a little with each page.
if (!isset($random_state)) {
$random_state = print_r($_SERVER, TRUE);
if (function_exists('getmypid')) {
// Further initialize with the somewhat random PHP process ID.
$random_state .= getmypid();
}
// hash() is only available in PHP 5.1.2+ or via PECL.
$has_hash = function_exists('hash')
&& in_array('sha256', hash_algos());
$bytes = '';
}
if ($has_hash) {
do {
$random_state = hash('sha256', microtime() . mt_rand() .
$random_state);
$bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
} while (strlen($bytes) < $count);
} else {
do {
$random_state = md5(microtime() . mt_rand() . $random_state);
$bytes .= pack("H*", md5(mt_rand() . $random_state));
} while (strlen($bytes) < $count);
}
}
}
$output = substr($bytes, 0, $count);
$bytes = substr($bytes, $count);
return $output;
}
Once you have that function you need a function to create your random keys:
<?php
function pseudo_random_key($byte_count = 32) {
return base64_encode(pseudoRandomBytes($byte_count));
}
As random does not mean unique! you need to merge a unique 32 bytes prefix as I suggested. As big number functions are time-expensive I'll use a chunk-math function using a prefix I suppose generated from time to time using a cron function and stored at an environment DB variable and an auto-incremental index also db-stored
<?php
function uniqueChunkMathKeysPrefix(){
// a call to read your db for prefix
// I suppose you have an environment string-keyed table
// and a couple of dbfunction to read and write data to it
$last18bytesPrefix = dbReadEnvVariable('unique_prefix');
// Also you store your current index wich returns to 0 once you get
// a 99999999999999 value
$lastuniqueindex = dbReadEnvVariable('last_unique_keys_index');
if ($lastuniqueindex < 99999999999999){
$currentuniqueindex = $lastuniqueindex + 1;
$curret18bytesPrefix = $last18bytesPrefix;
}else{
$currentuniqueindex = 0;
$curret18bytesPrefix = dbReadEnvVariable('next_unique_prefix');
// flag your db variables to notify cron to create a new next prefix
dbStoreEnvVariable('next_unique_prefix', 0);
dbStoreEnvVariable('unique_prefix', $curret18bytesPrefix);
// you have the time needed to have site visits and create new
// 99999999999999 keys as a while to run your cron to adjust your
// next prefix
}
// store your current index
dbStoreEnvVariable('last_unique_keys_index', $currentuniqueindex);
// Finally you create the unique index prefix part
$uniqueindexchunk = substr('00000000000000'.$currentuniqueindex, -14);
// return the output
return $curret18bytesPrefix.$uniqueindexchunk;
}
Now you can write a function for unique pseudo-random 64 bytes uniquekeys
<?php
function createUniquePseudoRandomKey(){
$newkey = uniqueChunkMathKeysPrefix() . pseudo_random_key(32);
// to beautify the output make a dummie call
// masking the 0s ties
return md5($newkey);
}
I have a need to verify password hashes generated using python passlib. My objective is to use passlib's pbkdf2_sha512 scheme for hashing all user passwords. However, due to the nature of our backend, I need to verify this password from php scripts, js and java. I haven't found libraries in either of them that can take a passlib hash and verify the password. I was wondering if there exist one before I set out to implement passlib's hashing algorithm in php, js and java.
I can offer this solution for php:
/*
* This function creates a passlib-compatible pbkdf2 hash result. Parameters are:
* $algo - one of the algorithms supported by the php `hash_pbkdf2()` function
* $password - the password to hash, `hash_pbkdf2()` format
* $salt - a random string in ascii format
* $iterations - the number of iterations to use
*/
function create_passlib_pbkdf2($algo, $password, $salt, $iterations)
{
$hash = hash_pbkdf2($algo, $password, base64_decode(str_replace(".", "+", $salt)), $iterations, 64, true);
return sprintf("\$pbkdf2-%s\$%d\$%s\$%s", $algo, $iterations, $salt, str_replace("+", ".", rtrim(base64_encode($hash), '=')));
}
I you copy the salt, iterations, and algorithm out of an existing passlib-generated hash string, and supply them with the plaintext password to this function, it will generated the same result as passlib.
Here's a php function to just verify a passlib pbkdf2 password, based on the above:
/*
* This function verifies a python passlib-format pbkdf2 hash against a password, returning true if they match
* only ascii format password are supported.
*/
function verify_passlib_pbkdf2($password, $passlib_hash)
{
if (empty($password) || empty($passlib_hash)) return false;
$parts = explode('$', $passlib_hash);
if (!array_key_exists(4, $parts)) return false;
/*
* Results in:
* Array
* (
* [0] =>
* [1] => pbkdf2-sha512
* [2] => 20000
* [3] => AGzdiek7yUzJ9iorZD6dBPdy
* [4] => 0298be2be9f2a84d2fcc56d8c88419f0819c3501e5434175cad3d8c44087866e7a42a3bd170a035108e18b1e296bb44f0a188f7862b3c005c5971b7b49df22ce
* )
*/
$t = explode('-', $parts[1]);
if (!array_key_exists(1, $t)) return false;
$algo = $t[1];
$iterations = (int) $parts[2];
$salt = $parts[3];
$orghash = $parts[4];
$hash = create_passlib_pbkdf2($algo, $password, $salt, $iterations);
return $passlib_hash === $hash;
}
Passlib's pbkdf2_sha256 hash format is custom to passlib, so there (probably?) won't be very many ports of it to other languages. But it's a very simple wrapper around the PBKDF2-HMAC-SHA256 algorithm + base64 encoding, both of which are standard and should have implementations for the other languages -- so it should be pretty easy to port.
However, if portability is a priority requirement, you might want to try using passlib's bcrypt or sha256_crypt hashes instead. Both of those are standard, and should have implementations across a number of languages.
Keep in mind, both bcrypt & sha256_crypt are pretty complex -- so if you can't find a port under one of the languages you need, porting pbkdf2_sha256 is going to be a LOT less effort than porting one of them.
Another option entirely is to invoke passlib under python via a subprocess.
Calling the following python oneliner...
python3 -c 'import sys; from passlib.hash import pbkdf2_sha256 as ph; print(ph.verify(input(), input()))'
... will let you write password\nhash\n to stdin, and have it write back True or False (or an error message if the hash is malformed).
Since the password is being written via stdin, this should be relatively secure (compared to passing it as an argument, or env var).
(The python2 equivalent is the same, just use raw_input() instead of input())
In java you can use jython, which allows to use python libraries and execute python code.
Here is sample java function to verify hash using passlib:
Boolean verify_pbkdf2_sha512(String pw, String hash) {
PythonInterpreter python = new PythonInterpreter();
python.exec("from passlib.hash import pbkdf2_sha512");
python.set("pw", new PyString(pw));
python.set("hash", new PyString(hash));
python.exec("valid = 1 if pbkdf2_sha512.identify(hash) and pbkdf2_sha512.verify(pw, hash) else 0");
Boolean valid = ((PyInteger)python.get("valid")).asInt()==1;
return (Boolean)valid;
}
You can find more information on my blog: http://codeinpython.blogspot.com/2015/11/using-python-passlib-in-java.html
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?
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 one-time pad).
Where do I store the private key?
I would use three 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 configuration 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:
$key = $userKey . $serverKey . $userSuppliedKey;
The benefit there, is that any two 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 two. 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 two (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.).
Here's a PHP class implementation of a strong encryption method:
/**
* A class to handle secure encryption and decryption of arbitrary data
*
* Note that this is not just straight encryption. It also has a few other
* features in it to make the encrypted data far more secure. Note that any
* other implementations used to decrypt data will have to do the same exact
* operations.
*
* Security Benefits:
*
* - Uses Key stretching
* - Hides the Initialization Vector
* - Does HMAC verification of source data
*
*/
class Encryption {
/**
* #var string $cipher The mcrypt cipher to use for this instance
*/
protected $cipher = '';
/**
* #var int $mode The mcrypt cipher mode to use
*/
protected $mode = '';
/**
* #var int $rounds The number of rounds to feed into PBKDF2 for key generation
*/
protected $rounds = 100;
/**
* Constructor!
*
* #param string $cipher The MCRYPT_* cypher to use for this instance
* #param int $mode The MCRYPT_MODE_* mode to use for this instance
* #param int $rounds The number of PBKDF2 rounds to do on the key
*/
public function __construct($cipher, $mode, $rounds = 100) {
$this->cipher = $cipher;
$this->mode = $mode;
$this->rounds = (int) $rounds;
}
/**
* Decrypt the data with the provided key
*
* #param string $data The encrypted datat to decrypt
* #param string $key The key to use for decryption
*
* #returns string|false The returned string if decryption is successful
* false if it is not
*/
public function decrypt($data, $key) {
$salt = substr($data, 0, 128);
$enc = substr($data, 128, -64);
$mac = substr($data, -64);
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
if (!hash_equals(hash_hmac('sha512', $enc, $macKey, true), $mac)) {
return false;
}
$dec = mcrypt_decrypt($this->cipher, $cipherKey, $enc, $this->mode, $iv);
$data = $this->unpad($dec);
return $data;
}
/**
* Encrypt the supplied data using the supplied key
*
* #param string $data The data to encrypt
* #param string $key The key to encrypt with
*
* #returns string The encrypted data
*/
public function encrypt($data, $key) {
$salt = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
$data = $this->pad($data);
$enc = mcrypt_encrypt($this->cipher, $cipherKey, $data, $this->mode, $iv);
$mac = hash_hmac('sha512', $enc, $macKey, true);
return $salt . $enc . $mac;
}
/**
* Generates a set of keys given a random salt and a master key
*
* #param string $salt A random string to change the keys each encryption
* #param string $key The supplied key to encrypt with
*
* #returns array An array of keys (a cipher key, a mac key, and a IV)
*/
protected function getKeys($salt, $key) {
$ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
$keySize = mcrypt_get_key_size($this->cipher, $this->mode);
$length = 2 * $keySize + $ivSize;
$key = $this->pbkdf2('sha512', $key, $salt, $this->rounds, $length);
$cipherKey = substr($key, 0, $keySize);
$macKey = substr($key, $keySize, $keySize);
$iv = substr($key, 2 * $keySize);
return array($cipherKey, $macKey, $iv);
}
/**
* Stretch the key using the PBKDF2 algorithm
*
* #see http://en.wikipedia.org/wiki/PBKDF2
*
* #param string $algo The algorithm to use
* #param string $key The key to stretch
* #param string $salt A random salt
* #param int $rounds The number of rounds to derive
* #param int $length The length of the output key
*
* #returns string The derived key.
*/
protected function pbkdf2($algo, $key, $salt, $rounds, $length) {
$size = strlen(hash($algo, '', true));
$len = ceil($length / $size);
$result = '';
for ($i = 1; $i <= $len; $i++) {
$tmp = hash_hmac($algo, $salt . pack('N', $i), $key, true);
$res = $tmp;
for ($j = 1; $j < $rounds; $j++) {
$tmp = hash_hmac($algo, $tmp, $key, true);
$res ^= $tmp;
}
$result .= $res;
}
return substr($result, 0, $length);
}
protected function pad($data) {
$length = mcrypt_get_block_size($this->cipher, $this->mode);
$padAmount = $length - strlen($data) % $length;
if ($padAmount == 0) {
$padAmount = $length;
}
return $data . str_repeat(chr($padAmount), $padAmount);
}
protected function unpad($data) {
$length = mcrypt_get_block_size($this->cipher, $this->mode);
$last = ord($data[strlen($data) - 1]);
if ($last > $length) return false;
if (substr($data, -1 * $last) !== str_repeat(chr($last), $last)) {
return false;
}
return substr($data, 0, -1 * $last);
}
}
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:
function hash_equals($a, $b) {
$key = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
}
Usage:
$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);
Then, to decrypt:
$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);
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
The updated version uses ENCRYPT-THEN-MAC, which is a far better method for ensuring the authenticity of the encrypted data.
Encryption:
It uses mcrypt to actually perform the encryption. I would suggest using either MCRYPT_BLOWFISH or MCRYPT_RIJNDAEL_128 cyphers and MCRYPT_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:
function makeKey($userKey, $serverKey, $userSuppliedKey) {
$key = hash_hmac('sha512', $userKey, $serverKey);
$key = hash_hmac('sha512', $key, $userSuppliedKey);
return $key;
}
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:
16 bytes for the salt
64 bytes for the hmac
data length
Padding so that data length % 8 == 0
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...
How do I encrypt and decrypt a password in PHP?
By implementing one of many encryption algorithms (or using one of many libraries)
What is the safest algorithm to encrypt the passwords with?
There are tons of different algorithms, none of which are 100% secure. But many of them are secure enough for commerce and even military purposes
Where do I store the private key?
If you have decided to implement a public key - cryptography algorithm (e.g., RSA), you don't store the private key. The user has a private key. Your system has a public key which could be stored anywhere you wish.
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)
Well, if your user can remember ridiculously long prime numbers then - yes, why not. But generally you would need to come up with the system which will allow user to store their key somewhere.
In what ways can the password be stolen and decrypted? What do I need to be aware of?
This depends on the algorithm used. However, always make sure that you don't send password unencrypted to or from the user. Either encrypt/decrypt it on the client side, or use HTTPS (or user other cryptographic means to secure connection between server and client).
However if all you need is to store passwords in encrypted way, I would suggest you to use a simple XOR cipher. The main problem with this algorithm is that it could be easily broken by frequency analysis. However, as generally passwords are not made from long paragraphs of English text I don't think you should worry about it. The second problem with an XOR cipher is that if you have a message in both encrypted and decrypted form you could easily find out password with which it was encrypted. Again, not a big problem in your case as it only affects the user who already was compromised by other means.
The PHP module you are after is Mcrypt.
The example from the manual is slightly edited for this example):
<?php
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "This is a very secret key";
$pass = "PasswordHere";
echo strlen($pass) . "\n";
$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $pass, MCRYPT_MODE_ECB, $iv);
echo strlen($crypttext) . "\n";
?>
You would use mcrypt_decrypt to decrypt your password.
The best algorithm is rather subjective - ask five people, and get five answers. Personally, if the default (Blowfish) isn't good enough for you, you probably have bigger problems!
Given that it is needed by PHP to encrypt, I am not sure you can hide it anywhere. Standard PHP best coding practices apply of course!
Given that the encryption key will be in your code anyway, I am 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 cryptography expert, but, I think what I have answered is standard practice.
A lot of users have suggested using mcrypt... which is correct, but I like to go a step further to make it easily stored and transferred (as sometimes encrypted values can make them hard to send using other technologies like cURL, or JSON).
After you have successfully encrypted using mcrypt, run it through base64_encode and then convert it to hexadecimal code. Once in hexadecimal code it's easy to transfer in a variety of ways.
$td = mcrypt_module_open('tripledes', '', 'ecb', '');
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
$key = substr("SUPERSECRETKEY", 0, mcrypt_enc_get_key_size($td));
mcrypt_generic_init($td, $key, $iv);
$encrypted = mcrypt_generic($td, $unencrypted);
$encrypted = $ua . "||||" . $iv;
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$encrypted = base64_encode($encrypted);
$encrypted = array_shift(unpack('H*', $encrypted));
And on the other side:
$encrypted = pack('H*', $encrypted);
$encrypted = base64_decode($encrypted);
list($encrypted, $iv) = explode("||||", $encrypted, 2);
$td = mcrypt_module_open('tripledes', '', 'ecb', '');
$key = substr("SUPERSECRETKEY", 0, mcrypt_enc_get_key_size($td));
mcrypt_generic_init($td, $key, $iv);
$unencrypted = mdecrypt_generic($td, $encrypted);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
I'd only suggest public key encryption if you want the ability to set a user's password without their interaction (this can be handy for resets and shared passwords).
Public key
The OpenSSL extension, specifically openssl_public_encrypt and openssl_private_decrypt
This would be straight RSA assuming your passwords will fit in key size - padding, otherwise you need a symmetric layer
Store both keys for each user, the private key's passphrase is their application password
Symmetric
The Mcrypt extension
AES-256 is probably a safe bet, but this could be a SO question in itself
You don't - this would be their application password
Both
4. Yes - users would have to enter their application password every time, but storing it in the session would raise other issues
5.
If someone steals the application data, it's as secure as the symmetric cipher (for the public key scheme, it's used to protect the private key with the passphrase.)
Your application should definitely be only accessible over SSL, preferably using client certificates.
Consider adding a second factor for authentication which would only be used once per session, like a token sent via SMS.
The passwords are for a hardware device, so checking against hashes are out of the question
Eh? I don't understand. Do you just mean that passwords 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 it’s 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 asymmetric 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.
I tried something like this, but please note that I am not cryptographer nor do I hold in-depth knowledge about PHP or any programming language. It's just an idea.
My idea is to store a key in some file or database (or enter manually) whose (location) cannot be easily predicted (and of course anything will be decrypted some day. The concept is to lengthen the decryption time) and encrypt sensitive information.
Code
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "evenifyouaccessmydatabaseyouwillneverfindmyemail";
$text = "myemail#domain.com";
echo "Key: " . $key . "<br/>";
echo "Text: " . $text . "<br/>";
echo "MD5: " . md5($text) . "<br/>";
echo "SHA-1: " . sha1($text) . "<br/>";
$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $text, MCRYPT_MODE_ECB, $iv);
echo "Encrypted Data: " . $crypttext . "<br>";
$base64 = base64_encode($crypttext);
echo "Encoded Data: " . $base64 . "<br/>";
$decode = base64_decode($base64);
$decryptdata = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $crypttext, MCRYPT_MODE_ECB, $iv);
echo "Decoded Data: " . ereg_replace("?", null, $decryptdata);
// Even if I add '?' to the sting to the text it works. I don't know why.
Please note that it is just a concept. Any improvement to this code would be highly appreciated.
Use password_hash and password_verify
<?php
/**
* In this case, we want to increase the default cost for BCRYPT to 12.
* Note that we also switched to BCRYPT, which will always be 60 characters.
*/
$options = [
'cost' => 12,
];
echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT, $options)."\n";
?>
And to decrypt:
<?php
// See the password_hash() example to see where this came from.
$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';
if (password_verify('rasmuslerdorf', $hash)) {
echo 'Password is valid!';
} else {
echo 'Invalid password.';
}
?>
I am in the process of creating a login system which uses both sessions (for those who disallow the use of cookies (to agree with the cookie law.. I am using the site http://www.cookielaw.org/the-cookie-law.aspx as a reference)
Now, I have this system for my cookie authentication
function GenerateString(){
$length = mt_rand(0,25);
$characters = '0123456789abcdefghijklmnopqrstuvwxyz';
$string = '';
for ($p = 0; $p < $length; $p++) {
$string .= $characters[mt_rand(5, strlen($characters) -1)];
}
return $string;
}
$RandomString = GenerateString();
$CookieAuth = $DB->prepare("INSERT INTO cookieauth (Username,RandomString) VALUES (?,?)");
$CookieAuth->bind_param('ss',$_POST['Username'],$RandomString);
$CookieAuth->execute(); // Insert the Authentication Methods into the database
$CookieAuth->close(); // Allow another query/statement
$GetInsertID = $DB->prepare("SELECT ID FROM CookieAuth WHERE RandomString=?");
$GetInsertID->bind_param('s',$Randomstring);
$GetInsertID->execute();
$GetInsertID->bind_result($RowID);
$GetInsertID->fetch();
$GetInsertID->close();
setcookie("Auth[ID]",$RowID);
setcookie("Auth[UName],$_POST['Username']);
setcookie("Auth[RandomString]",$RandomString);
Then to process the cookie:
if(isset($_COOKIE['Auth'])){
$Authenticate = $DB->prepare("SELECT Username,RandomString FROM cookieauth WHERE ID=?");
$Authenticate->bind_param('i',$_COOKIE['Auth']['ID']);
$Authenticate->execute();
$Authenticate->bind_result($RowUsername,$RowString);
$Authenticate->fetch();
$Authenticate->close();
if ($_Cookie['Auth']['UName'] == $RowUsername){
if ($_COOKIE['Auth']['RandomString'] == $RowString){
header("Location: LoggedIn.php");
}else{
die("Possible Cookie Manipulation, Autologin Cannot Continue");
}
}else{
die("Possible Cookie Manupulation, Autologin Cannot Continue!");
}
My overall objective is to provide an auto login feature by using cookies. As people should know they are essentially stored on the hard drive as plain text.. So If i include a randomly generated string which will be changed on further processing each time (then updating the cookie to match the database) is this a reasonably secure way to achieve the task? I mean, I understand that this is not 100% secure due to some users might attempt to manipulate the random string, so I could resort to a salt, random key then use hash_hmac to sha512 the salt+key and save that as the cookie...
My overall question, is the chunks I have provided a semi-secure method to process automatic logins via cookies and can minimize the possibility of some bad guys manipulating the keys to achieve the required data?
Introduction
Why do you want to authenticate cookie when that is exactly what sessions are going ? If you want to change the ID you can easily achieve that with session_regenerate_id as #MarcB has pointed.
My Assumptions
I want to assume i did not understand the question clearly and probably this is what you want to achieve
Store Values to Cookie
Know if such values have been modified
You solved it already
I could resort to a salt, random key then use hash_hmac to sha512 the salt+key and save that as the cookie...
That is exactly the the solution but you need to note that
Session Can Sill be hijacked
PHP has better ways of generating random strings
Imagine the overhead having to updated your mysql table every time for something sessions can easily do for you
using hash_hmac 512 would generate 126 in hex format you need to understand that there is Browser Cookie Limits so i suggest you reduce it to 256
Your Solution Modified
If we are going to use your solution we need some little modification
session_start();
// Strong private key stored Securly stored
// Used SESSION For demo
$privateKey = isset($_SESSION['key']) ? $_SESSION['key'] : mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
$my = new SignedCookie($privateKey);
$my->setCookie("test", "hello world", time() + 3600);
echo $my->getCookie("test");
Output
hello world
But the Data was stored like this :
This just uses hash_hmac to sign and verify your values and also uses a random variable to make sure the bad guys are not able to build table of possible values because really they don't have to break the hash .. the can just study it can also use a valid one previously used eg.
10 Cookies = AAAA
1 Cookie = BBBB
He can login with valid session and changed cookies from BBBB to AAAA so even if you re not storing to database always include a random argument
You can also still remove the cookies like this :
$my->setCookie("test", null, time() - 3600);
Simple Class Used
class SignedCookie {
private $prifix = '$x$';
private $privateKey;
function __construct($privateKey) {
$this->privateKey = $privateKey;
}
function setCookie($name, $value, $expire, $path = null, $domain = null, $secure = null, $httponly = null) {
$value = $value === null ? $value : $this->hash($value, mcrypt_create_iv(2, MCRYPT_DEV_URANDOM));
return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
}
function getCookie($name, $ignore = false) {
if (! isset($_COOKIE[$name]) || empty($_COOKIE[$name]))
return null; // does not exist
if ($ignore === false) {
if (substr($_COOKIE[$name], 0, 3) !== $this->prifix)
return - 1; // modified
$data = pack("H*", substr($_COOKIE[$name], 3)); // Unpack hex
$value = substr($data, 32, - 2); // Get Value
$rand = substr($data, - 2, 2); // Get Random prifix
if ($this->hash($value, $rand) !== $_COOKIE[$name])
return - 1; // modified
return $value;
}
return $_COOKIE[$name];
}
function hash($value, $suffix) {
// Added random suffix to help the hash keep changing
return $this->prifix . bin2hex(hash_hmac('sha256', $value . $suffix, $this->privateKey, true) . $value . $suffix);
}
}
Conclusion
You are not a security expert Just Use so just use SSL (SSL also has its issues but far better) or Look for an existing secure authentication service. #ircmaxell reminded me of Schneier's Law recently :
#Baba: "surprise" is the enemy of security. The ONLY thing that should be secret is the private key. Remember Schneier's Law: Anyone can invent an encryption scheme that they themselves can't break. My answer is based on tried and true cryptographic principles.
well i think you should take to that advice too.
I am currently working on a project which requires closed loop email verification. As part of the process I need to generate a random hash string which can be appended to a link sent to the user. When they click the link they will be directed to my site at which time the app will confirm the hash and complete the registration process. For all my hashing I have been using:
hash('sha256', $string);
But for this process, I need to seed $string with a random value. I have Zend Framework available and was looking to do something like this:
$crypt = new Zend_Filter_Encrypt_Mcrypt(array());
$hash = hash('sha256', $crypt->getVector());
My question is, is this a viable algorithm for generating random hash codes?
Here is the Zend_Filter_Encrypt_Mcrypt::setVector() method (generates the value returned via getVector():
public function setVector($vector = null)
{
$cipher = $this->_openCipher();
$size = mcrypt_enc_get_iv_size($cipher);
if (empty($vector)) {
$this->_srand();
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
$method = MCRYPT_RAND;
} else {
if (file_exists('/dev/urandom') || (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')) {
$method = MCRYPT_DEV_URANDOM;
} elseif (file_exists('/dev/random')) {
$method = MCRYPT_DEV_RANDOM;
} else {
$method = MCRYPT_RAND;
}
}
$vector = mcrypt_create_iv($size, $method);
} else if (strlen($vector) != $size) {
require_once 'Zend/Filter/Exception.php';
throw new Zend_Filter_Exception('The given vector has a wrong size for the set algorithm');
}
$this->_encryption['vector'] = $vector;
$this->_closeCipher($cipher);
return $this;
}
I'm not very familiar with ZF, but something that has the word Encrypt in it just sounds like the wrong approach.
The ->getVector() sounds similar to what the Initialization Vector does in symmetric encryption; the problem is that such a vector doesn't need to be cryptographically safe, just random. For instance, it may well be just implemented as uniqid(mt_rand()) or something.
->getVector() uses mcrypt to first initialize the encryption cipher to know how big the IV should be; this is typically 8 bytes, but it largely depends on the block size of the used cipher. The thing is, you're not encrypting anything; you just want a random sequence.
The better way to get a random sequence is by using openssl_random_pseudo_bytes() with a size of 8 bytes.
In its absence, you could also read from an entropy file such as /dev/random or /dev/urandom. Afterwards you can run it through binh2hex() to generate a hexadecimal string.
Something like this is pretty rudimentary but should work on Linux'y systems:
$rnd = bin2hex(file_get_contents('/dev/urandom', false, null, 0, 8));
As a fallback for Windows, you can still use something like:
$rnd = hash('sha256', uniqid(mt_rand(), true));
You may want to look into ircmaxell's CryptLib which has a fairly comprehensive suite of random generation features. If you use the medium strength random string generator, like so:
$generator = ( new CryptLib\Random\Factory() )->getMediumStrengthGenerator();
$string = $generator->generateString(LENGTH);
The library will use multiple cryptographically secure sources and run them through a mixer to generate a string. It's worth checking into if you just want a simple solution and don't want to recompile PHP with openssl.
See the readme on secure string generation.