From 5bb7e76ec73cba609b1d3b919dbe963dcc956c0c Mon Sep 17 00:00:00 2001
From: Christian Futterlieb <christian@futterlieb.ch>
Date: Mon, 11 Dec 2017 08:55:32 +0100
Subject: [PATCH] [!!!][TASK] Implement Salted Passwords against SaltInterface

Salt classes must implement SaltInterface only. The AbstractSalt
class is renamed to AbstractComposedSalt and implements
SaltInterface. Methods for salt-classes that compose the
password-hash string themselves (which are currently all in
saltedpasswords) are moved to AbstractComposedSalt as well.

This cleanup change prepares for the integration of the
PHP password API in a following change.

Relates: #79795
Relates: #79889
Resolves: #83294
Releases: master
Change-Id: Ife24aa39be99c5ad391b0f10497a2bceb04084f3
Reviewed-on: https://review.typo3.org/52737
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
---
 composer.json                                 |  1 +
 composer.lock                                 |  2 +-
 ...sCustomSaltingsMustUseTheSaltInterface.rst | 39 ++++++++++++++
 .../Classes/Evaluation/Evaluator.php          | 10 ++--
 ...tractSalt.php => AbstractComposedSalt.php} | 32 +++++++++---
 .../Classes/Salt/BlowfishSalt.php             | 37 +++++++-------
 .../saltedpasswords/Classes/Salt/Md5Salt.php  | 29 ++++++-----
 .../Classes/Salt/Pbkdf2Salt.php               | 51 ++++++++++---------
 .../Classes/Salt/PhpassSalt.php               | 49 +++++++++---------
 .../Classes/Salt/SaltFactory.php              | 38 +++++++-------
 .../Classes/Salt/SaltInterface.php            | 26 +++-------
 .../Classes/SaltedPasswordService.php         |  2 +-
 .../Documentation/DevelopersGuide/Index.rst   |  6 ++-
 .../Migrations/Code/ClassAliasMap.php         |  4 ++
 .../Tests/Unit/Salt/SaltFactoryTest.php       | 27 ++++------
 typo3/sysext/saltedpasswords/composer.json    |  5 ++
 16 files changed, 208 insertions(+), 150 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/9.0/Breaking-83294-SaltedPasswordsCustomSaltingsMustUseTheSaltInterface.rst
 rename typo3/sysext/saltedpasswords/Classes/Salt/{AbstractSalt.php => AbstractComposedSalt.php} (73%)
 create mode 100644 typo3/sysext/saltedpasswords/Migrations/Code/ClassAliasMap.php

diff --git a/composer.json b/composer.json
index 6692903609e9..c760a33a458d 100644
--- a/composer.json
+++ b/composer.json
@@ -84,6 +84,7 @@
 				"typo3/sysext/info/Migrations/Code/ClassAliasMap.php",
 				"typo3/sysext/lowlevel/Migrations/Code/ClassAliasMap.php",
 				"typo3/sysext/reports/Migrations/Code/ClassAliasMap.php",
+				"typo3/sysext/saltedpasswords/Migrations/Code/ClassAliasMap.php",
 				"typo3/sysext/workspaces/Migrations/Code/ClassAliasMap.php"
 			]
 		},
