| 
<?phpdeclare(strict_types=1);
 namespace ParagonIE\PasswordLock;
 
 use \Defuse\Crypto\Crypto;
 use \Defuse\Crypto\Key;
 use \ParagonIE\ConstantTime\Base64;
 use \ParagonIE\ConstantTime\Binary;
 
 class PasswordLock
 {
 /**
 * 1. Hash password using bcrypt-base64-SHA256
 * 2. Encrypt-then-MAC the hash
 *
 * @param string $password
 * @param Key $aesKey
 * @return string
 * @throws \Exception
 * @throws \InvalidArgumentException
 */
 public static function hashAndEncrypt(string $password, Key $aesKey): string
 {
 /** @var string $hash */
 $hash = \password_hash(
 Base64::encode(
 \hash('sha384', $password, true)
 ),
 PASSWORD_DEFAULT
 );
 if (!\is_string($hash)) {
 throw new \Exception("Unknown hashing error.");
 }
 return Crypto::encrypt($hash, $aesKey);
 }
 /**
 * 1. VerifyHMAC-then-Decrypt the ciphertext to get the hash
 * 2. Verify that the password matches the hash
 *
 * @param string $password
 * @param string $ciphertext
 * @param string $aesKey - must be exactly 16 bytes
 * @return bool
 * @throws \Exception
 * @throws \InvalidArgumentException
 */
 public static function decryptAndVerifyLegacy(string $password, string $ciphertext, string $aesKey): bool
 {
 if (Binary::safeStrlen($aesKey) !== 16) {
 throw new \Exception("Encryption keys must be 16 bytes long");
 }
 $hash = Crypto::legacyDecrypt(
 $ciphertext,
 $aesKey
 );
 if (!\is_string($hash)) {
 throw new \Exception("Unknown hashing error.");
 }
 return \password_verify(
 Base64::encode(
 \hash('sha256', $password, true)
 ),
 $hash
 );
 }
 
 /**
 * 1. VerifyHMAC-then-Decrypt the ciphertext to get the hash
 * 2. Verify that the password matches the hash
 *
 * @param string $password
 * @param string $ciphertext
 * @param Key $aesKey
 * @return bool
 * @throws \Exception
 * @throws \InvalidArgumentException
 */
 public static function decryptAndVerify(string $password, string $ciphertext, Key $aesKey): bool
 {
 $hash = Crypto::decrypt(
 $ciphertext,
 $aesKey
 );
 if (!\is_string($hash)) {
 throw new \Exception("Unknown hashing error.");
 }
 return \password_verify(
 Base64::encode(
 \hash('sha384', $password, true)
 ),
 $hash
 );
 }
 
 /**
 * Key rotation method -- decrypt with your old key then re-encrypt with your new key
 *
 * @param string $ciphertext
 * @param  Key $oldKey
 * @param Key $newKey
 * @return string
 */
 public static function rotateKey(string $ciphertext, Key $oldKey, Key $newKey): string
 {
 $plaintext = Crypto::decrypt($ciphertext, $oldKey);
 return Crypto::encrypt($plaintext, $newKey);
 }
 
 /**
 * For migrating from an older version of the library
 *
 * @param string $password
 * @param string $ciphertext
 * @param string $oldKey
 * @param Key $newKey
 * @return string
 * @throws \Exception
 */
 public static function upgradeFromVersion1(
 string $password,
 string $ciphertext,
 string $oldKey,
 Key $newKey
 ): string {
 if (!self::decryptAndVerifyLegacy($password, $ciphertext, $oldKey)) {
 throw new \Exception(
 'The correct password is necessary for legacy migration.'
 );
 }
 $plaintext = Crypto::legacyDecrypt($ciphertext, $oldKey);
 return self::hashAndEncrypt($plaintext, $newKey);
 }
 }
 
 |