0
0
mirror of https://github.com/postfixadmin/postfixadmin.git synced 2024-09-19 19:22:14 +02:00
postfixadmin/model/PFACrypt.php
benchea dan 51673118bb
Update PFACrypt.php
if you have old hashes like $nr$salt$hash and you migrate to {algoithm}hash
2022-02-09 10:39:15 +02:00

372 lines
11 KiB
PHP

<?php
class PFACrypt
{
private $algorithm;
const DOVECOT_NATIVE = [
'SHA1', 'SHA1.HEX', 'SHA1.B64',
'SSHA',
'BLF-CRYPT', 'BLF-CRYPT.B64',
'SHA512-CRYPT', 'SHA512-CRYPT.B64',
'ARGON2I',
'ARGON2I.B64',
'ARGON2ID',
'ARGON2ID.B64',
'SHA256', 'SHA256-CRYPT', 'SHA256-CRYPT.B64',
'SHA512-CRYPT',
'SHA512', 'SHA512.B64',
'MD5-CRYPT',
'PLAIN-MD5',
'CRYPT',
];
public function __construct(string $algorithm)
{
$this->algorithm = $algorithm;
}
/**
* When called with a 'pw' and a 'pw_db' (hash from e.g. database).
*
* If the return value matches $pw_db, then the plain text password ('pw') is correct.
*
* @param string $pw - plain text password
* @param string $pw_db - hash from e.g. database (what we're comparing $pw to).
* @return string if $pw is correct (hashes to $pw_db) then we return $pw_db. Else we return a new hash.
*
* @throws Exception
*/
public function pacrypt(string $pw, string $pw_db = ''): string
{
$algorithm = strtoupper($this->algorithm);
// try and 'upgrade' some dovecot commands to use local algorithms (rather tnan a dependency on the dovecot binary).
if (preg_match('/^DOVECOT:/i', $algorithm)) {
$tmp = preg_replace('/^DOVECOT:/i', '', $algorithm);
if (in_array($tmp, self::DOVECOT_NATIVE)) {
$algorithm = $tmp;
} else {
error_log("Warning: using algorithm that requires proc_open: $algorithm, consider using one of : " . implode(', ', self::DOVECOT_NATIVE));
}
}
if (!empty($pw_db) && preg_match('/^{([0-9a-z-\.]+)}/i', $pw_db, $matches)) {
$method_in_hash = $matches[1];
if ('COURIER:' . strtoupper($method_in_hash) == $algorithm) {
// don't try and be clever.
} elseif ($algorithm != $method_in_hash) {
error_log("Hey, you fed me a password using {$method_in_hash}, but the system is configured to use {$algorithm}");
$algorithm = $method_in_hash;
}
}
if (!empty($pw_db) && preg_match('/^\$[0-9]\$/i', $pw_db, $matches)) {
$method_in_hash = $matches[0];
switch ($method_in_hash){
case '$1$':
case '$6$':
$algorithm='SYSTEM';
}
}
if ($algorithm == 'SHA512CRYPT.B64') {
$algorithm = 'SHA512-CRYPT.B64';
}
switch ($algorithm) {
case 'SHA1':
case 'SHA1.B64':
case 'SHA1.HEX':
return $this->hashSha1($pw, $pw_db, $algorithm);
case 'BLF-CRYPT':
case 'BLF-CRYPT.B64':
return $this->blowfishCrypt($pw, $pw_db, $algorithm);
case 'SHA512-CRYPT':
case 'SHA512-CRYPT.B64':
return $this->sha512Crypt($pw, $pw_db, $algorithm);
case 'ARGON2I':
case 'ARGON2I.B64':
return $this->argon2iCrypt($pw, $pw_db, $algorithm);
case 'ARGON2ID':
case 'ARGON2ID.B64':
return $this->argon2idCrypt($pw, $pw_db, $algorithm);
case 'SSHA':
case 'COURIER:SSHA':
return $this->hashSha1Salted($pw, $pw_db);
case 'SHA256':
case 'COURIER:SHA256':
return $this->hashSha256($pw);
case 'SHA256-CRYPT':
case 'SHA256-CRYPT.B64':
return $this->sha256Crypt($pw, $pw_db, $algorithm);
case 'SHA512':
case 'SHA512B.b64':
case 'SHA512.B64':
return $this->hashSha512($pw, $algorithm);
case 'PLAIN-MD5': // {PLAIN-MD5} prefix
case 'MD5': // no prefix
return $this->hashMd5($pw, $algorithm); // this is hex encoded.
case 'COURIER:MD5':
return '{MD5}' . base64_encode(md5($pw, true)); // seems to need to be base64 encoded.
case 'COURIER:MD5RAW':
return '{MD5RAW}' . md5($pw);
case 'MD5-CRYPT':
return $this->cryptMd5($pw, $pw_db, $algorithm);
case 'CRYPT':
if (!empty($pw_db)) {
$pw_db = preg_replace('/^{CRYPT}/', '', $pw_db);
}
if (empty($pw_db)) {
$pw_db = '$2y$10$' . substr(sha1(random_bytes(8)), 0, 22);
}
return '{CRYPT}' . crypt($pw, $pw_db);
case 'SYSTEM':
return crypt($pw, $pw_db);
case 'CLEAR':
case 'PLAIN':
case 'CLEARTEXT':
if (!empty($pw_db)) {
if ($pw_db == "{{$algorithm}}$pw") {
return $pw_db;
}
return $pw;
}
return '{' . $algorithm . '}' . $pw;
case 'MYSQL_ENCRYPT':
return _pacrypt_mysql_encrypt($pw, $pw_db);
// these are all supported by the above (SHA,
case 'AUTHLIB':
return _pacrypt_authlib($pw, $pw_db);
}
if (preg_match("/^DOVECOT:/", $algorithm)) {
return _pacrypt_dovecot($pw, $pw_db);
}
if (substr($algorithm, 0, 9) === 'PHP_CRYPT') {
return _pacrypt_php_crypt($pw, $pw_db);
}
throw new Exception('unknown/invalid $CONF["encrypt"] setting: ' . $algorithm);
}
public function hashSha1(string $pw, string $pw_db = '', string $algorithm = 'SHA1'): string
{
$hash = hash('sha1', $pw, true);
if (preg_match('/\.HEX$/', $algorithm)) {
$hash = bin2hex($hash);
} else {
$hash = base64_encode($hash);
}
return "{{$algorithm}}{$hash}";
}
public function hashSha1Salted(string $pw, string $pw_db = ''): string
{
if (empty($pw_db)) {
$salt = base64_encode(random_bytes(3)); // 4 char salt.
} else {
$salt = substr(base64_decode(substr($pw_db, 6)), 20);
}
return '{SSHA}' . base64_encode(sha1($pw . $salt, true) . $salt);
}
public function hashSha512(string $pw, string $algorithm = 'SHA512')
{
$prefix = '{SHA512}';
if ($algorithm == 'SHA512.B64' || $algorithm == 'sha512b.b64') {
$prefix = '{SHA512.B64}';
}
return $prefix . base64_encode(hash('sha512', $pw, true));
}
public function hashMd5(string $pw, string $algorithm = 'PLAIN-MD5'): string
{
if ($algorithm == 'PLAIN-MD5') {
return '{PLAIN-MD5}' . md5($pw);
}
return md5($pw);
}
public function hashSha256(string $pw): string
{
return '{SHA256}' . base64_encode(hash('sha256', $pw, true));
}
public function cryptMd5(string $pw, string $pw_db = '', $algorithm = 'MD5-CRYPT')
{
if (!empty($pw_db)) {
$pw_db = preg_replace('/^{MD5.*}/', '', $pw_db);
}
if (empty($pw_db)) {
$pw_db = '$1$' . substr(sha1(random_bytes(8)), 0, 16);
}
return "{{$algorithm}}" . crypt($pw, $pw_db);
}
public function blowfishCrypt(string $pw, string $pw_db = '', string $algorithm = 'BLF-CRYPT'): string
{
if (!empty($pw_db)) {
if ($algorithm == 'BLF-CRYPT') {
$pw_db = preg_replace('/^{BLF-CRYPT}/', '', $pw_db);
}
if ($algorithm == 'BLF-CRYPT.B64') {
$pw_db = base64_decode(preg_replace('/^{BLF-CRYPT.B64}/', '', $pw_db));
}
$hash = crypt($pw, $pw_db);
if ($algorithm == 'BLF-CRYPT.B64') {
$hash = base64_encode($hash);
}
return "{{$algorithm}}{$hash}";
}
$r = password_hash($pw, PASSWORD_BCRYPT);
if (!is_string($r)) {
throw new \RuntimeException("Failed to generate password");
}
if ($algorithm == 'BLF-CRYPT.B64') {
return '{BLF-CRYPT.B64}' . base64_encode($r);
}
return '{BLF-CRYPT}' . $r;
}
public function sha256Crypt(string $pw, string $pw_db = '', string $algorithm = 'SHA256-CRYPT'): string
{
if (!empty($pw_db)) {
$pw_db = preg_replace('/^{SHA256-CRYPT(\.B64)?}/', '', $pw_db);
if ($algorithm == 'SHA256-CRYPT.B64') {
$pw_db = base64_decode($pw_db);
}
}
if (empty($pw_db)) {
$pw_db = '$5$' . substr(sha1(random_bytes(8)), 0, 16);
}
$hash = crypt($pw, $pw_db);
if ($algorithm == 'SHA256-CRYPT.B64') {
return '{SHA256-CRYPT.B64}' . base64_encode($hash);
}
return "{SHA256-CRYPT}" . $hash;
}
public function sha512Crypt(string $pw, string $pw_db = '', $algorithm = 'SHA512-CRYPT'): string
{
if (!empty($pw_db)) {
$pw_db = preg_replace('/^{SHA512-CRYPT(\.B64)?}/', '', $pw_db);
if ($algorithm == 'SHA512-CRYPT.B64') {
$pw_db = base64_decode($pw_db);
}
}
if (empty($pw_db)) {
$pw_db = '$6$' . substr(sha1(random_bytes(8)), 0, 16);
}
$hash = crypt($pw, $pw_db);
if ($algorithm == 'SHA512-CRYPT.B64') {
$hash = base64_encode($hash);
return "{SHA512-CRYPT.B64}{$hash}";
}
return "{SHA512-CRYPT}$hash";
}
public function argon2ICrypt(string $pw, string $pw_db = '', $algorithm = 'ARGON2I'): string
{
if (!empty($pw_db)) {
$pw_db = preg_replace('/^{ARGON2I(\.B64)?}/', '', $pw_db);
$orig_pwdb = $pw_db;
if ($algorithm == 'ARGON2I.B64') {
$pw_db = base64_decode($pw_db);
}
if (password_verify($pw, $pw_db)) {
return "{{$algorithm}}" . $orig_pwdb;
}
$hash = password_hash($pw, PASSWORD_ARGON2I);
if ($algorithm == 'ARGON2I') {
return '{ARGON2I}' . $hash;
}
return '{ARGON2I.B64}' . base64_encode($hash);
;
}
$hash = password_hash($pw, PASSWORD_ARGON2I);
if ($algorithm == 'ARGON2I') {
return '{ARGON2I}' . $hash;
}
return "{ARGON2I.B64}" . base64_encode($hash);
}
public function argon2idCrypt(string $pw, string $pw_db = '', string $algorithm = 'ARGON2ID'): string
{
if (!defined('PASSWORD_ARGON2ID')) {
throw new \Exception("Requires PHP 7.3+");
}
if (!empty($pw_db)) {
$pw_db = preg_replace('/^{ARGON2ID(\.B64)?}/', '', $pw_db);
$orig_pwdb = $pw_db;
if ($algorithm == 'ARGON2ID.B64') {
$pw_db = base64_decode($pw_db);
}
if (password_verify($pw, $pw_db)) {
return "{{$algorithm}}" . $orig_pwdb;
}
$hash = password_hash($pw, PASSWORD_ARGON2ID);
if ($algorithm == 'ARGON2ID') {
return '{ARGON2ID}' . $hash;
}
// if($algorithm == 'ARGON2ID.B64') {
return '{ARGON2ID.B64}' . base64_encode($hash);
}
$hash = password_hash($pw, PASSWORD_ARGON2ID);
if ($algorithm == 'ARGON2ID') {
return '{ARGON2ID}' . $hash;
}
return '{ARGON2ID.B64}' . base64_encode($hash);
}
}