diff --git a/composer.lock b/composer.lock
index 5c96b5518d69..bf4b2b582b02 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "6b0c9ff22949275bc4d6c60fd2f62f2b",
+    "content-hash": "ee8fd70e59c3f86ff7728b866e54149c",
     "packages": [
         {
             "name": "cogpowered/finediff",
diff --git a/typo3/sysext/core/Documentation/Changelog/9.0/Breaking-83294-SaltedPasswordsCustomSaltingsMustUseTheSaltInterface.rst b/typo3/sysext/core/Documentation/Changelog/9.0/Breaking-83294-SaltedPasswordsCustomSaltingsMustUseTheSaltInterface.rst
new file mode 100644
index 000000000000..ff49c77396fe
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/9.0/Breaking-83294-SaltedPasswordsCustomSaltingsMustUseTheSaltInterface.rst
@@ -0,0 +1,39 @@
+.. include:: ../../Includes.txt
+
+===============================================================================
+Breaking: #83294 - Salted Passwords: Custom saltings must use the SaltInterface
+===============================================================================
+
+See :issue:`83294`
+
+Description
+===========
+
+The salted passwords factory allowed to register custom saltings has been changed. All custom salts
+need to implement :php:`TYPO3\CMS\SaltedPasswords\Salt\SaltInterface`. Before, this was
+handled by extending from :php:`TYPO3\CMS\SaltedPasswords\Salt\AbstractSalt`, which has been renamed to
+:php:`TYPO3\CMS\SaltedPasswords\Salt\AbstractComposedSalt` when the salting is implemented.
+
+
+Impact
+======
+
+When writing custom salts for TYPO3, they need to implement the SaltInterface.
+
+If extending from :php:`AbstractSalt`, custom salt now need to extend from :php:`AbstractComposedSalt` and
+implement the additional method :php:`getSaltLength()` and :php:`isValidSalt($salt)`.
+
+
+Affected Installations
+======================
+
+TYPO3 installations using custom salts for `EXT:saltedpasswords`.
+
+
+Migration
+=========
+
+Switch to the new implemention details mentioned above, and change your custom salt to fit
+to the :php:`SaltInterface` API.
+
+.. index:: PHP-API, NotScanned
\ No newline at end of file
diff --git a/typo3/sysext/saltedpasswords/Classes/Evaluation/Evaluator.php b/typo3/sysext/saltedpasswords/Classes/Evaluation/Evaluator.php
index 4d33b28fdcd9..58ecf7faa7dc 100644
--- a/typo3/sysext/saltedpasswords/Classes/Evaluation/Evaluator.php
+++ b/typo3/sysext/saltedpasswords/Classes/Evaluation/Evaluator.php
@@ -14,6 +14,9 @@ namespace TYPO3\CMS\Saltedpasswords\Evaluation;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Saltedpasswords\Salt\SaltFactory;
+use TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility;
+
 /**
  * Class implementing salted evaluation methods.
  */
@@ -49,20 +52,19 @@ class Evaluator
      */
     public function evaluateFieldValue($value, $is_in, &$set)
     {
-        $isEnabled = $this->mode ? \TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility::isUsageEnabled($this->mode) : \TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility::isUsageEnabled();
+        $isEnabled = $this->mode ? SaltedPasswordsUtility::isUsageEnabled($this->mode) : SaltedPasswordsUtility::isUsageEnabled();
         if ($isEnabled) {
             $isMD5 = preg_match('/[0-9abcdef]{32,32}/', $value);
             $hashingMethod = substr($value, 0, 2);
             $isDeprecatedSaltedHash = ($hashingMethod === 'C$' || $hashingMethod === 'M$');
-            /** @var $objInstanceSaltedPW \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface */
-            $objInstanceSaltedPW = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(null, $this->mode);
+            $objInstanceSaltedPW = SaltFactory::getSaltingInstance(null, $this->mode);
             if ($isMD5) {
                 $set = true;
                 $value = 'M' . $objInstanceSaltedPW->getHashedPassword($value);
             } else {
                 // Determine method used for the (possibly) salted hashed password
                 $tempValue = $isDeprecatedSaltedHash ? substr($value, 1) : $value;
-                $tempObjInstanceSaltedPW = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance($tempValue);
+                $tempObjInstanceSaltedPW = SaltFactory::getSaltingInstance($tempValue);
                 if (!is_object($tempObjInstanceSaltedPW)) {
                     $set = true;
                     $value = $objInstanceSaltedPW->getHashedPassword($value);
diff --git a/typo3/sysext/saltedpasswords/Classes/Salt/AbstractSalt.php b/typo3/sysext/saltedpasswords/Classes/Salt/AbstractComposedSalt.php
similarity index 73%
rename from typo3/sysext/saltedpasswords/Classes/Salt/AbstractSalt.php
rename to typo3/sysext/saltedpasswords/Classes/Salt/AbstractComposedSalt.php
index 069df622a7d6..3d1829f78c82 100644
--- a/typo3/sysext/saltedpasswords/Classes/Salt/AbstractSalt.php
+++ b/typo3/sysext/saltedpasswords/Classes/Salt/AbstractComposedSalt.php
@@ -1,4 +1,5 @@
 <?php
+declare(strict_types=1);
 namespace TYPO3\CMS\Saltedpasswords\Salt;
 
 /*
@@ -16,9 +17,9 @@ namespace TYPO3\CMS\Saltedpasswords\Salt;
 
 /**
  * Abstract class with methods needed to be extended
- * in a salted hashing class.
+ * in a salted hashing class that composes an own salted password hash.
  */
-abstract class AbstractSalt
+abstract class AbstractComposedSalt implements SaltInterface
 {
     /**
      * Method applies settings (prefix, optional hash count, optional suffix)
@@ -27,28 +28,43 @@ abstract class AbstractSalt
      * @param string $salt A salt to apply setting to
      * @return string Salt with setting
      */
-    abstract protected function applySettingsToSalt($salt);
+    abstract protected function applySettingsToSalt(string $salt): string;
 
     /**
      * Generates a random base salt settings for the hash.
      *
      * @return string A string containing settings and a random salt
      */
-    abstract protected function getGeneratedSalt();
+    abstract protected function getGeneratedSalt(): string;
 
     /**
      * Returns a string for mapping an int to the corresponding base 64 character.
      *
      * @return string String for mapping an int to the corresponding base 64 character
      */
-    abstract protected function getItoa64();
+    abstract protected function getItoa64(): string;
 
     /**
      * Returns setting string to indicate type of hashing method.
      *
      * @return string Setting string of hashing method
      */
-    abstract protected function getSetting();
+    abstract protected function getSetting(): string;
+
+    /**
+     * Returns length of required salt.
+     *
+     * @return int Length of required salt
+     */
+    abstract public function getSaltLength(): int;
+
+    /**
+     * Method determines if a given string is a valid salt
+     *
+     * @param string $salt String to check
+     * @return bool TRUE if it's valid salt, otherwise FALSE
+     */
+    abstract public function isValidSalt(string $salt): bool;
 
     /**
      * Encodes bytes into printable base 64 using the *nix standard from crypt().
@@ -57,7 +73,7 @@ abstract class AbstractSalt
      * @param int $count The number of characters (bytes) to encode.
      * @return string Encoded string
      */
-    public function base64Encode($input, $count)
+    public function base64Encode(string $input, int $count): string
     {
         $output = '';
         $i = 0;
@@ -91,7 +107,7 @@ abstract class AbstractSalt
      * @param int $byteLength Length of bytes to calculate in base64 chars
      * @return int Required length of base64 characters
      */
-    protected function getLengthBase64FromBytes($byteLength)
+    protected function getLengthBase64FromBytes(int $byteLength): int
     {
         // Calculates bytes in bits in base64
         return (int)ceil($byteLength * 8 / 6);
diff --git a/typo3/sysext/saltedpasswords/Classes/Salt/BlowfishSalt.php b/typo3/sysext/saltedpasswords/Classes/Salt/BlowfishSalt.php
index 18cdbb23605f..58b0cd625903 100644
--- a/typo3/sysext/saltedpasswords/Classes/Salt/BlowfishSalt.php
+++ b/typo3/sysext/saltedpasswords/Classes/Salt/BlowfishSalt.php
@@ -1,4 +1,5 @@
 <?php
+declare(strict_types=1);
 namespace TYPO3\CMS\Saltedpasswords\Salt;
 
 /*
@@ -87,7 +88,7 @@ class BlowfishSalt extends Md5Salt
      * @param string $salt A salt to apply setting to
      * @return string Salt with setting
      */
-    protected function applySettingsToSalt($salt)
+    protected function applySettingsToSalt(string $salt): string
     {
         $saltWithSettings = $salt;
         $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
@@ -104,7 +105,7 @@ class BlowfishSalt extends Md5Salt
      * @param string $setting Complete hash or a hash's setting string or to get log2 iteration count from
      * @return int Used hashcount for given hash string
      */
-    protected function getCountLog2($setting)
+    protected function getCountLog2(string $setting): int
     {
         $countLog2 = null;
         $setting = substr($setting, strlen($this->getSetting()));
@@ -124,7 +125,7 @@ class BlowfishSalt extends Md5Salt
      * @see $hashCount
      * @see setHashCount()
      */
-    public function getHashCount()
+    public function getHashCount(): int
     {
         return isset(self::$hashCount) ? self::$hashCount : self::HASH_COUNT;
     }
@@ -137,7 +138,7 @@ class BlowfishSalt extends Md5Salt
      * @see $maxHashCount
      * @see setMaxHashCount()
      */
-    public function getMaxHashCount()
+    public function getMaxHashCount(): int
     {
         return isset(self::$maxHashCount) ? self::$maxHashCount : self::MAX_HASH_COUNT;
     }
@@ -147,9 +148,9 @@ class BlowfishSalt extends Md5Salt
      *
      * @return bool Method available
      */
-    public function isAvailable()
+    public function isAvailable(): bool
     {
-        return CRYPT_BLOWFISH;
+        return (bool)CRYPT_BLOWFISH;
     }
 
     /**
@@ -160,7 +161,7 @@ class BlowfishSalt extends Md5Salt
      * @see $minHashCount
      * @see setMinHashCount()
      */
-    public function getMinHashCount()
+    public function getMinHashCount(): int
     {
         return isset(self::$minHashCount) ? self::$minHashCount : self::MIN_HASH_COUNT;
     }
@@ -173,7 +174,7 @@ class BlowfishSalt extends Md5Salt
      *
      * @return int Length of a Blowfish salt in bytes
      */
-    public function getSaltLength()
+    public function getSaltLength(): int
     {
         return self::$saltLengthBlowfish;
     }
@@ -186,7 +187,7 @@ class BlowfishSalt extends Md5Salt
      *
      * @return string Setting string of Blowfish salted hashes
      */
-    public function getSetting()
+    public function getSetting(): string
     {
         return self::$settingBlowfish;
     }
@@ -202,7 +203,7 @@ class BlowfishSalt extends Md5Salt
      * @param string $saltedPW Salted hash to check if it needs an update
      * @return bool TRUE if salted hash needs an update, otherwise FALSE
      */
-    public function isHashUpdateNeeded($saltedPW)
+    public function isHashUpdateNeeded(string $saltedPW): bool
     {
         // Check whether this was an updated password.
         if (strncmp($saltedPW, '$2', 2) || !$this->isValidSalt($saltedPW)) {
@@ -222,7 +223,7 @@ class BlowfishSalt extends Md5Salt
      * @param string $salt String to check
      * @return bool TRUE if it's valid salt, otherwise FALSE
      */
-    public function isValidSalt($salt)
+    public function isValidSalt(string $salt): bool
     {
         $isValid = ($skip = false);
         $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
@@ -252,7 +253,7 @@ class BlowfishSalt extends Md5Salt
      * @param string $saltedPW String to check
      * @return bool TRUE if it's valid salted hashed password, otherwise FALSE
      */
-    public function isValidSaltedPW($saltedPW)
+    public function isValidSaltedPW(string $saltedPW): bool
     {
         $isValid = !strncmp($this->getSetting(), $saltedPW, strlen($this->getSetting()));
         if ($isValid) {
@@ -269,9 +270,9 @@ class BlowfishSalt extends Md5Salt
      * @see $hashCount
      * @see getHashCount()
      */
-    public function setHashCount($hashCount = null)
+    public function setHashCount(int $hashCount = null)
     {
-        self::$hashCount = !is_null($hashCount) && is_int($hashCount) && $hashCount >= $this->getMinHashCount() && $hashCount <= $this->getMaxHashCount() ? $hashCount : self::HASH_COUNT;
+        self::$hashCount = !is_null($hashCount) && $hashCount >= $this->getMinHashCount() && $hashCount <= $this->getMaxHashCount() ? $hashCount : self::HASH_COUNT;
     }
 
     /**
@@ -282,9 +283,9 @@ class BlowfishSalt extends Md5Salt
      * @see $maxHashCount
      * @see getMaxHashCount()
      */
-    public function setMaxHashCount($maxHashCount = null)
+    public function setMaxHashCount(int $maxHashCount = null)
     {
-        self::$maxHashCount = !is_null($maxHashCount) && is_int($maxHashCount) ? $maxHashCount : self::MAX_HASH_COUNT;
+        self::$maxHashCount = $maxHashCount ?? self::MAX_HASH_COUNT;
     }
 
     /**
@@ -295,8 +296,8 @@ class BlowfishSalt extends Md5Salt
      * @see $minHashCount
      * @see getMinHashCount()
      */
-    public function setMinHashCount($minHashCount = null)
+    public function setMinHashCount(int $minHashCount = null)
     {
-        self::$minHashCount = !is_null($minHashCount) && is_int($minHashCount) ? $minHashCount : self::MIN_HASH_COUNT;
+        self::$minHashCount = $minHashCount ?? self::MIN_HASH_COUNT;
     }
 }
diff --git a/typo3/sysext/saltedpasswords/Classes/Salt/Md5Salt.php b/typo3/sysext/saltedpasswords/Classes/Salt/Md5Salt.php
index 0543b4380942..fb5576cab569 100644
--- a/typo3/sysext/saltedpasswords/Classes/Salt/Md5Salt.php
+++ b/typo3/sysext/saltedpasswords/Classes/Salt/Md5Salt.php
@@ -1,4 +1,5 @@
 <?php
+declare(strict_types=1);
 namespace TYPO3\CMS\Saltedpasswords\Salt;
 
 /*
@@ -24,7 +25,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  * MD5 salted hashing with PHP's crypt() should be available
  * on most of the systems.
  */
-class Md5Salt extends AbstractSalt implements SaltInterface
+class Md5Salt extends AbstractComposedSalt
 {
     /**
      * Keeps a string for mapping an int to the corresponding
@@ -59,7 +60,7 @@ class Md5Salt extends AbstractSalt implements SaltInterface
      * @param string $salt A salt to apply setting to
      * @return string Salt with setting
      */
-    protected function applySettingsToSalt($salt)
+    protected function applySettingsToSalt(string $salt): string
     {
         $saltWithSettings = $salt;
         $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
@@ -78,7 +79,7 @@ class Md5Salt extends AbstractSalt implements SaltInterface
      * @param string $saltedHashPW salted hash to compare plain-text password with
      * @return bool TRUE, if plain-text password matches the salted hash, otherwise FALSE
      */
-    public function checkPassword($plainPW, $saltedHashPW)
+    public function checkPassword(string $plainPW, string $saltedHashPW): bool
     {
         $isCorrect = false;
         if ($this->isValidSalt($saltedHashPW)) {
@@ -98,7 +99,7 @@ class Md5Salt extends AbstractSalt implements SaltInterface
      *
      * @return string A character string containing settings and a random salt
      */
-    protected function getGeneratedSalt()
+    protected function getGeneratedSalt(): string
     {
         $randomBytes = GeneralUtility::makeInstance(Random::class)->generateRandomBytes($this->getSaltLength());
         return $this->base64Encode($randomBytes, $this->getSaltLength());
@@ -111,7 +112,7 @@ class Md5Salt extends AbstractSalt implements SaltInterface
      * @param string $salt Optional custom salt with setting to use
      * @return string Salted hashed password
      */
-    public function getHashedPassword($password, $salt = null)
+    public function getHashedPassword(string $password, string $salt = null)
     {
         $saltedPW = null;
         if (!empty($password)) {
@@ -128,7 +129,7 @@ class Md5Salt extends AbstractSalt implements SaltInterface
      *
      * @return string String for mapping an int to the corresponding base 64 character
      */
-    protected function getItoa64()
+    protected function getItoa64(): string
     {
         return self::ITOA64;
     }
@@ -138,9 +139,9 @@ class Md5Salt extends AbstractSalt implements SaltInterface
      *
      * @return bool Method available
      */
-    public function isAvailable()
+    public function isAvailable(): bool
     {
-        return CRYPT_MD5;
+        return (bool)CRYPT_MD5;
     }
 
     /**
@@ -148,7 +149,7 @@ class Md5Salt extends AbstractSalt implements SaltInterface
      *
      * @return int Length of a MD5 salt in bytes
      */
-    public function getSaltLength()
+    public function getSaltLength(): int
     {
         return self::$saltLengthMD5;
     }
@@ -158,7 +159,7 @@ class Md5Salt extends AbstractSalt implements SaltInterface
      *
      * @return string Suffix of a salt
      */
-    protected function getSaltSuffix()
+    protected function getSaltSuffix(): string
     {
         return self::$saltSuffixMD5;
     }
@@ -168,7 +169,7 @@ class Md5Salt extends AbstractSalt implements SaltInterface
      *
      * @return string Setting string of MD5 salted hashes
      */
-    public function getSetting()
+    public function getSetting(): string
     {
         return self::$settingMD5;
     }
@@ -185,7 +186,7 @@ class Md5Salt extends AbstractSalt implements SaltInterface
      * @param string $passString Salted hash to check if it needs an update
      * @return bool TRUE if salted hash needs an update, otherwise FALSE
      */
-    public function isHashUpdateNeeded($passString)
+    public function isHashUpdateNeeded(string $passString): bool
     {
         return false;
     }
@@ -196,7 +197,7 @@ class Md5Salt extends AbstractSalt implements SaltInterface
      * @param string $salt String to check
      * @return bool TRUE if it's valid salt, otherwise FALSE
      */
-    public function isValidSalt($salt)
+    public function isValidSalt(string $salt): bool
     {
         $isValid = ($skip = false);
         $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
@@ -226,7 +227,7 @@ class Md5Salt extends AbstractSalt implements SaltInterface
      * @param string $saltedPW String to check
      * @return bool TRUE if it's valid salted hashed password, otherwise FALSE
      */
-    public function isValidSaltedPW($saltedPW)
+    public function isValidSaltedPW(string $saltedPW): bool
     {
         $isValid = !strncmp($this->getSetting(), $saltedPW, strlen($this->getSetting()));
         if ($isValid) {
diff --git a/typo3/sysext/saltedpasswords/Classes/Salt/Pbkdf2Salt.php b/typo3/sysext/saltedpasswords/Classes/Salt/Pbkdf2Salt.php
index 55784725ab4d..ab80faadb342 100644
--- a/typo3/sysext/saltedpasswords/Classes/Salt/Pbkdf2Salt.php
+++ b/typo3/sysext/saltedpasswords/Classes/Salt/Pbkdf2Salt.php
@@ -1,4 +1,5 @@
 <?php
+declare(strict_types=1);
 namespace TYPO3\CMS\Saltedpasswords\Salt;
 
 /*
@@ -21,7 +22,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  * Class that implements PBKDF2 salted hashing based on PHP's
  * hash_pbkdf2() function.
  */
-class Pbkdf2Salt extends AbstractSalt implements SaltInterface
+class Pbkdf2Salt extends AbstractComposedSalt
 {
     /**
      * Keeps a string for mapping an int to the corresponding
@@ -88,7 +89,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @param string $salt A salt to apply setting to
      * @return string Salt with setting
      */
-    protected function applySettingsToSalt($salt)
+    protected function applySettingsToSalt(string $salt): string
     {
         $saltWithSettings = $salt;
         // salt without setting
@@ -106,7 +107,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @param string $saltedHashPW salted hash to compare plain-text password with
      * @return bool TRUE, if plain-text password matches the salted hash, otherwise FALSE
      */
-    public function checkPassword($plainPW, $saltedHashPW)
+    public function checkPassword(string $plainPW, string $saltedHashPW): bool
     {
         return $this->isValidSalt($saltedHashPW) && \hash_equals($this->getHashedPassword($plainPW, $saltedHashPW), $saltedHashPW);
     }
@@ -117,7 +118,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @param string $setting Complete hash or a hash's setting string or to get log2 iteration count from
      * @return int|null Used hashcount for given hash string
      */
-    protected function getIterationCount($setting)
+    protected function getIterationCount(string $setting)
     {
         $iterationCount = null;
         $setting = substr($setting, strlen($this->getSetting()));
@@ -143,7 +144,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      *
      * @return string A character string containing settings and a random salt
      */
-    protected function getGeneratedSalt()
+    protected function getGeneratedSalt(): string
     {
         return GeneralUtility::makeInstance(Random::class)->generateRandomBytes($this->getSaltLength());
     }
@@ -155,7 +156,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @param string $salt
      * @return string
      */
-    protected function getStoredSalt($salt)
+    protected function getStoredSalt(string $salt): string
     {
         if (!strncmp('$', $salt, 1)) {
             if (!strncmp($this->getSetting(), $salt, strlen($this->getSetting()))) {
@@ -171,7 +172,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      *
      * @return string String for mapping an int to the corresponding base 64 character
      */
-    protected function getItoa64()
+    protected function getItoa64(): string
     {
         return self::ITOA64;
     }
@@ -183,7 +184,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @param string $salt Optional custom salt with setting to use
      * @return string|null Salted hashed password
      */
-    public function getHashedPassword($password, $salt = null)
+    public function getHashedPassword(string $password, string $salt = null)
     {
         $saltedPW = null;
         if ($password !== '') {
@@ -207,7 +208,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @see $hashCount
      * @see setHashCount()
      */
-    public function getHashCount()
+    public function getHashCount(): int
     {
         return isset(self::$hashCount) ? self::$hashCount : self::HASH_COUNT;
     }
@@ -220,7 +221,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @see $maxHashCount
      * @see setMaxHashCount()
      */
-    public function getMaxHashCount()
+    public function getMaxHashCount(): int
     {
         return isset(self::$maxHashCount) ? self::$maxHashCount : self::MAX_HASH_COUNT;
     }
@@ -230,7 +231,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      *
      * @return bool Method available
      */
-    public function isAvailable()
+    public function isAvailable(): bool
     {
         return function_exists('hash_pbkdf2');
     }
@@ -243,7 +244,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @see $minHashCount
      * @see setMinHashCount()
      */
-    public function getMinHashCount()
+    public function getMinHashCount(): int
     {
         return isset(self::$minHashCount) ? self::$minHashCount : self::MIN_HASH_COUNT;
     }
@@ -256,7 +257,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      *
      * @return int Length of a PBKDF2 salt in bytes
      */
-    public function getSaltLength()
+    public function getSaltLength(): int
     {
         return self::$saltLengthPbkdf2;
     }
@@ -269,7 +270,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      *
      * @return string Setting string of PBKDF2 salted hashes
      */
-    public function getSetting()
+    public function getSetting(): string
     {
         return self::$settingPbkdf2;
     }
@@ -285,7 +286,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @param string $saltedPW Salted hash to check if it needs an update
      * @return bool TRUE if salted hash needs an update, otherwise FALSE
      */
-    public function isHashUpdateNeeded($saltedPW)
+    public function isHashUpdateNeeded(string $saltedPW): bool
     {
         // Check whether this was an updated password.
         if (strncmp($saltedPW, $this->getSetting(), strlen($this->getSetting())) || !$this->isValidSalt($saltedPW)) {
@@ -305,7 +306,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @param string $salt String to check
      * @return bool TRUE if it's valid salt, otherwise FALSE
      */
-    public function isValidSalt($salt)
+    public function isValidSalt(string $salt): bool
     {
         $isValid = ($skip = false);
         $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
@@ -335,7 +336,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @param string $saltedPW String to check
      * @return bool TRUE if it's valid salted hashed password, otherwise FALSE
      */
-    public function isValidSaltedPW($saltedPW)
+    public function isValidSaltedPW(string $saltedPW): bool
     {
         $isValid = !strncmp($this->getSetting(), $saltedPW, strlen($this->getSetting()));
         if ($isValid) {
@@ -352,9 +353,9 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @see $hashCount
      * @see getHashCount()
      */
-    public function setHashCount($hashCount = null)
+    public function setHashCount(int $hashCount = null)
     {
-        self::$hashCount = !is_null($hashCount) && is_int($hashCount) && $hashCount >= $this->getMinHashCount() && $hashCount <= $this->getMaxHashCount() ? $hashCount : self::HASH_COUNT;
+        self::$hashCount = !is_null($hashCount) && $hashCount >= $this->getMinHashCount() && $hashCount <= $this->getMaxHashCount() ? $hashCount : self::HASH_COUNT;
     }
 
     /**
@@ -365,9 +366,9 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @see $maxHashCount
      * @see getMaxHashCount()
      */
-    public function setMaxHashCount($maxHashCount = null)
+    public function setMaxHashCount(int $maxHashCount = null)
     {
-        self::$maxHashCount = !is_null($maxHashCount) && is_int($maxHashCount) ? $maxHashCount : self::MAX_HASH_COUNT;
+        self::$maxHashCount = $maxHashCount ?? self::MAX_HASH_COUNT;
     }
 
     /**
@@ -378,9 +379,9 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @see $minHashCount
      * @see getMinHashCount()
      */
-    public function setMinHashCount($minHashCount = null)
+    public function setMinHashCount(int $minHashCount = null)
     {
-        self::$minHashCount = !is_null($minHashCount) && is_int($minHashCount) ? $minHashCount : self::MIN_HASH_COUNT;
+        self::$minHashCount = $minHashCount ?? self::MIN_HASH_COUNT;
     }
 
     /**
@@ -391,7 +392,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @param int $count The number of characters (bytes) to encode.
      * @return string Encoded string
      */
-    public function base64Encode($input, $count)
+    public function base64Encode(string $input, int $count): string
     {
         $input = substr($input, 0, $count);
         return rtrim(str_replace('+', '.', base64_encode($input)), " =\r\n\t\0\x0B");
@@ -404,7 +405,7 @@ class Pbkdf2Salt extends AbstractSalt implements SaltInterface
      * @param string $value
      * @return string
      */
-    public function base64Decode($value)
+    public function base64Decode(string $value): string
     {
         return base64_decode(str_replace('.', '+', $value));
     }
diff --git a/typo3/sysext/saltedpasswords/Classes/Salt/PhpassSalt.php b/typo3/sysext/saltedpasswords/Classes/Salt/PhpassSalt.php
index 89d93cb16e2a..9dcf0846cb18 100644
--- a/typo3/sysext/saltedpasswords/Classes/Salt/PhpassSalt.php
+++ b/typo3/sysext/saltedpasswords/Classes/Salt/PhpassSalt.php
@@ -1,4 +1,5 @@
 <?php
+declare(strict_types=1);
 namespace TYPO3\CMS\Saltedpasswords\Salt;
 
 /*
@@ -28,7 +29,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  * @see http://drupal.org/node/29706/
  * @see http://www.openwall.com/phpass/
  */
-class PhpassSalt extends AbstractSalt implements SaltInterface
+class PhpassSalt extends AbstractComposedSalt
 {
     /**
      * Keeps a string for mapping an int to the corresponding
@@ -100,7 +101,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      * @param string $salt A salt to apply setting to
      * @return string Salt with setting
      */
-    protected function applySettingsToSalt($salt)
+    protected function applySettingsToSalt(string $salt): string
     {
         $saltWithSettings = $salt;
         $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
@@ -122,7 +123,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      * @param string $saltedHashPW Salted hash to compare plain-text password with
      * @return bool TRUE, if plain-text password matches the salted hash, otherwise FALSE
      */
-    public function checkPassword($plainPW, $saltedHashPW)
+    public function checkPassword(string $plainPW, string $saltedHashPW): bool
     {
         $hash = $this->cryptPassword($plainPW, $saltedHashPW);
         return $hash && \hash_equals($hash, $saltedHashPW);
@@ -133,7 +134,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      *
      * @return bool Method available
      */
-    public function isAvailable()
+    public function isAvailable(): bool
     {
         return true;
     }
@@ -150,7 +151,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      * @param string $setting An existing hash or the output of getGeneratedSalt()
      * @return mixed A string containing the hashed password (and salt)
      */
-    protected function cryptPassword($password, $setting)
+    protected function cryptPassword(string $password, string $setting)
     {
         $saltedPW = null;
         $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
@@ -183,7 +184,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      * @param string $setting Complete hash or a hash's setting string or to get log2 iteration count from
      * @return int Used hashcount for given hash string
      */
-    protected function getCountLog2($setting)
+    protected function getCountLog2(string $setting): int
     {
         return strpos($this->getItoa64(), $setting[strlen($this->getSetting())]);
     }
@@ -199,7 +200,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      *
      * @return string A character string containing settings and a random salt
      */
-    protected function getGeneratedSalt()
+    protected function getGeneratedSalt(): string
     {
         $randomBytes = GeneralUtility::makeInstance(Random::class)->generateRandomBytes($this->getSaltLength());
         return $this->base64Encode($randomBytes, $this->getSaltLength());
@@ -213,7 +214,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      * @see $hashCount
      * @see setHashCount()
      */
-    public function getHashCount()
+    public function getHashCount(): int
     {
         return isset(self::$hashCount) ? self::$hashCount : self::HASH_COUNT;
     }
@@ -223,9 +224,9 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      *
      * @param string $password Plaintext password to create a salted hash from
      * @param string $salt Optional custom salt with setting to use
-     * @return string salted hashed password
+     * @return string|null salted hashed password
      */
-    public function getHashedPassword($password, $salt = null)
+    public function getHashedPassword(string $password, string $salt = null)
     {
         $saltedPW = null;
         if (!empty($password)) {
@@ -242,7 +243,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      *
      * @return string String for mapping an int to the corresponding base 64 character
      */
-    protected function getItoa64()
+    protected function getItoa64(): string
     {
         return self::ITOA64;
     }
@@ -255,7 +256,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      * @see $maxHashCount
      * @see setMaxHashCount()
      */
-    public function getMaxHashCount()
+    public function getMaxHashCount(): int
     {
         return isset(self::$maxHashCount) ? self::$maxHashCount : self::MAX_HASH_COUNT;
     }
@@ -268,7 +269,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      * @see $minHashCount
      * @see setMinHashCount()
      */
-    public function getMinHashCount()
+    public function getMinHashCount(): int
     {
         return isset(self::$minHashCount) ? self::$minHashCount : self::MIN_HASH_COUNT;
     }
@@ -278,7 +279,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      *
      * @return int Length of a Blowfish salt in bytes
      */
-    public function getSaltLength()
+    public function getSaltLength(): int
     {
         return self::$saltLengthPhpass;
     }
@@ -288,7 +289,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      *
      * @return string Setting string of PHPass salted hashes
      */
-    public function getSetting()
+    public function getSetting(): string
     {
         return self::$settingPhpass;
     }
@@ -305,7 +306,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      * @param string $passString Salted hash to check if it needs an update
      * @return bool TRUE if salted hash needs an update, otherwise FALSE
      */
-    public function isHashUpdateNeeded($passString)
+    public function isHashUpdateNeeded(string $passString): bool
     {
         // Check whether this was an updated password.
         if (strncmp($passString, '$P$', 3) || strlen($passString) != 34) {
@@ -321,7 +322,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      * @param string $salt String to check
      * @return bool TRUE if it's valid salt, otherwise FALSE
      */
-    public function isValidSalt($salt)
+    public function isValidSalt(string $salt): bool
     {
         $isValid = ($skip = false);
         $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
@@ -351,7 +352,7 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      * @param string $saltedPW String to check
      * @return bool TRUE if it's valid salted hashed password, otherwise FALSE
      */
-    public function isValidSaltedPW($saltedPW)
+    public function isValidSaltedPW(string $saltedPW): bool
     {
         $isValid = !strncmp($this->getSetting(), $saltedPW, strlen($this->getSetting()));
         if ($isValid) {
@@ -368,9 +369,9 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      * @see $hashCount
      * @see getHashCount()
      */
-    public function setHashCount($hashCount = null)
+    public function setHashCount(int $hashCount = null)
     {
-        self::$hashCount = !is_null($hashCount) && is_int($hashCount) && $hashCount >= $this->getMinHashCount() && $hashCount <= $this->getMaxHashCount() ? $hashCount : self::HASH_COUNT;
+        self::$hashCount = !is_null($hashCount) && $hashCount >= $this->getMinHashCount() && $hashCount <= $this->getMaxHashCount() ? $hashCount : self::HASH_COUNT;
     }
 
     /**
@@ -381,9 +382,9 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      * @see $maxHashCount
      * @see getMaxHashCount()
      */
-    public function setMaxHashCount($maxHashCount = null)
+    public function setMaxHashCount(int $maxHashCount = null)
     {
-        self::$maxHashCount = !is_null($maxHashCount) && is_int($maxHashCount) ? $maxHashCount : self::MAX_HASH_COUNT;
+        self::$maxHashCount = $maxHashCount ?? self::MAX_HASH_COUNT;
     }
 
     /**
@@ -394,8 +395,8 @@ class PhpassSalt extends AbstractSalt implements SaltInterface
      * @see $minHashCount
      * @see getMinHashCount()
      */
-    public function setMinHashCount($minHashCount = null)
+    public function setMinHashCount(int $minHashCount = null)
     {
-        self::$minHashCount = !is_null($minHashCount) && is_int($minHashCount) ? $minHashCount : self::MIN_HASH_COUNT;
+        self::$minHashCount = $minHashCount ?? self::MIN_HASH_COUNT;
     }
 }
diff --git a/typo3/sysext/saltedpasswords/Classes/Salt/SaltFactory.php b/typo3/sysext/saltedpasswords/Classes/Salt/SaltFactory.php
index c8888a33651f..359bb2ee271f 100644
--- a/typo3/sysext/saltedpasswords/Classes/Salt/SaltFactory.php
+++ b/typo3/sysext/saltedpasswords/Classes/Salt/SaltFactory.php
@@ -1,4 +1,5 @@
 <?php
+declare(strict_types=1);
 namespace TYPO3\CMS\Saltedpasswords\Salt;
 
 /*
@@ -14,6 +15,9 @@ namespace TYPO3\CMS\Saltedpasswords\Salt;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility;
+
 /**
  * Class that implements Blowfish salted hashing based on PHP's
  * crypt() function.
@@ -24,7 +28,7 @@ class SaltFactory
      * An instance of the salted hashing method.
      * This member is set in the getSaltingInstance() function.
      *
-     * @var \TYPO3\CMS\Saltedpasswords\Salt\AbstractSalt
+     * @var SaltInterface
      */
     protected static $instance = null;
 
@@ -34,7 +38,7 @@ class SaltFactory
      *
      * @return array
      */
-    public static function getRegisteredSaltedHashingMethods()
+    public static function getRegisteredSaltedHashingMethods(): array
     {
         $saltMethods = static::getDefaultSaltMethods();
         if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/saltedpasswords']['saltMethods'])) {
@@ -58,13 +62,13 @@ class SaltFactory
      *
      * @return array
      */
-    protected static function getDefaultSaltMethods()
+    protected static function getDefaultSaltMethods(): array
     {
         return [
-            \TYPO3\CMS\Saltedpasswords\Salt\Md5Salt::class => \TYPO3\CMS\Saltedpasswords\Salt\Md5Salt::class,
-            \TYPO3\CMS\Saltedpasswords\Salt\BlowfishSalt::class => \TYPO3\CMS\Saltedpasswords\Salt\BlowfishSalt::class,
-            \TYPO3\CMS\Saltedpasswords\Salt\PhpassSalt::class => \TYPO3\CMS\Saltedpasswords\Salt\PhpassSalt::class,
-            \TYPO3\CMS\Saltedpasswords\Salt\Pbkdf2Salt::class => \TYPO3\CMS\Saltedpasswords\Salt\Pbkdf2Salt::class
+            Md5Salt::class => Md5Salt::class,
+            BlowfishSalt::class => BlowfishSalt::class,
+            PhpassSalt::class => PhpassSalt::class,
+            Pbkdf2Salt::class => Pbkdf2Salt::class
         ];
     }
 
@@ -78,7 +82,7 @@ class SaltFactory
      *
      * @param string|null $saltedHash Salted hashed password to determine the type of used method from or NULL to reset to the default type
      * @param string $mode The TYPO3 mode (FE or BE) saltedpasswords shall be used for
-     * @return SaltInterface An instance of salting hash method class
+     * @return SaltInterface|null An instance of salting hash method class or null if given hash is not supported
      */
     public static function getSaltingInstance($saltedHash = '', $mode = TYPO3_MODE)
     {
@@ -94,9 +98,9 @@ class SaltFactory
                     self::$instance = null;
                 }
             } else {
-                $classNameToUse = \TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility::getDefaultSaltingHashingMethod($mode);
+                $classNameToUse = SaltedPasswordsUtility::getDefaultSaltingHashingMethod($mode);
                 $availableClasses = static::getRegisteredSaltedHashingMethods();
-                self::$instance = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($availableClasses[$classNameToUse]);
+                self::$instance = GeneralUtility::makeInstance($availableClasses[$classNameToUse]);
             }
         }
         return self::$instance;
@@ -111,17 +115,17 @@ class SaltFactory
      * @param string $mode (optional) The TYPO3 mode (FE or BE) saltedpasswords shall be used for
      * @return bool TRUE, if salting hashing method has been found, otherwise FALSE
      */
-    public static function determineSaltingHashingMethod($saltedHash, $mode = TYPO3_MODE)
+    public static function determineSaltingHashingMethod(string $saltedHash, $mode = TYPO3_MODE): bool
     {
         $registeredMethods = static::getRegisteredSaltedHashingMethods();
-        $defaultClassName = \TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility::getDefaultSaltingHashingMethod($mode);
+        $defaultClassName = SaltedPasswordsUtility::getDefaultSaltingHashingMethod($mode);
         $defaultReference = $registeredMethods[$defaultClassName];
         unset($registeredMethods[$defaultClassName]);
         // place the default method first in the order
         $registeredMethods = [$defaultClassName => $defaultReference] + $registeredMethods;
         $methodFound = false;
         foreach ($registeredMethods as $method) {
-            $objectInstance = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($method);
+            $objectInstance = GeneralUtility::makeInstance($method);
             if ($objectInstance instanceof SaltInterface) {
                 $methodFound = $objectInstance->isValidSaltedPW($saltedHash);
                 if ($methodFound) {
@@ -137,13 +141,13 @@ class SaltFactory
      * Method sets a custom salting hashing method class.
      *
      * @param string $resource Object resource to use (e.g. \TYPO3\CMS\Saltedpasswords\Salt\BlowfishSalt::class)
-     * @return \TYPO3\CMS\Saltedpasswords\Salt\AbstractSalt An instance of salting hashing method object
+     * @return SaltInterface|null An instance of salting hashing method object or null
      */
-    public static function setPreferredHashingMethod($resource)
+    public static function setPreferredHashingMethod(string $resource)
     {
         self::$instance = null;
-        $objectInstance = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($resource);
-        if (is_object($objectInstance) && is_subclass_of($objectInstance, \TYPO3\CMS\Saltedpasswords\Salt\AbstractSalt::class)) {
+        $objectInstance = GeneralUtility::makeInstance($resource);
+        if ($objectInstance instanceof SaltInterface) {
             self::$instance = $objectInstance;
         }
         return self::$instance;
diff --git a/typo3/sysext/saltedpasswords/Classes/Salt/SaltInterface.php b/typo3/sysext/saltedpasswords/Classes/Salt/SaltInterface.php
index 83022efaa26f..fac7ea5fbf61 100644
--- a/typo3/sysext/saltedpasswords/Classes/Salt/SaltInterface.php
+++ b/typo3/sysext/saltedpasswords/Classes/Salt/SaltInterface.php
@@ -1,4 +1,5 @@
 <?php
+declare(strict_types=1);
 namespace TYPO3\CMS\Saltedpasswords\Salt;
 
 /*
@@ -28,21 +29,14 @@ interface SaltInterface
      * @param string $saltedHashPW Salted hash to compare plain-text password with
      * @return bool TRUE, if plaintext password is correct, otherwise FALSE
      */
-    public function checkPassword($plainPW, $saltedHashPW);
-
-    /**
-     * Returns length of required salt.
-     *
-     * @return int Length of required salt
-     */
-    public function getSaltLength();
+    public function checkPassword(string $plainPW, string $saltedHashPW): bool;
 
     /**
      * Returns whether all prequesites for the hashing methods are matched
      *
      * @return bool Method available
      */
-    public function isAvailable();
+    public function isAvailable(): bool;
 
     /**
      * Method creates a salted hash for a given plaintext password
@@ -51,7 +45,7 @@ interface SaltInterface
      * @param string $salt Optional custom salt to use
      * @return string Salted hashed password
      */
-    public function getHashedPassword($password, $salt = null);
+    public function getHashedPassword(string $password, string $salt = null);
 
     /**
      * Checks whether a user's hashed password needs to be replaced with a new hash.
@@ -65,15 +59,7 @@ interface SaltInterface
      * @param string $passString Salted hash to check if it needs an update
      * @return bool TRUE if salted hash needs an update, otherwise FALSE
      */
-    public function isHashUpdateNeeded($passString);
-
-    /**
-     * Method determines if a given string is a valid salt
-     *
-     * @param string $salt String to check
-     * @return bool TRUE if it's valid salt, otherwise FALSE
-     */
-    public function isValidSalt($salt);
+    public function isHashUpdateNeeded(string $passString): bool;
 
     /**
      * Method determines if a given string is a valid salted hashed password.
@@ -81,5 +67,5 @@ interface SaltInterface
      * @param string $saltedPW String to check
      * @return bool TRUE if it's valid salted hashed password, otherwise FALSE
      */
-    public function isValidSaltedPW($saltedPW);
+    public function isValidSaltedPW(string $saltedPW): bool;
 }
diff --git a/typo3/sysext/saltedpasswords/Classes/SaltedPasswordService.php b/typo3/sysext/saltedpasswords/Classes/SaltedPasswordService.php
index 60f44fdaf04e..e8d1e49a316a 100644
--- a/typo3/sysext/saltedpasswords/Classes/SaltedPasswordService.php
+++ b/typo3/sysext/saltedpasswords/Classes/SaltedPasswordService.php
@@ -50,7 +50,7 @@ class SaltedPasswordService extends AbstractAuthenticationService
      * An instance of the salted hashing method.
      * This member is set in the getSaltingInstance() function.
      *
-     * @var \TYPO3\CMS\Saltedpasswords\Salt\AbstractSalt
+     * @var \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface
      */
     protected $objInstanceSaltedPW = null;
 
diff --git a/typo3/sysext/saltedpasswords/Documentation/DevelopersGuide/Index.rst b/typo3/sysext/saltedpasswords/Documentation/DevelopersGuide/Index.rst
index b027deb086c1..6f46e4d481ee 100644
--- a/typo3/sysext/saltedpasswords/Documentation/DevelopersGuide/Index.rst
+++ b/typo3/sysext/saltedpasswords/Documentation/DevelopersGuide/Index.rst
@@ -93,8 +93,10 @@ such additional method available for this extension.
 Steps to be done:
 
 - create a new salting class that implements interface
-  :code:`\TYPO3\CMS\Saltedpasswords\Salt\SaltInterface` and abstract class
-  :code:`\TYPO3\CMS\Saltedpasswords\Salt\AbstractSalt` (see class
+  :code:`\TYPO3\CMS\Saltedpasswords\Salt\SaltInterface`
+
+  Optional: take advantage of abstract class
+  :code:`\TYPO3\CMS\Saltedpasswords\Salt\AbstractComposedSalt` (see class
   :code:`\TYPO3\CMS\Saltedpasswords\Salt\Md5Salt` for an example implementation)
 
 - register your salting method class
diff --git a/typo3/sysext/saltedpasswords/Migrations/Code/ClassAliasMap.php b/typo3/sysext/saltedpasswords/Migrations/Code/ClassAliasMap.php
new file mode 100644
index 000000000000..eff3a7a3c98e
--- /dev/null
+++ b/typo3/sysext/saltedpasswords/Migrations/Code/ClassAliasMap.php
@@ -0,0 +1,4 @@
+<?php
+return [
+    'TYPO3\\CMS\\Saltedpasswords\\Salt\\AbstractSalt' => \TYPO3\CMS\Saltedpasswords\Salt\AbstractComposedSalt::class,
+];
diff --git a/typo3/sysext/saltedpasswords/Tests/Unit/Salt/SaltFactoryTest.php b/typo3/sysext/saltedpasswords/Tests/Unit/Salt/SaltFactoryTest.php
index d72c23e8eb5b..281b9fb77e1a 100644
--- a/typo3/sysext/saltedpasswords/Tests/Unit/Salt/SaltFactoryTest.php
+++ b/typo3/sysext/saltedpasswords/Tests/Unit/Salt/SaltFactoryTest.php
@@ -24,7 +24,7 @@ class SaltFactoryTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
     /**
      * Keeps instance of object to test.
      *
-     * @var \TYPO3\CMS\Saltedpasswords\Salt\AbstractSalt
+     * @var \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface
      */
     protected $objectInstance = null;
 
@@ -59,32 +59,23 @@ class SaltFactoryTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $this->assertNotNull($this->objectInstance);
     }
 
-    /**
-     * @test
-     */
-    public function objectInstanceExtendsAbstractClass()
-    {
-        $this->assertTrue(is_subclass_of($this->objectInstance, \TYPO3\CMS\Saltedpasswords\Salt\AbstractSalt::class));
-    }
-
     /**
      * @test
      */
     public function objectInstanceImplementsInterface()
     {
-        $this->assertTrue(method_exists($this->objectInstance, 'checkPassword'), 'Missing method checkPassword() from interface ' . \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface::class . '.');
-        $this->assertTrue(method_exists($this->objectInstance, 'isHashUpdateNeeded'), 'Missing method isHashUpdateNeeded() from interface ' . \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface::class . '.');
-        $this->assertTrue(method_exists($this->objectInstance, 'isValidSalt'), 'Missing method isValidSalt() from interface ' . \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface::class . '.');
-        $this->assertTrue(method_exists($this->objectInstance, 'isValidSaltedPW'), 'Missing method isValidSaltedPW() from interface ' . \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface::class . '.');
-        $this->assertTrue(method_exists($this->objectInstance, 'getHashedPassword'), 'Missing method getHashedPassword() from interface ' . \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface::class . '.');
-        $this->assertTrue(method_exists($this->objectInstance, 'getSaltLength'), 'Missing method getSaltLength() from interface ' . \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface::class . '.');
+        $this->assertInstanceOf(\TYPO3\CMS\Saltedpasswords\Salt\SaltInterface::class, $this->objectInstance);
     }
 
     /**
      * @test
      */
-    public function base64EncodeReturnsProperLength()
+    public function abstractComposedSaltBase64EncodeReturnsProperLength()
     {
+        // set up an instance that extends AbstractComposedSalt first
+        $saltPbkdf2 = '$pbkdf2-sha256$6400$0ZrzXitFSGltTQnBWOsdAw$Y11AchqV4b0sUisdZd0Xr97KWoymNE0LNNrnEgY4H9M';
+        $this->objectInstance = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance($saltPbkdf2);
+
         // 3 Bytes should result in a 6 char length base64 encoded string
         // used for MD5 and PHPass salted hashing
         $byteLength = 3;
@@ -107,6 +98,7 @@ class SaltFactoryTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $saltMD5 = '$1$rasmusle$rISCgZzpwk3UhDidwXvin0';
         $this->objectInstance = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance($saltMD5);
         $this->assertTrue(get_class($this->objectInstance) == \TYPO3\CMS\Saltedpasswords\Salt\Md5Salt::class || is_subclass_of($this->objectInstance, \TYPO3\CMS\Saltedpasswords\Salt\Md5Salt::class));
+        $this->assertInstanceOf(\TYPO3\CMS\Saltedpasswords\Salt\AbstractComposedSalt::class, $this->objectInstance);
     }
 
     /**
@@ -117,6 +109,7 @@ class SaltFactoryTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $saltBlowfish = '$2a$07$abcdefghijklmnopqrstuuIdQV69PAxWYTgmnoGpe0Sk47GNS/9ZW';
         $this->objectInstance = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance($saltBlowfish);
         $this->assertTrue(get_class($this->objectInstance) == \TYPO3\CMS\Saltedpasswords\Salt\BlowfishSalt::class || is_subclass_of($this->objectInstance, \TYPO3\CMS\Saltedpasswords\Salt\BlowfishSalt::class));
+        $this->assertInstanceOf(\TYPO3\CMS\Saltedpasswords\Salt\AbstractComposedSalt::class, $this->objectInstance);
     }
 
     /**
@@ -127,6 +120,7 @@ class SaltFactoryTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $saltPhpass = '$P$CWF13LlG/0UcAQFUjnnS4LOqyRW43c.';
         $this->objectInstance = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance($saltPhpass);
         $this->assertTrue(get_class($this->objectInstance) == \TYPO3\CMS\Saltedpasswords\Salt\PhpassSalt::class || is_subclass_of($this->objectInstance, \TYPO3\CMS\Saltedpasswords\Salt\PhpassSalt::class));
+        $this->assertInstanceOf(\TYPO3\CMS\Saltedpasswords\Salt\AbstractComposedSalt::class, $this->objectInstance);
     }
 
     /**
@@ -137,6 +131,7 @@ class SaltFactoryTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $saltPbkdf2 = '$pbkdf2-sha256$6400$0ZrzXitFSGltTQnBWOsdAw$Y11AchqV4b0sUisdZd0Xr97KWoymNE0LNNrnEgY4H9M';
         $this->objectInstance = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance($saltPbkdf2);
         $this->assertTrue(get_class($this->objectInstance) == \TYPO3\CMS\Saltedpasswords\Salt\Pbkdf2Salt::class || is_subclass_of($this->objectInstance, \TYPO3\CMS\Saltedpasswords\Salt\Pbkdf2Salt::class));
+        $this->assertInstanceOf(\TYPO3\CMS\Saltedpasswords\Salt\AbstractComposedSalt::class, $this->objectInstance);
     }
 
     /**
diff --git a/typo3/sysext/saltedpasswords/composer.json b/typo3/sysext/saltedpasswords/composer.json
index 9a1914cfaea1..21966ff90b22 100644
--- a/typo3/sysext/saltedpasswords/composer.json
+++ b/typo3/sysext/saltedpasswords/composer.json
@@ -30,6 +30,11 @@
 				"partOfMinimalUsableSystem": true
 			},
 			"extension-key": "saltedpasswords"
+		},
+		"typo3/class-alias-loader": {
+			"class-alias-maps": [
+				"Migrations/Code/ClassAliasMap.php"
+			]
 		}
 	},
 	"autoload": {
-- 
GitLab