Similar to the question in the "Making RSA keys from a password in python
" question, I want to to repeatedly create the same pair of RSA keys using a given password.
The question mentioned above has this code as the answer:
from Crypto.Protocol.KDF import PBKDF2
from Crypto.PublicKey import RSA
password = "swordfish" # for testing
salt = "yourAppName" # replace with random salt if you can store one
master_key = PBKDF2(password, salt, count=10000) # bigger count = better
def my_rand(n):
# kluge: use PBKDF2 with count=1 and incrementing salt as deterministic PRNG
my_rand.counter += 1
return PBKDF2(master_key, "my_rand:%d" % my_rand.counter, dkLen=n, count=1)
my_rand.counter = 0
RSA_key = RSA.generate(2048, randfunc=my_rand)
I tried to replicate this code in PHP like this:
$password = "swordfish";
$salt = "yourAppName";
$master_key = hash_pbkdf2("sha256", $password, $salt, 10, 256);
$counter = 0;
function my_rand($n) {
$counter++;
return hash_pbkdf2("sha256", $master_key, "my_rand:" . $counter, 10, 256);
}
$RSA_key = openssl_pkey_new(???);
But I now don't know how to replicate the RSA key generator using the custom random function as the PHP openssl_pkey_new function and the phpseclib both do not have an option to add a custom random function.
What do I have to do to repeatedly generate the same RSA key pair from a given password?
TL;DR: you should not generate a RSA key pair from a given password
It looks like both OpenSSL nor phpseclib have been created with this in mind. This is not surprising as there is much to be said against the solution itself. Furthermore, there are many implementation issues, especially for RSA.
If you would still go ahead to implement this ill advised scheme, then you should look up a PHP implementation for the key generation and then store that with your solution. The reason for this is that otherwise the random number generation, prime finding or RSA key generation method may change internally (when you upgrade the library) and produce a different key pair. I would not use this solution on Java even though it does let you insert your own RNG to allow for deterministic key pair generation, which is the technical term of what you're trying to do.
There are other ways of having users keep their private key secure, such as password based encryption of the key. This requires storage of the encrypted key, but at least it is common practice and has much less reason to fail.
You'll need some kind of hybrid cryptography if you want to encrypt arbitrarily sized messages - just like you'd have to with any other kind of RSA key pair, really.
Note that the situation is slightly better for Elliptic Curve cryptography. You could combine PBKDF2 with SHA-256 to create a private key, and then derive the public key by performing point multiplication of base point G with that private key value. This is much less likely to fail. You could use ECIES to encrypt with Elliptic Curves, as EC does not have a way to directly encrypt messages or keys.
However, you'd still have the problem that you can never change the password, and that you have to use a constant salt to always generate the same private key and public keys. Because of this, I would still not recommend the scheme, as it allows offline, multi-target attacks to find the password, using the public key value or a ciphertext.
To be sure that the password cannot be found it needs to be really strong - so strong that you probably have to store it somewhere.
Related
The PHP documentation for the openssl_encrypt functions states
string openssl_encrypt ( string $data , string $method , string
$password [, int $options = 0 [, string $iv = "" ]] )
Can somebody help me understand what the argument named $password is?
An answer could include a confirmation or rejection of the idea, that besides named $password the parameter indeed is used as the key for the encryption.
What is the password parameter to openssl_encrypt? Is it a password string (with only printable characters) or is it a key (with non-prinatable characters and ASCII-Z terminators)?
Explanation
I am stuck with the documention of PHP's openssl_encrypt. Being a nice guy and trying to do the "RTM" I cannot make much sense with the imho unsatisfying documentation.
The problem is that for me there is a difference between a password and a key when it comes to encryption. A key is directly the parameter used for encryption and hence necessarily of a specific size - "the keylength" - i.e. 128/256/512 bits depending on the cipher and keylength desired. A password on the other hand is a to my understanding a human readable string entered via the keyboard which may in difference be of any length and which is before being used to encrypt first converted into a key.
hence schematic difference:
password => key => encryption
key => encryption
Unfortunatelly in the PHP openssl_encrypt documentation I cannot find any information how to use a key. The only thing suggested is a parameter "password".
Can anybody give me a glue how a key can be used?
Surely I donot want to enter the key as the password parameter as I want a specific key to be used in encryption. I do not want this key to be simply missunderstood as a parameter and serve for another key being calculated from my "key mistaken as password".
Additionally the mistery continues looking at the documention regarding the initialization vector parameter in the same openssl_encrypt function. It simply states:
iv A non-NULL Initialization Vector.
and that iv should be a string. Given that the iv is normaly a binary data of a certain length and for instance a string terminating \0 (hex 0x00) can be occuring inside I am puzzled what format is desired.
In essence I feel very much left alone with the PHP documentation which also states
WARNING This function is currently not documented; only its argument
list is available.
Update
I did some testing, and "trying around" to help me figure out what the password parameter is.
using this code:
$pass="0123456789abcdefghijklmnob";
$iv="0123456789abcdef";
echo "using $pass results:\n";
echo openssl_encrypt("test secret string", 'aes-128-cbc', $pass,NULL,$iv);
I get this result:
using 0123456789abcdefghijklmnob results:
XjEeaLucY38Y6XEUceYMYKTebR4kOp3s727ipMl5KNc=
Then changing the length of the "password" parameter:
$pass="0123456789abcdefg"; //hijklmnob";
$iv="0123456789abcdef";
echo "using $pass results:\n";
echo openssl_encrypt("test secret string", 'aes-128-cbc', $pass,NULL,$iv);
I get still the same encrypted code:
using 0123456789abcdefg results:
XjEeaLucY38Y6XEUceYMYKTebR4kOp3s727ipMl5KNc
It seems by way of testing yet not by way of being informed by the documentation that
the password is indeed only considered up to the first 16 bytes which seem to be the 128 bit that would be the key.
It frightens me that in such a sensitive function (used for encryption) the documentation is bad and excessive input of one poorly documented parameter is not even warned about. Also I am quite convinced that password should rather be named key as it seems those 16 bytes do directly represent the key.
The $password parameter is indeed the key. The key size depends on the AES mode you're using (as you know).
As you noted in your update, for AES-128, only the first 16 characters/bytes count for the key. For AES-256, it would be the first 32 characters.
When one uses the openssl_encrypt() and openssl_decrypt() functions, one can simply pass a 32 character human-readable password for the $password/key parameter.
For example, my input for the password parameter might be $password = "This is 32 characters long......";
Most people don't like having to write up a plaintext password that is a fixed length, so they might compute a hash and truncate it to the correct length. They would use this hash as their encryption key/password.
For example, I could compute an MD5 hash of a password/phrase of any length that I would like and then use that as my AES password/key:
$plaintextPass = 'This is my password. This password is not exactly 32 bytes, but that is okay because I am hashing this.';
$password = hash('md5', $plaintextPass); /* the encryption key */
With that in place, no matter what plaintext password I use, I can have a valid 32 character/byte string as my encryption key/password. This does reduce the entropy of the encryption key/password, though, because a normal 32 character string has a larger key space than an MD5 hash output (256 possibilities per byte vs. 16 possibilities per byte); however, 16^32 is still certainly out of the range of brute force.
Off topic: In some of my personal programs, I have been working on using 32 randomly generated bytes with values between 0-255. This would make the entropy of the encryption key 256^32 which is infeasible to bruteforce. This will also be resistant to dictionary attacks because the 32-bytes are randomly generated using a cryptographically secure pseudo-random number generator (CSPRNG).
So, to sum this all up, yes, the $password parameter is indeed the key used for encryption. I agree with you that is should be written as $key in the documentation, but oh well. The password/key which you select for encryption can be humanly readable as long as it satisfies the length requirements of the hashing key. To satisfy these length requirements, one can hash a human-readable/plaintext password and use the hash of that password as the key ($password parameter), but the human-readable password should still be long and unique.
The documentation is not very clear, however this comment on the openssl_encrypt() PHP doc page really helped me when I was trying to understand the function. The comment provides an example as well as useful information. To direct quote the author of the comment:
Because the password parameter documented here is not the password.
...
It is the key!
[Comment minorly edited]
I am coding with openssl, and I would like to know, why the openssl_sign function, gives a diferent result than openssl_private_encrypt in a logical sense.
Specifically with openssl_sign:
$fp = fopen("i.pem", "r"); //i.pem is the private key file
$priv_key = fread($fp, 8192);
fclose($fp);
$pkeyid = openssl_get_privatekey($priv_key);
$data="f2e140eb-2b09-44ab-8504-87b25d81914c";
openssl_sign($data, $signature, $pkeyid);
$reto22 = base64_encode($signature); //this gives UNmlEfwISea9hoGfiwdM.......
Specifically with openssl_private_encrypt:
$llave_priv = file_get_contents("i.pem"); //i.pem is the private key file
$plaintext = "f2e140eb-2b09-44ab-8504-87b25d81914c";
openssl_private_encrypt($plaintext, $encrypted, $llave_priv);
$reto = base64_encode($encrypted); //this gives ugSMAsCQlIKIlQ17exIvSEqkA60.......
Why is reto22 is different than $reto? they should be the same, shouldn't they?
encrypt with priv key = sign, as far as I know
thanks for clarifying
mario
Generally speaking, Encryption in public key systems is performed with the public key (so that the private key can be used to decrypt it) while signing is done with the private key (so that the public key can be used to verify it)
Signatures with openssl involve encrypting the hash of the message. So even if the same key is used, the output will be different, because while openssl_private_encrypt does encrypt with the private key like you would in a signature scheme, it doesn't hash the message, or (possibly, not certain) perform the same padding that a signature scheme would perform.
Stick with openssl_sign, as it will be more efficient and less prone to potential side channel attacks than rolling your own signature scheme.
See this answer:
https://stackoverflow.com/a/2706636/1359088
It's helpful because it explains that openssl_sign performs a hash on the data internally before returning the signature, whereas openssl_private_encrypt requires you to perform the hash yourself. I understand conceptually why you want to openssl_sign (because encrypting is normally done with the public key, whereas signing is with the private key), but I was going crazy because SSCrypto has a method named sign that was returning the same data as openssl_private_encrypt rather than openssl_sign, and that answer above helped me to sort it out. I'm signing a message in an iPhone app, which will be verified by PHP; I'm using SSCrypto for the signing and openssl_verify to verify, but I'm testing with openssl_sign because I need the data to be identical to work.
You can use flour to make bread and you can use flour to make a roux. Yet bread isn't a roux and a roux isn't bread.
Similarly, encryption isn't signing and signing isn't encryption.
I did a series of research on this topic, but unfortunately I couldn't find a perfect way to encrypt and decrypt files in PHP. Which mean what I'm trying to do is find some way to encrypt & decrypt my items without worry of cracker knew my algorithm. If some algorithm that need to secrete & hide, it can't solve my problems while once the logic shared through anywhere, or they broke into my server and get the source file, then it should be some way to decrypt it using the same decryption algorithm. Previously I found several great posts on StackOverFlow website, but it still couldn't answer my question.
The best way to encrypt password of the world, from what I conclude through reading. Blowfish encryption. It's one way hashing algorithm with 1000's times iteration which make cracker need 7 years to decrypt by using the same specification GPU.
Obviously, this makes it impossible to decrypt while it's one-way hashing.
How do you use bcrypt for hashing passwords in PHP?
Why do salts make dictionary attacks 'impossible'?
The best way to encrypt and decrypt password in PHP, as this question quote as it is. Refer to what I found through the web, sha1 and md5 both are cracked & broken algorithm, even we change the algorithm from
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $string, MCRYPT_MODE_CBC, md5(md5($key))));
To
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, sha1(md5($key)), $string, MCRYPT_MODE_CBC, sha1(md5(md5($key)))));
Are not it's just increasing the toughness to decrypt it but still crack-able while just time issue ?
Best way to use PHP to encrypt and decrypt passwords?
I'm thinking of using our server processor / harddisc GUID to generate the salt and encrypt the password.
It's still some stupid way to do while cracker got the access to the server and they can just use PHP to echo the GUID and do the decryption. Or if it works, a few years later my website will be in trouble. The reason is harddisc, processor never last forever. When the time my processor or harddisc down, it's a time when my website down and lost all the credential.
Update
Found this question which doing with blowfish for decryption in PHP. Is it solving the question of finding secured way to encrypt and hard to decrypt by others ?
How to decrypt using Blowfish algorithm in php?
Can anyone please suggest on how should I overcome this issue ? Thanks.
Checkout this well documented article A reversible password encryption routine for PHP, intended for those PHP developers who want a password encryption routine that is reversible.
Even though this class is intended for password encryption, you can use it for encryption/decryption of any text.
function encryption_class() {
$this->errors = array();
// Each of these two strings must contain the same characters, but in a different order.
// Use only printable characters from the ASCII table.
// Do not use single quote, double quote or backslash as these have special meanings in PHP.
// Each character can only appear once in each string.
$this->scramble1 = '! #$%&()*+,-./0123456789:;<=>?#ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~';
$this->scramble2 = 'f^jAE]okIOzU[2&q1{3`h5w_794p#6s8?BgP>dFV=m D<TcS%Ze|r:lGK/uCy.Jx)HiQ!#$~(;Lt-R}Ma,NvW+Ynb*0X';
if (strlen($this->scramble1) <> strlen($this->scramble2)) {
trigger_error('** SCRAMBLE1 is not same length as SCRAMBLE2 **', E_USER_ERROR);
} // if
$this->adj = 1.75; // this value is added to the rolling fudgefactors
$this->mod = 3; // if divisible by this the adjustment is made negative
}
Caution:
If you are using PHP version >= 5.3.3, then you have to change the class name from encryption_class to __construct
Reason:
As of PHP 5.3.3, methods with the same name as the last element of a namespaced class name will no longer be treated as constructor.
Usage:
$crypt = new encryption_class();
$crypt->setAdjustment(1.75); // 1st adjustment value (optional)
$crypt->setModulus(3); // 2nd adjustment value (optional)
/**
*
* #param string $key - Your encryption key
* #param string $sourceText - The source text to be encrypted
* #param integer $encLen - positive integer indicating the minimum length of encrypted text
* #return string - encrypted text
*/
$encrypt_result = $crypt->encrypt($key, $sourceText, $encLen);
/**
*
* #param string $key - Your encryption key (same used for encryption)
* #param string $encrypt_result - The text to be decrypted
* #return string - decrypted text
*/
$decrypt_result = $crypt->decrypt($key, $encrypt_result);
Update:
Above class is not intended for encrypting files, but you can!!!
base64_encode your source text (file contents)
for actual encryption, apply above enc/dec class over base64-encoded text
for decryption, apply above enc/dec class over actually encrypted text
base64_decode will give you the actual file contents (you can save a copy of file with this content)
I've encrypted an image, decrypted back and saved to a new file!!! checkout the code.
//class for encrypt/decrypt routines
require 'class.encryption.php';
//configuring your security levels
$key = 'This is my secret key; with symbols (#$^*&<?>/!#_+), cool eh?!!! :)';
$adjustment = 1.75;
$modulus = 2;
//customizing
$sourceFileName = 'source-image.png';
$destFileName = 'dest-image.png';
$minSpecifiedLength = 512;
//base64 encoding file contents, to get all characters in our range
//binary too!!!
$sourceText = base64_encode(file_get_contents($sourceFileName));
$crypt = new encryption_class();
$crypt->setAdjustment($adjustment); //optional
$crypt->setModulus($modulus); //optional
//encrypted text
$encrypt_result = $crypt->encrypt($key, $sourceText, $minSpecifiedLength);
//receive initial file contents after decryption
$decrypt_result = base64_decode($crypt->decrypt($key, $encrypt_result));
//save as new file!!!
file_put_contents($destFileName, $decrypt_result);
Bear in mind that, in order to crack passwords, a hacker would have to have access to the encrypted passwords in the first place. In order to do that they would have to compromise the server's security, which should be impossible if the site is coded correctly (proper escaping or prepared statements).
One of the strongest yet simplest forms of encryption is XOR, however it is entirely dependent on the key. If the key is the same length as the encoded text, then it is completely unbreakable without that key. Even having the key half the length of the text is extremely unlikely to be broken.
In the end, though, whatever method you choose is secured by your FTP/SSH/whatever password that allows you to access the server's files. If your own password is compromised, a hacker can see everything.
Your question leads to two different answers. It's an important difference, whether you need to decrypt the data later (like files), or if you can use a one way hash (for passwords).
One-Way-Hash
If you do not need to decrypt your data (passwords), you should use a hash function. This is safer, because even if an attacker has control over your server and your database, he should not be able to retrieve the original password. Since users often use their password for several websites, at least he doesn't gain access to other sites as well.
As you already stated, one of the most recommended hash functions today, is bcrypt. Despite it's origin in the blowfish algorithm, it is in fact a hash function (not encryption). Bcrypt was designed especially to hash passwords, and is therefore slow (needs computing time). It's recommended to use a well established library like phpass, and if you want to understand how to implement it, you can read this article, where i tried to explain the most important points.
Encryption
If you need to decrypt your data later (files), you cannot prevent, that an attacker with control over your server, can decrypt the files as well (after all the server has to be able to decrypt it). All adds up to the question of where to store the secret key. The only thing you can do, is to make it harder to get the key.
That means, if you store the key in a file, it should be outside the http root directory, so it can on no account be accessed from the internet. You could store it on a different server, so the attacker would need control over both servers, though then you face the problem of the secure communication between the servers. In every case, you can make theft harder, but you cannot prevent it completely.
Depending on your scenario, you could encrypt the files locally on your computer, and only store the encrypted files on the server. The server would not be able to decrypt the files on it's own then, so they are safe.
So you already know about salting and hashing, but you can also "stretch" your passwords, where instead of just hashing each password once, you hash it several thousand times. This will slow down brute force attacks and increase the lifespan of your hashing algorithm. Interestingly it works by intentionally slowing down your server...
What I would recommend is writing your own custom hash function. First, you add salt to the password, then you pick a hash algorithm (say sha512, or perhaps a newer algorithm that is designed to be inefficient for this very purpose) and hash it, say, 10,000 times, then store it in the database. And as you already know, when a user logs in, instead of reversing the hash, you simply run their input through the same algorithm and see if it matches.
The beauty of writing your own hash function is that when it comes time to update your hash algorithm because the old one has become vulnerable to brute force attacks, all you have to do is add to your hash function, taking the result of the old hash algorithm, re-salting it, and hashing it again using your new algorithm. You can use whatever hash algorithm is considered secure at the time. Then, you can simply re-hash every password already stored in your database with the new part of your hash function, thus ensuring backwards compatibility. Depending on how many users you have and how fast your server is, it might only take a couple of seconds to perform this update.
There is still a vulnerability, however. If a hacker has an old copy of your database and cracks it, he still knows the passwords of any users who haven't changed their passwords yet. The only way around this is to require your users to occasionally change their passwords, which may or may not be suitable for your site depending on the nature of the information it contains. Some security professionals suggest that users only change their passwords if they are compromised because if the system makes it too difficult to manage passwords, they will begin doing insecure things like keeping their passwords under their keyboards, which for some organizations is a bigger threat than having users that never change their passwords. If your website is a forum or review site or something of that nature, you should consider how much users have to lose by having their account hacked, how easy it is to restore their data to the way it was before it was hacked, and whether they will consider your site worth updating their password for if your password policy is too annoying.
One possible hash function:
function the_awesomest_hash($password)
{
$salt1 = "awesomesalt!";
$password = $salt1 . $password;
for($i = 0; $i < 10000; $i++)
{
$password = hash('sha512', $password);
}
// Some time has passed, and you have added to your hash function
$salt2 = "niftysalt!";
$password = $salt2 . $password;
for($i = 0; $i < 10000; $i++)
{
$password = hash('futuresuperhash1024', $password);
}
return $password;
}
Now, in order to update all the passwords already in your database, you would run them through this function:
function update_hash($password)
{
// This is the last part of your the_awesomest_hash() function
$salt2 = "niftysalt!";
$password = $salt2 . $password;
for($i = 0; $i < 10000; $i++)
{
$password = hash('futuresuperhash1024', $password);
}
return $password;
}
I like to write my own hash functions because it's easier to keep track of what exactly is happening for when it comes time to update them.
After some study of PHP, particularly the random number generation, the only way to securely encrypt with PHP is by using an OpenSSL wrapper. Especially the creators of mcrypt are a bunch of morons, just look at the example of not how to perform cryptography in their sample:
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "This is a very secret key";
$text = "Meet me at 11 o'clock behind the monument.";
echo strlen($text) . "\n";
$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_ECB, $iv);
echo strlen($crypttext) . "\n";
Note that by default MCRYPT_RAND is not seeded well. Furthermore, there is at least about 5 mistakes in above code alone, and they won't fix it.
[EDIT] See below for an ammended sample. Note that this sample is not very safe either (as explained above). Furthermore normally you should not encrypt passwords...
# the key should be random binary, use scrypt, bcrypt or PBKDF2 to convert a string into a key
# key is specified using hexadecimals
$key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
echo "Key size (in bits): " . $key_size * 8 . "\n";
$plaintext = "This string was AES-256 / CBC / ZeroBytePadding encrypted.";
echo "Plain text: " . $plain_text . "\n";
$ciphertext_base64 = encryptText($key, $plaintext);
echo $ciphertext_base64 . "\n";
function encryptText(string $key_hex, string $plaintext) {
# --- ENCRYPTION ---
# show key size use either 16, 24 or 32 byte keys for AES-128, 192 and 256 respectively
$key_size = strlen($key);
# create a random IV to use with CBC encoding
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
# use an explicit encoding for the plain text
$plaintext_utf8 = utf8_encode($plaintext);
# creates a cipher text compatible with AES (Rijndael block size = 128) to keep the text confidential
# only suitable for encoded input that never ends with value 00h (because of default zero padding)
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plaintext_utf8, MCRYPT_MODE_CBC, $iv);
# prepend the IV for it to be available for decryption
$ciphertext = $iv . $ciphertext;
# encode the resulting cipher text so it can be represented by a string
$ciphertext_base64 = base64_encode($ciphertext);
return $ciphertext_base64;
}
# === WARNING ===
# Resulting cipher text has no integrity or authenticity added
# and is not protected against padding oracle attacks.
# --- DECRYPTION ---
$ciphertext_dec = base64_decode($ciphertext_base64);
# retrieves the IV, iv_size should be created using mcrypt_get_iv_size()
$iv_dec = substr($ciphertext_dec, 0, $iv_size);
# retrieves the cipher text (everything except the $iv_size in the front)
$ciphertext_dec = substr($ciphertext_dec, $iv_size);
# may remove 00h valued characters from end of plain text
$plaintext_utf8_dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec);
echo $plaintext_utf8_dec . "\n";
So far i know the best way to save password is with salted hash like used in joomla. You can also add extra keys to md5 hash along with traditional base64.I wrote a script like that sometime ago, tried to find it but can't.
Joomla uses salted md5 passwords. Take the hashed password you gave: 30590cccd0c7fd813ffc724591aea603:WDmIt53GwY2X7TvMqDXaMWJ1mrdZ1sKb
If your password was say 'password', then:
md5('passwordWDmIt53GwY2X7TvMqDXaMWJ1mrdZ1sKb') = 30590cccd0c7fd813ffc724591aea603
So, take your password. Generate a random 32 character string. Compute the md5 of the password concatenated with the random string. Store the md5 result plus a : plus the random 32 character string in the database.
I'm looking for a way to generate license keys with AS3 or PHP.
I need to calculate key with username application, version number and max user number.
For example, here is the code:
<?php
error_reporting(E_ALL);
function KeyGen($userName){
$key = md5($userName);
$new_key = '';
for($i=1; $i <= 25; $i ++ ){
$new_key .= $key[$i];
if ( $i%5==0 && $i != 25) $new_key.='-';
}
return strtoupper($new_key);
}
$userCab="MyUserName-v6.0-12";
$key=KeyGen($userCab);
echo $key;
?>
The generates key about like this: 1AS7-09BD-96A1-CC8D-F106.
But now, how to decrypt this key to extract the two last digit of $userCab.
You're using the md5 algorithm, which is a one-way hashing algorithm. In other words, you cannot decrypt it. Normal instances that use hashing algorithms like this do not attempt to decrypt, instead they compare a given input (the user's key) with an existing hash.
If you need to decrypt the key to get information it contains, you may have to create a custom algorithm, or use a decryptable algorithm such as AES and then base64_encode() the encrypted string to have something user-friendly (but it will be a long string, in most cases). Take a look at the available mcrypt functions for methods that are decryptable to find one that will suit your needs.
If you need a specific key format, as your example shows, a custom algorithm may be your best bet.
You cannot decrypt an MD5 hash. If you are going to "decrypt" it later with a check, you will want to store the values in a MySql table and encrypt the values from that table each time to check to see if it matches the key they put into it.
Don't use AS3 to encrypt anything, as someone can easily decompile your .swf and see your source code.
At the moment I have a database with md5 passwords stored, a few years back this was considered a little more secure than it is now and it's got to the point where the passwords need to be more secure.
I've read a lot of posts on here about crypt, md5, hash, bcrypt, etc and have come to consider using something along the lines of the following to 'secure' the passwords better than they are now.
I will use a combination of hash("sha512" and two salts, the first salt will be a site wide salt stored in a file such as .htaccess and the second salt will be created for each user.
Here's an example along the lines of what I'm testing at the moment:
.htaccess
SetEnv SITEWIDE_SALT NeZa5Edabex?26Y#j5pr7VASpu$8UheVaREj$yA*59t*A$EdRUqer_prazepreTr
example.php
$currentpassword = //get password
$pepper = getenv('SITEWIDE_SALT');
$salt = microtime().ip2long($_SERVER['REMOTE_ADDR']);
$saltpepper = $salt.$pepper;
$password = hash("sha512", md5($currentpassword).$saltpepper);
The salt would obviously need to be stored in a separate table to allow checking of future inserted login passwords but it would never be possible for a user to see. Do you think this is a sufficient way to go about this?
Ok, let's go over a few points here
What you have in $salt is not a salt. It's deterministic (meaning that there is no randomness in there at all). If you want a salt, use either mcrypt_create_iv($size, MCRYPT_DEV_URANDOM) or some other source of actual random entropy. The point is that it should be both unique and random. Note that it doesn't need to be cryptographically secure random... At absolute worst, I'd do something like this:
function getRandomBytes($length) {
$bytes = '';
for ($i = 0; $i < $length; $i++) {
$bytes .= chr(mt_rand(0, 255));
}
return $bytes;
}
As #Anony-Mousse indicated, never feed the output of one hash function into another without re-appending the original data back to it. Instead, use a proper iterative algorithm such as PBKDF2, PHPASS or CRYPT_BLOWFISH ($2a$).
My suggestion would be to use crypt with blowfish, as it's the best available for PHP at this time:
function createBlowfishHash($password) {
$salt = to64(getRandomBytes(16));
$salt = '$2a$10$' . $salt;
$result = crypt($password, $salt);
}
And then verify using a method like this:
function verifyBlowfishHash($password, $hash) {
return $hash == crypt($password, $hash);
}
(note that to64 is a good method defined here). You could also use str_replace('+', '.', base64_encode($salt));...
I'd also suggest you read the following two:
Fundamental difference between hashing and encrypting
Many hash iterations, append salt every time?
Edit: To Answer the Migration Question
Ok, so I realize that my answer did not address the migration aspect of the original question. So here's how I would solve it.
First, build a temporary function to create a new blowfish hash from the original md5 hash, with a random salt and a prefix so that we can detect this later:
function migrateMD5Password($md5Hash) {
$salt = to64(getRandomBytes(16));
$salt = '$2a$10$' . $salt;
$hash = crypt($md5Hash, $salt);
return '$md5' . $hash;
}
Now, run all the existing md5 hashes through this function and save the result in the database. We put our own prefix in so that we can detect the original password and add the additional md5 step. So now we're all migrated.
Next, create another function to verify passwords, and if necessary update the database with a new hash:
function checkAndMigrateHash($password, $hash) {
if (substr($hash, 0, 4) == '$md5') {
// Migrate!
$hash = substr($hash, 4);
if (!verifyBlowfishHash(md5($password), $hash) {
return false;
}
// valid hash, so let's generate a new one
$newHash = createBlowfishHash($password);
saveUpdatedPasswordHash($newHash);
return true;
} else {
return verifyBlowfishHash($password, $hash);
}
}
This is what I would suggest for a few reasons:
It gets the md5() hashes out of your database immediately.
It eventually (next login for each user) updates the hash to a better alternative (one that's well understood).
It's pretty easy to follow in code.
To answer the comments:
A salt doesn't need to be random - I direct you to RFC 2898 - Password Based Cryptography. Namely, Section 4.1. And I quote:
If there is no concern about interactions between multiple uses
of the same key (or a prefix of that key) with the password-
based encryption and authentication techniques supported for a
given password, then the salt may be generated at random and
need not be checked for a particular format by the party
receiving the salt. It should be at least eight octets (64
bits) long.
Additionally,
Note. If a random number generator or pseudorandom generator is not
available, a deterministic alternative for generating the salt (or
the random part of it) is to apply a password-based key derivation
function to the password and the message M to be processed.
A PseudoRandom Generator is available, so why not use it?
Is your solution the same as bcrypt? I can't find much documentation on what bcrypt actually is? - I'll assume that you already read the bcrypt Wikipedia Article, and try to explain it better.
BCrypt is based off the Blowfish block cipher. It takes the key schedule setup algorithm from the cipher, and uses that to hash the passwords. The reason that it is good, is that the setup algorithm for Blowfish is designed to be very expensive (which is part of what makes blowfish so strong of a cypher). The basic process is as follows:
A 18 element array (called P boxes, 32 bits in size) and 4 2-dimensional arrays (called S boxes, each with 256 entries of 8 bits each) are used to setup the schedule by initializing the arrays with predetermined static values. Additionally, a 64 bit state is initialized to all 0's.
The key passed in is XOred with all 18 P boxes in order (rotating the key if it's too short).
The P boxes are then used to encrypt the state that was previously initialized.
The ciphertext produced by step 3 is used to replace P1 and P2 (the first 2 elements of the P array).
Step 3 is repeated, and the result is put in P3 and P4. This continues until P17 and P18 are populated.
That's the key derivation from the Blowfish Cipher. BCrypt modifies that to this:
The 64 bit state is initialized to an encrypted version of the salt.
Same
The P boxes are then used to encrypt the (state xor part of the salt) that was previously initialized.
Same
Same
The resulting setup is then used to encrypt the password 64 times. That's what's returned by BCrypt.
The point is simple: It's a very expensive algorithm that takes a lot of CPU time. That's the real reason that it should be used.
I hope that clears things up.
Implementation of your new, more secure, password storage should use bcrypt or PBKDF2, as that's really the best solution out there right now.
Don't nest things, as you don't get any real security out of this due to collisions as #Anony-Mousse describes.
What you may want to do it implement a "transition routine" where your app transitions users over from the old MD5-based system to the new more secure system as they log in. When a login request comes in, see if the user is in the new, more secure, system. If so, bcrypt/PBKDF2 the password, compare, and you're good to go. If they are not (no one will be at first), check them using the older MD5-based system. If it matches (password is correct), perform the bcrypt/PBKDF2 transformation of the password (since you now have it), store it in the new system, and delete the old MD5 record. Next time they log in, they have an entry in the new system so you're good to go. Once all of the users have logged in once you implement this, you can remove this transition functionality and just authenticate against the new system.
Do not nest md5 inside your sha512 hash. An md5 collision then implies a hash collision in the outer hash, too (because you are hashing the same values!)
The common way of storing passwords is to use a scheme such as
<method><separator><salt><separator><hash>
When validating the password, you read <method> and <salt> from this field, reapply them to the password, and then check that it produces the same <hash>.
Check the crypt functions you have available. On a modern Linux system, crypt should be able to use sha512 password hashing in a sane way: PHP crypt manual. Do not reinvent the wheel, you probably just screw up more badly than md5, unless you are an expert on cryptographic hashing. It will even take care of above scheme: the Linux standard is to use $ as separator, and $6$ is the method ID for sha512, while $2a$ indicates you want to use blowfish. So you can even have multiple hashes in use in your database. md5 hashes are prefixed with $1$<salt>$ (unless you reinvented md5 hashing, then your hashes may be incompatible).
Seriously, reuse the existing crypt function. It is well checked by experts, extensible, and compatible across many applications.
I looked into this subject a while back and found the following link of great use:
Secure hash and salt for PHP passwords
I also use the following to create a random salt:
public static function getRandomString($length = 20) {
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$string = '';
for ($i = 0; $i < $length; $i++) {
$string .= substr($characters, (mt_rand() % strlen($characters)), 1);
}
return $string;
}