mirror of
https://github.com/postfixadmin/postfixadmin.git
synced 2024-09-19 19:22:14 +02:00
add SSHA format, add some specific courier algorithms, try and partially update docs
This commit is contained in:
parent
858c0a0ecd
commit
543285a203
@ -6,6 +6,9 @@ They should not be stored in plain text.
|
|||||||
|
|
||||||
Whatever format you choose will need to be supported by your IMAP server (and whatever provides SASL auth for Postfix)
|
Whatever format you choose will need to be supported by your IMAP server (and whatever provides SASL auth for Postfix)
|
||||||
|
|
||||||
|
If you can, use a format that includes a different salt per password (e.g. one of the crypt variants, like Blowfish (BLF-CRYPT) or Argon2I/Argon2ID).
|
||||||
|
Try and avoid formats that are unsalted hashes (md5, SHA1) as these offer minimal protection in the event of a data leak.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
See config.inc.php (or config.local.php) and look for
|
See config.inc.php (or config.local.php) and look for
|
||||||
@ -20,6 +23,49 @@ This document is probably not complete.
|
|||||||
|
|
||||||
It possibly provides better documentation than was present before. This may not say much.
|
It possibly provides better documentation than was present before. This may not say much.
|
||||||
|
|
||||||
|
Supported hash formats include :
|
||||||
|
|
||||||
|
* MD5-CRYPT (aka MD5),
|
||||||
|
* SHA1,
|
||||||
|
* SHA1-CRYPT,
|
||||||
|
* SSHA (4 char salted sha1),
|
||||||
|
* BLF-CRYPT (Blowfish),
|
||||||
|
* SHA512,
|
||||||
|
* SHA512-CRYPT,
|
||||||
|
* ARGON2I,
|
||||||
|
* ARGON2ID,
|
||||||
|
* SHA256,
|
||||||
|
* SHA256-CRYPT,
|
||||||
|
* PLAIN-MD5 (aka md5)
|
||||||
|
* CRYPT
|
||||||
|
|
||||||
|
Historically PostfixAdmin has supported all dovecot algorithms (methods) by using the 'doveadm' system binary. As of XXXXXXX, we attempt to use a native/PHP implementation for a number of these to remove issues caused by use of proc_open / dovecot file permissions etc (see #379).
|
||||||
|
|
||||||
|
It's recommended you use the algorithm/mechanism from your MTA, and configure PostfixAdmin with the same value prefixed by the MTA name -
|
||||||
|
|
||||||
|
For example, if dovecot has `default_pass_scheme = SHA256` use `$CONF['encrypt'] = 'dovecot:SHA256'; ` in PostfixAdmin.
|
||||||
|
|
||||||
|
|
||||||
|
| Dovecot pass scheme | PostfixAdmin `$CONF['encrypt']` setting |
|
||||||
|
|------|------|
|
||||||
|
| SHA256 | dovecot:SHA256 |
|
||||||
|
| SHA256-CRYPT.B64 | dovecot:SHA256-CRYPT.B64 |
|
||||||
|
| SHA256-CRYPT | dovecot:SHA256-CRYPT |
|
||||||
|
| SHA512-CRYPT | dovecot:SHA512-CRYPT |
|
||||||
|
| ARGON2I | dovecot:ARGON2I |
|
||||||
|
| ARGON2ID | dovecot:ARGON2ID |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Courier Example | PostfixAdmin |
|
||||||
|
|-------|-------|
|
||||||
|
| md5 | courier:md5 |
|
||||||
|
| md5raw | courier:md5raw |
|
||||||
|
| sha1 | courier:sha1 |
|
||||||
|
| ssha | courier:ssha |
|
||||||
|
| sha256 | courier:sha256 |
|
||||||
|
|
||||||
|
|
||||||
### cleartext
|
### cleartext
|
||||||
|
|
||||||
No hashing. May be useful for debugging.
|
No hashing. May be useful for debugging.
|
||||||
@ -58,9 +104,9 @@ You should not use this (it does not offer a high level of security), but is pro
|
|||||||
|
|
||||||
Uses PHP's crypt function.
|
Uses PHP's crypt function.
|
||||||
|
|
||||||
Probably throws an E_NOTICE. Avoid?
|
Probably throws an E_NOTICE.
|
||||||
|
|
||||||
example : `$1$tWgqTIuF$1HFciCXrhVpACGjBMxNr/0`
|
Example : `$1$tWgqTIuF$1HFciCXrhVpACGjBMxNr/0`
|
||||||
|
|
||||||
### authlib
|
### authlib
|
||||||
|
|
||||||
@ -86,16 +132,18 @@ Presumably weak.
|
|||||||
|
|
||||||
Uses sha1, base64 encoded. Unsalted. Avoid.
|
Uses sha1, base64 encoded. Unsalted. Avoid.
|
||||||
|
|
||||||
### dovecot:CRYPT-METHOD
|
### dovecot:METHOD
|
||||||
|
|
||||||
Uses dovecot binary to produce hash.
|
May use dovecot binary to produce hash, if the format you request isn't in PFACrypt::DOVECOT_NATIVE
|
||||||
|
|
||||||
Pros -
|
Using a format that PostfixAdmin doesn't support natively has the following pros/cons :
|
||||||
|
|
||||||
|
#### Pros
|
||||||
|
|
||||||
* Minimal dependency on PostfixAdmin / PHP code.
|
* Minimal dependency on PostfixAdmin / PHP code.
|
||||||
* Hash should definitely work with dovecot!
|
* Hash should definitely work with dovecot!
|
||||||
|
|
||||||
Cons -
|
#### Cons
|
||||||
|
|
||||||
* file permissions and/or execution of doveadm by the web server may be problematic.
|
* file permissions and/or execution of doveadm by the web server may be problematic.
|
||||||
* requires: proc_open(...) - which might be blocked by e.g. safemode.
|
* requires: proc_open(...) - which might be blocked by e.g. safemode.
|
||||||
|
@ -4,6 +4,22 @@ class PFACrypt
|
|||||||
{
|
{
|
||||||
private $algorithm;
|
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', 'SHA512.B64',
|
||||||
|
'MD5',
|
||||||
|
'PLAIN-MD5',
|
||||||
|
'CRYPT',
|
||||||
|
];
|
||||||
|
|
||||||
public function __construct(string $algorithm)
|
public function __construct(string $algorithm)
|
||||||
{
|
{
|
||||||
$this->algorithm = $algorithm;
|
$this->algorithm = $algorithm;
|
||||||
@ -16,26 +32,10 @@ class PFACrypt
|
|||||||
// try and 'upgrade' some dovecot commands to use local algorithms (rather tnan a dependency on the dovecot binary).
|
// try and 'upgrade' some dovecot commands to use local algorithms (rather tnan a dependency on the dovecot binary).
|
||||||
if (preg_match('/^dovecot:/', $algorithm)) {
|
if (preg_match('/^dovecot:/', $algorithm)) {
|
||||||
$tmp = preg_replace('/^dovecot:/', '', $algorithm);
|
$tmp = preg_replace('/^dovecot:/', '', $algorithm);
|
||||||
|
if (in_array($tmp, self::DOVECOT_NATIVE)) {
|
||||||
$supported = [
|
|
||||||
'SHA1', 'SHA1,HEX', 'SHA1.B64',
|
|
||||||
'BLF-CRYPT', 'BLF-CRYPT.B64',
|
|
||||||
'SHA512-CRYPT', 'SHA512-CRYPT.B64',
|
|
||||||
'ARGON2I',
|
|
||||||
'ARGON2I.B64',
|
|
||||||
'ARGON2ID',
|
|
||||||
'ARGON2ID.B64',
|
|
||||||
'SHA256', 'SHA256-CRYPT', 'SHA256-CRYPT.B64',
|
|
||||||
'SHA512', 'SHA512.B64',
|
|
||||||
'MD5',
|
|
||||||
'PLAIN-MD5',
|
|
||||||
'CRYPT',
|
|
||||||
];
|
|
||||||
|
|
||||||
if (in_array($tmp, $supported)) {
|
|
||||||
$algorithm = $tmp;
|
$algorithm = $tmp;
|
||||||
} else {
|
} else {
|
||||||
error_log("Warning: using alogrithm that requires proc_open: $algorithm, consider using one of : " . implode(', ', $supported));
|
error_log("Warning: using algorithm that requires proc_open: $algorithm, consider using one of : " . implode(', ', self::DOVECOT_NATIVE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,8 +62,13 @@ class PFACrypt
|
|||||||
case 'ARGON2ID.B64':
|
case 'ARGON2ID.B64':
|
||||||
return $this->argon2idCrypt($pw, $pw_db, $algorithm);
|
return $this->argon2idCrypt($pw, $pw_db, $algorithm);
|
||||||
|
|
||||||
|
case 'SSHA':
|
||||||
|
case 'courier:ssha':
|
||||||
|
return $this->hashSha1Salted($pw, $pw_db);
|
||||||
|
|
||||||
case 'SHA256':
|
case 'SHA256':
|
||||||
return '{SHA256}' . base64_encode(hash('sha256', $pw, true));
|
case 'courier:sha256':
|
||||||
|
return $this->hashSha256($pw);
|
||||||
|
|
||||||
case 'SHA256-CRYPT':
|
case 'SHA256-CRYPT':
|
||||||
case 'SHA256-CRYPT.B64':
|
case 'SHA256-CRYPT.B64':
|
||||||
@ -79,6 +84,12 @@ class PFACrypt
|
|||||||
case 'PLAIN-MD5':
|
case 'PLAIN-MD5':
|
||||||
return $this->hashMd5($pw, $algorithm);
|
return $this->hashMd5($pw, $algorithm);
|
||||||
|
|
||||||
|
case 'courier:md5':
|
||||||
|
return '{MD5}' . base64_encode(md5($pw, true));
|
||||||
|
|
||||||
|
case 'courier:md5raw':
|
||||||
|
return '{MD5RAW}' . bin2hex(md5($pw, true));
|
||||||
|
|
||||||
case 'MD5':
|
case 'MD5':
|
||||||
case 'MD5-CRYPT':
|
case 'MD5-CRYPT':
|
||||||
return $this->cryptMd5($pw, $pw_db, $algorithm);
|
return $this->cryptMd5($pw, $pw_db, $algorithm);
|
||||||
@ -118,6 +129,7 @@ class PFACrypt
|
|||||||
case 'sha512crypt.b64':
|
case 'sha512crypt.b64':
|
||||||
return $this->pacrypt_sha512crypt_b64($pw, $pw_db);
|
return $this->pacrypt_sha512crypt_b64($pw, $pw_db);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match("/^dovecot:/", $algorithm)) {
|
if (preg_match("/^dovecot:/", $algorithm)) {
|
||||||
@ -144,6 +156,16 @@ class PFACrypt
|
|||||||
return "{{$algorithm}}{$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')
|
public function hashSha512(string $pw, string $algorithm = 'SHA512')
|
||||||
{
|
{
|
||||||
$prefix = '{SHA512}';
|
$prefix = '{SHA512}';
|
||||||
@ -160,10 +182,14 @@ class PFACrypt
|
|||||||
if ($algorithm == 'PLAIN-MD5') {
|
if ($algorithm == 'PLAIN-MD5') {
|
||||||
return '{PLAIN-MD5}' . md5($pw);
|
return '{PLAIN-MD5}' . md5($pw);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 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')
|
public function cryptMd5(string $pw, string $pw_db = '', $algorithm = 'MD5-CRYPT')
|
||||||
{
|
{
|
||||||
if (!empty($pw_db)) {
|
if (!empty($pw_db)) {
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/../model/PFACrypt.php');
|
||||||
|
|
||||||
class PaCryptTest extends \PHPUnit\Framework\TestCase
|
class PaCryptTest extends \PHPUnit\Framework\TestCase
|
||||||
{
|
{
|
||||||
public function testMd5Crypt()
|
public function testMd5Crypt()
|
||||||
{
|
{
|
||||||
$hash = _pacrypt_md5crypt('test', '');
|
$hash = _pacrypt_md5crypt('test', '');
|
||||||
|
|
||||||
|
$h = new PFACrypt('MD5-CRYPT');
|
||||||
|
|
||||||
$this->assertNotEmpty($hash);
|
$this->assertNotEmpty($hash);
|
||||||
$this->assertNotEquals('test', $hash);
|
$this->assertNotEquals('test', $hash);
|
||||||
|
|
||||||
@ -204,6 +208,7 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase
|
|||||||
"CRYPT": "{CRYPT}$2y$05$ORqzr0AagWr25v3ixHD5QuMXympIoNTbipEFZz6aAmovGNoij2vDO",
|
"CRYPT": "{CRYPT}$2y$05$ORqzr0AagWr25v3ixHD5QuMXympIoNTbipEFZz6aAmovGNoij2vDO",
|
||||||
"MD5-CRYPT": "{MD5-CRYPT}$1$AIjpWveQ$2s3eEAbZiqkJhMYUIVR240",
|
"MD5-CRYPT": "{MD5-CRYPT}$1$AIjpWveQ$2s3eEAbZiqkJhMYUIVR240",
|
||||||
"PLAIN-MD5": "{PLAIN-MD5}cc03e747a6afbbcbf8be7668acfebee5",
|
"PLAIN-MD5": "{PLAIN-MD5}cc03e747a6afbbcbf8be7668acfebee5",
|
||||||
|
"SSHA": "{SSHA}ZkqrSEAhvd0FTHaK1IxAQCRa5LWbxGQY",
|
||||||
"PLAIN": "{PLAIN}test123",
|
"PLAIN": "{PLAIN}test123",
|
||||||
"CLEAR": "{CLEAR}test123",
|
"CLEAR": "{CLEAR}test123",
|
||||||
"CLEARTEXT": "{CLEARTEXT}test123",
|
"CLEARTEXT": "{CLEARTEXT}test123",
|
||||||
@ -235,4 +240,44 @@ EOF;
|
|||||||
$this->assertEquals($pfa_new_hash, $new_new, "Trying: $algorithm => gave: $new_new with $pfa_new_hash ... ");
|
$this->assertEquals($pfa_new_hash, $new_new, "Trying: $algorithm => gave: $new_new with $pfa_new_hash ... ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSomeCourierHashes()
|
||||||
|
{
|
||||||
|
global $CONF;
|
||||||
|
|
||||||
|
$options = [
|
||||||
|
'courier:md5' => '{MD5}zAPnR6avu8v4vnZorP6+5Q==',
|
||||||
|
'courier:md5raw' => '{MD5RAW}cc03e747a6afbbcbf8be7668acfebee5',
|
||||||
|
'courier:ssha' => '{SSHA}pJTac1QSIHoi0qBPdqnBvgPdjfFtDRVY',
|
||||||
|
'courier:sha256' => '{SHA256}7NcYcNGWMxapfjrDQIyYNa2M8PPBvHA1J8MCZVNPda4=',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($options as $algorithm => $example_hash) {
|
||||||
|
$CONF['encrypt'] = $algorithm;
|
||||||
|
|
||||||
|
$pacrypt_check = pacrypt('test123', $example_hash);
|
||||||
|
$pacrypt_sanity = pacrypt('zzzzz', $example_hash);
|
||||||
|
$pfa_new_hash = pacrypt('test123');
|
||||||
|
|
||||||
|
$this->assertNotEquals($pacrypt_sanity, $pfa_new_hash);
|
||||||
|
$this->assertNotEquals($pacrypt_sanity, $example_hash);
|
||||||
|
|
||||||
|
$this->assertEquals($pacrypt_check, $example_hash, "Should match, algorithm: $algorithm generated:{$pacrypt_check} vs example:{$example_hash}");
|
||||||
|
|
||||||
|
$new = pacrypt('test123', $pfa_new_hash);
|
||||||
|
|
||||||
|
$this->assertEquals($pfa_new_hash, $new, "Trying: $algorithm => gave: $new with $pfa_new_hash");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWeSupportWhatWeSayWeDo()
|
||||||
|
{
|
||||||
|
foreach (PFACrypt::DOVECOT_NATIVE as $algorithm) {
|
||||||
|
$c = new PFACrypt($algorithm);
|
||||||
|
$hash1 = $c->hash('test123');
|
||||||
|
|
||||||
|
$this->assertEquals($hash1, $c->hash('test123', $hash1));
|
||||||
|
$this->assertNotEquals($hash1, $c->hash('9999test9999', $hash1));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user