Created
June 7, 2013 17:02
-
-
Save markwu/5730741 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Created by: Mr McManDude | |
* Date: 2/22/13 | |
*/ | |
/** | |
* Class GCrypt | |
*/ | |
class GCrypt | |
{ | |
/** | |
* @var string Algorithm used to hash the key. | |
*/ | |
private static $keyHashAlgorithm = 'SHA256'; | |
/** | |
* @var string Algorithm used to generate the Checksum. | |
*/ | |
private static $checksumHashAlgorithm = 'MD5'; | |
/** | |
* @var string The prefix to the salt. | |
*/ | |
private static $saltPrefix = 'GB'; | |
/** | |
* @var string The key to encrypt with. | |
*/ | |
private $key; | |
/** | |
* @var string The algorithm used. | |
*/ | |
private $algorithm; | |
/** | |
* @var string The mode used. | |
*/ | |
private $mode; | |
/** | |
* @var int Size of the IV based on the algorithm. | |
*/ | |
private $ivSize; | |
/** | |
* @var int Size of the IV once it has been base64 encoded. | |
*/ | |
private $base64_ivSize; | |
/** | |
* @var int Size of the salt. | |
*/ | |
private $saltSize; | |
/** | |
* @var int Size of the salt once it has been base64 encoded. | |
*/ | |
private $base64_saltSize; | |
/** | |
* @var int Size of the checksum. | |
*/ | |
private $checksumSize; | |
/** | |
* @var int Size of the checksum once it has been base64 encoded. | |
*/ | |
private $base64_checksumSize; | |
/** | |
* @param string $key | |
* @param string $algorithm | |
* @param string $mode | |
*/ | |
public function __construct($key, $algorithm = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC) | |
{ | |
$tempSalt = $this->generateSalt(); //Used for size calculations. | |
$tempChecksum = $this->getChecksum(' '); //Used for size calculations. | |
$this->key = $key; | |
$this->algorithm = $algorithm; | |
$this->mode = $mode; | |
//This is a dumb way of getting lengths, but i can't figure out how else to reliably calculate it... | |
$this->saltSize = strlen($tempSalt); | |
$this->base64_saltSize = strlen(base64_encode($tempSalt)); | |
$this->checksumSize = strlen($tempChecksum); | |
$this->base64_checksumSize = strlen(base64_encode($tempChecksum)); | |
$this->ivSize = mcrypt_get_iv_size($this->algorithm, $this->mode); | |
$this->base64_ivSize = strlen(base64_encode($this->generateIV())); | |
} | |
/** | |
* Securely encrypts the data, all that needs to be saved is the returned string. | |
* | |
* @param string $data | |
* | |
* @return string | |
* @throws Exception 1: No data was provided. | |
*/ | |
public function encrypt($data) | |
{ | |
if(!isset($data)) | |
throw new Exception("There is no data set!", 1); | |
$data = trim($data); //Trim all whitespaces off it. | |
$salt = $this->generateSalt(); | |
$key = $this->getKey($salt); | |
$iv = $this->generateIV(); | |
$checksum = $this->getChecksum($data); | |
$decrypted = $data . $checksum; | |
$encrypted = mcrypt_encrypt($this->algorithm, $key, $decrypted, $this->mode, $iv); | |
$iv_base64 = rtrim(base64_encode($iv)); | |
$encrypted_base64 = base64_encode($encrypted); | |
$salt_base64 = rtrim(base64_encode($salt)); | |
return $iv_base64 . $encrypted_base64 . $salt_base64; | |
} | |
/** | |
* Securely decrypts the data, will throw an exception if the data does not pass the corruption check. | |
* | |
* @param string $data | |
* | |
* @return string | |
* @throws Exception 1: No data was provided. 2: Data was corrupt (hash did not match output) | |
*/ | |
public function decrypt($data) | |
{ | |
if(!isset($data)) | |
throw new Exception("There is no data set!", 1); | |
$data = trim($data); //Trim all whitespaces off it. | |
list($iv, $encrypted, $salt) = $this->splitEncrypted($data); | |
$key = $this->getKey($salt); | |
$data = rtrim(@mcrypt_decrypt($this->algorithm, $key, $encrypted, $this->mode, $iv), "\0\4"); | |
list($decrypted, $hash) = $this->splitHash($data); | |
$newHash = $this->getChecksum($decrypted); | |
if($newHash != $hash) | |
throw new Exception('Data is corrupt!', 2); | |
return $decrypted; | |
} | |
/** | |
* Returns the key used in encryption/decryption by combining the $salt and $this->key with $keyHashAlgorithm | |
* | |
* @param string $salt | |
* | |
* @return string | |
*/ | |
private function getKey($salt) | |
{ | |
return hash(self::$keyHashAlgorithm, $this->key . $salt, TRUE); | |
} | |
/** | |
* Generates an IV used in encryption/decryption. | |
* | |
* @return string | |
*/ | |
private function generateIV() | |
{ | |
return mcrypt_create_iv($this->ivSize, MCRYPT_DEV_URANDOM); | |
} | |
/** | |
* Generates the salt for encryption/decryption. | |
* | |
* @return string | |
*/ | |
private function generateSalt() | |
{ | |
return uniqid(self::$saltPrefix, TRUE); | |
} | |
/** | |
* Gets the checksum created with $checksumHashAlgorithm when provided with $data. | |
* | |
* @param string $data | |
* | |
* @return string | |
*/ | |
private function getChecksum($data) | |
{ | |
return hash(self::$checksumHashAlgorithm, $data, TRUE); | |
} | |
/** | |
* Splits the encrypted string ($data) into $IV $Encrypted and $Salt. | |
* | |
* @param string $data | |
* | |
* @return string[] | |
*/ | |
private function splitEncrypted($data) | |
{ | |
$iv_base64 = substr($data, 0, $this->base64_ivSize); | |
$encrypted_base64 = substr($data, $this->base64_ivSize, -($this->base64_saltSize)); | |
$salt_base64 = substr($data, -($this->base64_saltSize)); | |
$iv = base64_decode($iv_base64); | |
$encrypted = base64_decode($encrypted_base64); | |
$salt = base64_decode($salt_base64); | |
return array($iv, $encrypted, $salt); | |
} | |
/** | |
* Splits the decrypted data into $decrypted and $hash | |
* | |
* @param string $data | |
* | |
* @return string[] | |
*/ | |
private function splitHash($data) | |
{ | |
$decrypted = substr($data, 0, -($this->checksumSize)); | |
$hash = substr($data, -($this->checksumSize)); | |
return array($decrypted, $hash); | |
} | |
} | |
/** | |
* Class GHash | |
*/ | |
class GHash | |
{ | |
/** | |
* @var int The random data source location | |
*/ | |
private static $randomConstant = MCRYPT_DEV_RANDOM; | |
/** | |
* @var int The difficulty integer used in hashing | |
*/ | |
/** | |
* @var string The encryption algorithm | |
*/ | |
private $difficulty, $algorithm; | |
/** | |
* @var string Format of the hash | |
*/ | |
/** | |
* @var int Length of the salt itself | |
*/ | |
/** | |
* @var int Maximum length of the resulting salt (all things included) | |
*/ | |
private $hashFormat, $saltLength, $saltMaxLength; | |
/** | |
* | |
* @param int $difficulty The difficulty level (differs for each algorithm) | |
* @param string $algorithm The CRYPT constant that represents the algorithm you want to use (in string form) | |
* | |
* @throws Exception 1: Crypt is not available. 2: Algorithm is not supported. 3: Invalid Difficulty. 4: Invalid Algorithm. | |
*/ | |
public function __construct($difficulty = 10, $algorithm = 'CRYPT_BLOWFISH') | |
{ | |
$this->difficulty = $difficulty; | |
$this->algorithm = $algorithm; | |
if(!function_exists('crypt')){ | |
throw new Exception('Crypt must be loaded for ' . __CLASS__ . '.', 1); | |
} elseif(constant($this->algorithm) != 1){ | |
throw new Exception('Algorithm (' . $this->algorithm . ') Not Supported!', 2); | |
} | |
switch($this->algorithm){ | |
case 'CRYPT_STD_DES': | |
$this->hashFormat = ''; | |
$this->saltLength = 2; | |
$this->saltMaxLength = 2; | |
break; | |
case 'CRYPT_EXT_DES': | |
if(($this->difficulty < 1) || ($this->difficulty > 16777215)) | |
throw new Exception('Invalid Difficulty! Acceptable range is between 1 and 16,777,215', 3); | |
$encodedIterations = $this->base64_int_encode($this->difficulty); | |
$this->hashFormat = '_' . $encodedIterations; | |
$this->saltLength = 4; | |
$this->saltMaxLength = 9; | |
break; | |
case 'CRYPT_MD5': | |
$this->hashFormat = '$1$'; | |
$this->saltLength = 10; | |
$this->saltMaxLength = 12; | |
break; | |
case 'CRYPT_BLOWFISH': | |
if(($this->difficulty < 4) || ($this->difficulty > 31)) | |
throw new Exception('Invalid Difficulty! Acceptable range is between 4 and 31.', 3); | |
$this->hashFormat = sprintf("$2y$%02d$", $this->difficulty); | |
$this->saltLength = 22; | |
$this->saltMaxLength = 30; | |
break; | |
case 'CRYPT_SHA256': | |
case 'CRYPT_SHA512': | |
if(($this->difficulty < 1000) || ($this->difficulty > 999999999)) | |
throw new Exception('Invalid Difficulty! Acceptable range is between 1000 and 999,999,999.', 3); | |
$hashNumber = ($this->algorithm == 'CRYPT_SHA256' ? 5 : 6); | |
$this->hashFormat = '$' . $hashNumber . '$rounds=' . (string)$this->difficulty . '$'; | |
$this->saltLength = 16; | |
$this->saltMaxLength = 16 + strlen($this->hashFormat); | |
break; | |
default: | |
throw new Exception('Invalid Algorithm!', 4); | |
} | |
} | |
/** | |
* Provide the password, and save the string, nothing else is needed. | |
* | |
* @param string $password The password you want to safely hash. | |
* | |
* @return string The full string you should store of the hashed password. | |
* @throws Exception 1: $password was blank. | |
*/ | |
public function hash($password) | |
{ | |
if(!isset($password)) | |
throw new Exception('$password must be set!', 1); | |
$rawSalt = mcrypt_create_iv($this->saltLength, self::$randomConstant); | |
$longSalt = $this->hashFormat . str_replace('+', '.', base64_encode($rawSalt)) . '$'; | |
$salt = substr($longSalt, 0, $this->saltMaxLength); | |
return crypt($password, $salt); | |
} | |
/** | |
* Run this to verify if a give password matches a given hash. | |
* TODO: add a rehash-if-needed bool and stuff... | |
* | |
* @param string $password The password you want to check against. | |
* @param string $hash The hash you want to check against. | |
* | |
* @return bool TRUE if it matches, FALSE otherwise. | |
* @throws Exception 1: $password empty. 2: $hash empty. | |
*/ | |
public function verify($password, $hash) | |
{ | |
if(!isset($password)){ | |
throw new Exception('$password must be set!', 1); | |
} elseif(!isset($hash)){ | |
throw new Exception('$hash must be set!', 2); | |
} | |
$check = crypt($password, $hash); | |
if($check == $hash) | |
$verified = TRUE; | |
else | |
$verified = FALSE; | |
return $verified; | |
} | |
/** | |
* Run this function to get a TRUE/FALSE on if the password is hashed with the correct algorithm and level of difficulty. | |
* | |
* @param string $hash The hash to check | |
* | |
* @return bool TRUE if it needs rehash, FALSE otherwise. | |
*/ | |
public function needsRehash($hash) | |
{ | |
$hashAlgorithm = self::getAlgorithm($hash); | |
$hashDifficulty = self::getDifficulty($hash); | |
if($hashAlgorithm != $this->algorithm){ | |
$rehash = TRUE; | |
} elseif(($hashDifficulty != FALSE) && ($hashDifficulty < $this->difficulty)){ | |
$rehash = TRUE; | |
} else{ | |
$rehash = FALSE; | |
} | |
return $rehash; | |
} | |
/** | |
* Returns the algorithm used to hash the password (as a string of the constant) | |
* | |
* @param string $hash The hash to check | |
* | |
* @return string The string of the constant of the algorithm used. | |
*/ | |
public static function getAlgorithm($hash) | |
{ | |
$id = self::getIdString($hash); | |
switch($id){ | |
case FALSE; | |
$algorithm = 'CRYPT_STD_DES'; | |
break; | |
case '_'; | |
$algorithm = 'CRYPT_EXT_DES'; | |
break; | |
case '$1$': | |
$algorithm = 'CRYPT_MD5'; | |
break; | |
case '$2a$': | |
case '$2x$': | |
case '$2y$': | |
$algorithm = 'CRYPT_BLOWFISH'; | |
break; | |
case '$5$': | |
$algorithm = 'CRYPT_SHA256'; | |
break; | |
case '$6$': | |
$algorithm = 'CRYPT_SHA512'; | |
} | |
return $algorithm; | |
} | |
/** | |
* Retrieve the salt from the hash. | |
* | |
* @param string $hash The hash to retrieve the salt from. | |
* | |
* @return string The salt | |
*/ | |
public static function getSalt($hash) | |
{ | |
$algorithm = self::getAlgorithm($hash); | |
switch($algorithm){ | |
case 'CRYPT_STD_DES': | |
$strLength = 2; | |
break; | |
case 'CRYPT_EXT_DES': | |
$strLength = 9; | |
break; | |
case 'CRYPT_MD5': | |
//$1$rasmusle$ | |
$strLength = 12; | |
break; | |
case 'CRYPT_BLOWFISH': | |
//$2a$07$usesomesillystringforsalt$ | |
$strLength = 30; | |
break; | |
case 'CRYPT_SHA256': | |
case 'CRYPT_SHA512': | |
//$5$rounds=5000$usesomesillystringforsalt$ | |
$strLength = strrpos($hash, '$'); | |
} | |
$hash = substr($hash, 0, $strLength); | |
return $hash; | |
} | |
/** | |
* Returns the integer of the difficulty used to hash the password (returns false if no difficulty can be used in that algorithm) | |
* | |
* @param string $hash The hash to get the difficulty from. | |
* | |
* @return bool|int The difficulty (FALSE if no difficulty can be used in that algorithm) | |
*/ | |
public static function getDifficulty($hash) | |
{ | |
$algorithm = self::getAlgorithm($hash); | |
switch($algorithm){ | |
case 'CRYPT_STD_DES': | |
case 'CRYPT_EXT_DES': | |
case 'CRYPT_MD5': | |
$difficulty = FALSE; | |
break; | |
case 'CRYPT_BLOWFISH': | |
$salt = self::getSalt($hash); | |
$difficulty = (int)substr($salt, 4, 2); | |
break; | |
case 'CRYPT_SHA256': | |
case 'CRYPT_SHA512': | |
$salt = self::getSalt($hash); | |
$roundsPos = strpos($salt, 'rounds='); | |
$SPos = strpos($salt, '$', $roundsPos); | |
$difficulty = (int)substr($salt, ($roundsPos + 7), $SPos); | |
} | |
return $difficulty; | |
} | |
/** | |
* Returns the ID string of the salt of the hash of the password. EX: "$2y$" for CRYPT_BLOWFISH | |
* | |
* @param string $hash The hash to get the IdString from | |
* | |
* @return bool|string False if there is none (CRYPT_STD_DES) or the IdString otherwise. | |
*/ | |
public static function getIdString($hash) | |
{ | |
$firstPos = strpos($hash, '$'); | |
if($firstPos !== FALSE){ | |
$secondPos = strpos($hash, '$', $firstPos + 1) + 1; | |
$idLength = $secondPos - $firstPos; | |
$id = substr($hash, $firstPos, $idLength); | |
} elseif(substr($hash, 0, 1) == '_'){ | |
$id = '_'; | |
} else{ | |
$id = FALSE; | |
} | |
return $id; | |
} | |
/** | |
* Base64 encode an integer for use in the CRYPT_EXT_DES iterations. Takes an integer and returns a 4 character string. | |
* THIS DOES NO VALIDATION/CHECKING OF THE INT OR RETURNED STRING! | |
* | |
* @param int $num The number to convert | |
* | |
* @return string The base64'd string (4 characters) | |
*/ | |
private function base64_int_encode($num) | |
{ | |
$alphabet_raw = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; | |
$alphabet = str_split($alphabet_raw); | |
$arr = array(); | |
$base = sizeof($alphabet); | |
while($num){ | |
$rem = $num % $base; | |
$num = (int)($num / $base); | |
$arr[] = $alphabet[$rem]; | |
} | |
$arr = array_reverse($arr); | |
$string = implode($arr); | |
return str_pad($string, 4, '.', STR_PAD_LEFT); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment