From ea5cb63b85c0d4199c84c5491d457ce88186b496 Mon Sep 17 00:00:00 2001 From: Andre Polykanine Date: Sat, 9 Jul 2022 23:55:25 +0200 Subject: [PATCH] Deprecate Symmetric Key (#53) * Deprecate Symmetric Key * Fix changelog --- CHANGELOG.md | 2 + LICENSE | 2 +- README.md | 61 ++++++------ captainhook.json | 2 +- src/Base64.php | 2 +- src/Crypt.php | 34 +++---- src/Exception/Base64Exception.php | 2 +- src/Exception/CryptException.php | 2 +- src/Exception/DecryptionException.php | 2 +- src/Exception/EncryptionException.php | 2 +- src/Exception/InvalidTokenException.php | 2 +- src/Exception/IridiumException.php | 2 +- src/Exception/OsstException.php | 2 +- src/Exception/PasswordException.php | 2 +- src/Exception/SharedKeyException.php | 42 ++++++++ src/Exception/SymmetricKeyException.php | 5 + src/Key/DerivedKeys.php | 4 +- src/Key/SharedKey.php | 123 ++++++++++++++++++++++++ src/Key/SymmetricKey.php | 7 ++ src/Osst.php | 22 ++--- src/Password.php | 18 ++-- tests/Base64Test.php | 2 +- tests/CryptTest.php | 18 ++-- tests/OsstTest.php | 2 +- tests/PasswordTest.php | 16 +-- tests/SharedKeyTest.php | 72 ++++++++++++++ tests/SymmetricKeyTest.php | 3 + 27 files changed, 354 insertions(+), 99 deletions(-) create mode 100644 src/Exception/SharedKeyException.php create mode 100644 src/Key/SharedKey.php create mode 100644 tests/SharedKeyTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index f47b083..214dd33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ * [#52](https://github.com/Oire/Iridium-php/pull/52): Add release notes config; Add PHP CodeSniffer to Github Actions CI. +* [#53](https://github.com/Oire/Iridium-php/pull/53): + Deprecated Symmetric Key in favor of Shared Key for simpler naming. # Version 1.1 diff --git a/LICENSE b/LICENSE index 3b6d4a3..e9f13e8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright © 2021 Andre Polykanine also known as Menelion Elensúlë, The Magical Kingdom of Oirë, https://github.com/Oire +Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, The Magical Kingdom of Oirë, https://github.com/Oire Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 77da239..44b4fa4 100644 --- a/README.md +++ b/README.md @@ -93,17 +93,18 @@ The Base64 class has the following methods: The Crypt module is used to encrypt and decrypt data. **Note**! Do not use this for managing passwords! Passwords must not be encrypted, they must be *hashed* instead. To manage passwords, use the Password module (see below). -Currently the Crypt module supports only symmetric-key encryption, i.e., encryption and decryption is performed with one shared key. +Currently the Crypt module supports only shared key encryption, i.e., encryption and decryption is performed with one single key. -### Symmetric Key +### Shared Key +**Note**! the `SymmetricKey` class is deprecated since version 1.2 and will be removed in version 2.0. It holds the same object but was renamed to `SharedKey` for simplicity. This objects holds a key used to encrypt and decrypt data with the Crypt module. First you need to create a key and save it somewhere (i.e., in a .env file): ```php -use Oire\Iridium\Key\SymmetricKey; +use Oire\Iridium\Key\SharedKey; -$symmetricKey = new SymmetricKey(); -$key = $symmetricKey->getKey(); +$sharedKey = new SharedKey(); +$key = $sharedKey->getKey(); // Save the key instead echo $key . PHP_EOL; ``` @@ -114,11 +115,11 @@ This will output a readable and storable string, something similar to this: AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8 ``` -#### Symmetric Key Methods +#### SharedKey Methods Generally, you will only need the `getKey()` method for storing the key in a safe place. You can also benefit from using the `__toString()` method and treat the key object as a string. However, let’s describe all the methods for the sake of completeness: -* `__construct(string|null $key = null)` — Class constructor. If a key is provided, it will be applied to create a new SymmetricKey instance. If not, a random key will be generated instead. +* `__construct(string|null $key = null)` — Class constructor. If a key is provided, it will be applied to create a new SharedKey instance. If not, a random key will be generated instead. * `getRawKey(): string` — Returns the key in raw binary form. Needed mostly for internal use. * `getKey(): string` — Returns the key in readable and storable form. Use this to retrieve a newly generated random key. * `deriveKeys(string|null $salt = null): DerivedKeys` — Uses [hash key derivation function](https://en.wikipedia.org/wiki/HKDF) to derive encryption and authentication keys and returns a `DerivedKeys` object, see below. Use this only if you really know what you are doing. It is used internally by the Crypt module. If the salt is provided, derives the keys based on that salt (used for decryption). In 99,(9)% of cases you don’t need to use this method directly. @@ -126,9 +127,9 @@ Generally, you will only need the `getKey()` method for storing the key in a saf ### Derived Keys -The DerivedKeys object holds the keys derived by the `deriveKeys()` method of the symmetric key. Again, in 99,(9)% of cases you don’t want to use it, but let’s enumerate its methods. +The DerivedKeys object holds the keys derived by the `deriveKeys()` method of the shared key. Again, in 99,(9)% of cases you don’t want to use it, but let’s enumerate its methods. -* `__construct(string $salt, string $encryptionKey, string $authenticationKey)` — Class constructor. Is instantiated by the `deriveKeys()` method of the `SymmetricKey` object. +* `__construct(string $salt, string $encryptionKey, string $authenticationKey)` — Class constructor. Is instantiated by the `deriveKeys()` method of the `SharedKey` object. * `getSalt(): string` — Gets the encryption salt. * `getEncryptionKey(): string` — Gets the derived encryption key. * `getAuthenticationKey(): string` — Gets the derived authentication key. @@ -136,34 +137,34 @@ The DerivedKeys object holds the keys derived by the `deriveKeys()` method of th ### Crypt Usage Examples -If you created a Symmetric key as shown above, you can encrypt your data with this key: +If you created a shared key as shown above, you can encrypt your data with this key: ```php use Oire\Iridium\Crypt; -use Oire\Iridium\Key\SymmetricKey; +use Oire\Iridium\Key\SharedKey; $data = 'Mischief managed!'; -$symmetricKey = new SymmetricKey($key); -$encrypted = Crypt::encrypt($data, $symmetricKey); +$sharedKey = new SharedKey($key); +$encrypted = Crypt::encrypt($data, $sharedKey); ``` That's it, you may store your encrypted data in a database or perform other actions with them. To decrypt the data with the same key, use the following: ```php -$decrypted = Crypt::decrypt($encrypted, $symmetricKey); +$decrypted = Crypt::decrypt($encrypted, $sharedKey); ``` ### Exceptions -Crypt throws `EncryptionException`, `DecryptionException` and sometimes a more general `CryptException`. If something is wrong with the key, a `SymmetricKeyException` is thrown. +Crypt throws `EncryptionException`, `DecryptionException` and sometimes a more general `CryptException`. If something is wrong with the key, a `SharedKeyException` is thrown. ### Methods The Crypt class has the following methods: -* `static encrypt(string $data, SymmetricKey $key): string` — Encrypts given data with a given key. Returns the encrypted data in readable and storable form. -* `static Decrypt(string $encryptedData, SymmetricKey $key): string` — Decrypts previously encrypted data with the same key they were encrypted with and returns the original string. -* `static swapKey(string $data, SymmetricKey $oldKey, SymmetricKey $newKey): string` — Reencrypts encrypted data with a different key and returns the newly encrypted data. +* `static encrypt(string $data, SharedKey $key): string` — Encrypts given data with a given key. Returns the encrypted data in readable and storable form. +* `static Decrypt(string $encryptedData, SharedKey $key): string` — Decrypts previously encrypted data with the same key they were encrypted with and returns the original string. +* `static swapKey(string $data, SharedKey $oldKey, SharedKey $newKey): string` — Reencrypts encrypted data with a different key and returns the newly encrypted data. ## 🔒 Password @@ -175,14 +176,14 @@ To lock, i.e., hash a password, use the following: ```php use Oire\Iridium\Exception\PasswordException; -use Oire\Iridium\Key\SymmetricKey; +use Oire\Iridium\Key\SharedKey; use Oire\Iridium\Password; // You should have $key somewhere in an environment variable -$symmetricKey = new SymmetricKey($key); +$sharedKey = new SharedKey($key); try { - $storeMe = Password::lock($_POST['password'], $symmetricKey); + $storeMe = Password::lock($_POST['password'], $sharedKey); } catch (PasswordException $e) { // Handle errors } @@ -193,7 +194,7 @@ To check whether a provided password is valid, use the following: ```php try { - $isPasswordValid = Password::check($_POST['password'], $hashFromDatabase, $symmetricKey); + $isPasswordValid = Password::check($_POST['password'], $hashFromDatabase, $sharedKey); } catch (PasswordException $e) { // Handle errors. Something went wrong: most often it's a wrong or corrupted key } @@ -212,8 +213,8 @@ Remember that you cannot "decrypt" a password and obviously must not store unhas The Password class has the following methods: -* `static Lock(string $password, SymmetricKey $key): string` — Locks, i.e., hashes a password and encrypts it with a given key. Returns the encrypted hash in readable and storable format. A hashed password cannot be restored, so it is safe to be stored in a database. -* `static Check(string $password, string $encryptedHash, SymmetricKey $key): bool` — Verifies whether a given password matches the provided hash. Returns `true` on success and `false` on failure. +* `static Lock(string $password, SharedKey $key): string` — Locks, i.e., hashes a password and encrypts it with a given key. Returns the encrypted hash in readable and storable format. A hashed password cannot be restored, so it is safe to be stored in a database. +* `static Check(string $password, string $encryptedHash, SharedKey $key): bool` — Verifies whether a given password matches the provided hash. Returns `true` on success and `false` on failure. ## 🍪 Osst, Simple Yet Secure Tokens Suitable for Authentication Cookies and Password Recovery @@ -340,13 +341,13 @@ You may set expiration time in three different ways, as you like: You may store some sensitive data in the additional information for the token such as old and new e-mail address and similar things. **Note**! Do **not** store plain-text passwords in this property, it can be decrypted! Passwords must not be decryptable, they must be *hashed* instead. If you need to handle passwords, use the Password class, it is suitable for proper password hashing (see above). You may store password hashes in this property, though. -If your additional info contains sensitive data, you can encrypt it. To do this, you first need to have an Iridium symmetric key (see above): +If your additional info contains sensitive data, you can encrypt it. To do this, you first need to have an Iridium key (see above): ```php -use Oire\Iridium\Key\SymmetricKey; +use Oire\Iridium\Key\SharedKey; use Oire\Iridium\Osst; -$key = new SymmetricKey(); +$key = new SharedKey(); // Store the key somewhere safe, i.e., in an environment variable. You can safely cast it to string for that (see above) $additionalInfo = '{"oldEmail": "john@example.com", "newEmail": "john.doe@example.com"}'; $osst = (new Osst($dbConnection)) @@ -371,7 +372,7 @@ Osst throws two types of exceptions: Below all of the Osst methods are outlined. -* `__construct(PDO $dbConnection, string|null $token, Oire\Iridium\Key\SymmetricKey|null $additionalInfoDecryptionKey)` — Instantiate a new Osst object. Provide a PDO instance as the first parameter, the user-provided token as the second one, and the Iridium key for decrypting additional info as the third one. **Note**! Provide the token only if you received it from the user. If you want to create a fresh token, the second and third parameters must not be set. +* `__construct(PDO $dbConnection, string|null $token, Oire\Iridium\Key\SharedKey|null $additionalInfoDecryptionKey)` — Instantiate a new Osst object. Provide a PDO instance as the first parameter, the user-provided token as the second one, and the Iridium key for decrypting additional info as the third one. **Note**! Provide the token only if you received it from the user. If you want to create a fresh token, the second and third parameters must not be set. * `getDbConnection(): PDO` — Get the database connection for the current Osst instance as a PDO object. * `getToken(): string` — Get the token for the current Osst instance as a string. Throws `OsstException` if the token was not created or set before. * `getUserId(): int` — Get the ID of the user the token belongs to, as an integer. @@ -388,7 +389,7 @@ Below all of the Osst methods are outlined. * `getTokenType(): int|null` — Get the type for the current token. Returns integer if the token type was set before, or null if the token has no type. * `setTokenType(int|null $tokenType): self` — Set the type for the current token, as integer or null. Returns `$this` for chainability. * `getAdditionalInfo(): string|null` — Get additional info for the token. Returns string or null, if additional info was not set before. -* `setAdditionalInfo(string|null $additionalInfo, Oire\Iridium\Key\SymmetricKey|null $encryptionKey = null): self` — Set additional info for the current token. If the `$encryptionKey` parameter is not empty, tries to encrypt the additional information using the Crypt class. Returns `$this` for chainability. +* `setAdditionalInfo(string|null $additionalInfo, Oire\Iridium\Key\SharedKey|null $encryptionKey = null): self` — Set additional info for the current token. If the `$encryptionKey` parameter is not empty, tries to encrypt the additional information using the Crypt class. Returns `$this` for chainability. * `persist(): self` — Store the token into the database. Returns `$this` for chainability. * `revokeToken(bool $deleteToken = false): void` — Revoke. i.e., invalidate the current token after it is used. If the `$deleteToken` parameter is set to `true`, the token will be deleted from the database, and `getToken()` will return `null`. If it is set to `false` (default), the expiration time for the token will be updated and set to a value in the past. The method returns no value. * `static clearExpiredTokens(PDO $dbConnection): int` — Delete all expired tokens from the database. As it is a static method, it receives the database connection as a PDO object. Returns the number of deleted tokens, as integer. @@ -415,5 +416,5 @@ When your pull request is submitted, make sure all checks passed on CI. ## License -Copyright © 2021-2022, Andre Polykanine also known as Menelion Elensúlë, [The Magical Kingdom of Oirë](https://github.com/Oire/). +Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, [The Magical Kingdom of Oirë](https://github.com/Oire/). This software is licensed under an MIT license. diff --git a/captainhook.json b/captainhook.json index 4e6ad62..19ec9f6 100644 --- a/captainhook.json +++ b/captainhook.json @@ -9,7 +9,7 @@ "action": "php vendor/bin/php-cs-fixer fix" }, { - "action": "php vendor/bin/phpcs" + "action": "php vendor/bin/phpcs src tests" }, { "action": "php vendor/bin/phpunit" diff --git a/src/Base64.php b/src/Base64.php index fd19ce5..cea9bc2 100644 --- a/src/Base64.php +++ b/src/Base64.php @@ -5,7 +5,7 @@ /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens - * Copyright © 2021, Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * Portions copyright © 2016 Paragon Initiative Enterprises. * Portions copyright © 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com) * diff --git a/src/Crypt.php b/src/Crypt.php index 2ca2e0f..cb2f378 100644 --- a/src/Crypt.php +++ b/src/Crypt.php @@ -6,14 +6,14 @@ use Oire\Iridium\Exception\CryptException; use Oire\Iridium\Exception\DecryptionException; use Oire\Iridium\Exception\EncryptionException; -use Oire\Iridium\Exception\SymmetricKeyException; +use Oire\Iridium\Exception\SharedKeyException; use Oire\Iridium\Key\DerivedKeys; -use Oire\Iridium\Key\SymmetricKey; +use Oire\Iridium\Key\SharedKey; /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens * Performs Authenticated Encryption. - * Copyright © 2021, Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * Copyright © 2016 Scott Arciszewski, Paragon Initiative Enterprises, https://paragonie.com. * Portions copyright © 2016 Taylor Hornby, Defuse Security Research and Development, https://defuse.ca. * @@ -47,11 +47,11 @@ final class Crypt /** * Encrypt data with a given key. * @param string $plainText The data to be encrypted - * @param SymmetricKey $key The Iridium key used for encryption + * @param SharedKey $key The Iridium key used for encryption * @throws EncryptionException * @return string Returns the encrypted data */ - public static function encrypt(string $plainText, SymmetricKey $key): string + public static function encrypt(string $plainText, SharedKey $key): string { if (!function_exists('openssl_encrypt')) { throw new EncryptionException('OpenSSL encryption not available.'); @@ -63,7 +63,7 @@ public static function encrypt(string $plainText, SymmetricKey $key): string try { $derivedKeys = $key->deriveKeys(); - } catch (SymmetricKeyException $e) { + } catch (SharedKeyException $e) { throw new EncryptionException(sprintf('Unable to derive keys: %s', $e->getMessage()), $e); } @@ -99,11 +99,11 @@ public static function encrypt(string $plainText, SymmetricKey $key): string /** * Decrypt data with a given key. * @param string $cipherText The encrypted data, as a string - * @param SymmetricKey $key The Iridium key the data was encrypted with + * @param SharedKey $key The Iridium key the data was encrypted with * @throws DecryptionException * @return string the decrypted plain text */ - public static function decrypt(string $cipherText, SymmetricKey $key): string + public static function decrypt(string $cipherText, SharedKey $key): string { if (!function_exists('openssl_decrypt')) { throw new DecryptionException('OpenSSL decryption not available.'); @@ -132,7 +132,7 @@ public static function decrypt(string $cipherText, SymmetricKey $key): string try { $derivedKeys = $key->deriveKeys($salt); - } catch (SymmetricKeyException $e) { + } catch (SharedKeyException $e) { throw new DecryptionException(sprintf('Unable to derive keys: %s.', $e->getMessage()), $e); } @@ -196,17 +196,17 @@ public static function decrypt(string $cipherText, SymmetricKey $key): string /** * Change encryption key (for instance, if the old one is compromised). - * @param string $cipherText The encrypted data - * @param SymmetricKey $oldKey The key the data was encrypted before - * @param SymmetricKey $newKey The key for re-encrypting the data - * @throws SymmetricKeyException - * @return string Returns the re-encrypted data + * @param string $cipherText The encrypted data + * @param SharedKey $oldKey The key the data was encrypted before + * @param SharedKey $newKey The key for re-encrypting the data + * @throws SharedKeyException + * @return string Returns the re-encrypted data */ - public static function swapKey(string $cipherText, SymmetricKey $oldKey, SymmetricKey $newKey): string + public static function swapKey(string $cipherText, SharedKey $oldKey, SharedKey $newKey): string { try { $plainText = self::decrypt($cipherText, $oldKey); - } catch (SymmetricKeyException $e) { + } catch (SharedKeyException $e) { throw new CryptException(sprintf('Invalid old key: %s', $e->getMessage()), $e); } catch (DecryptionException $e) { throw new CryptException(sprintf('Decryption failed: %s.', $e->getMessage()), $e); @@ -214,7 +214,7 @@ public static function swapKey(string $cipherText, SymmetricKey $oldKey, Symmetr try { return self::encrypt($plainText, $newKey); - } catch (SymmetricKeyException $e) { + } catch (SharedKeyException $e) { throw new CryptException(sprintf('Invalid new key: %s', $e->getMessage()), $e); } catch (EncryptionException $e) { throw new CryptException(sprintf('Encryption failed: %s.', $e->getMessage()), $e); diff --git a/src/Exception/Base64Exception.php b/src/Exception/Base64Exception.php index 1392692..2c38bd8 100644 --- a/src/Exception/Base64Exception.php +++ b/src/Exception/Base64Exception.php @@ -4,7 +4,7 @@ /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens - * Copyright © 2021, Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * Portions copyright © 2016 Paragon Initiative Enterprises. * Portions copyright © 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com) * diff --git a/src/Exception/CryptException.php b/src/Exception/CryptException.php index 165cd70..397fa1c 100644 --- a/src/Exception/CryptException.php +++ b/src/Exception/CryptException.php @@ -5,7 +5,7 @@ /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens * Wraps Bcrypt-SHA2 in Authenticated Encryption. - * Copyright © 2021, Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * Copyright © 2016 Scott Arciszewski, Paragon Initiative Enterprises, https://paragonie.com. * Portions copyright © 2016 Taylor Hornby, Defuse Security Research and Development, https://defuse.ca. * diff --git a/src/Exception/DecryptionException.php b/src/Exception/DecryptionException.php index a15a5cc..5998563 100644 --- a/src/Exception/DecryptionException.php +++ b/src/Exception/DecryptionException.php @@ -5,7 +5,7 @@ /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens * Wraps Bcrypt-SHA2 in Authenticated Encryption. - * Copyright © 2021, Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * Copyright © 2016 Scott Arciszewski, Paragon Initiative Enterprises, https://paragonie.com. * Portions copyright © 2016 Taylor Hornby, Defuse Security Research and Development, https://defuse.ca. * diff --git a/src/Exception/EncryptionException.php b/src/Exception/EncryptionException.php index b633f01..d48186c 100644 --- a/src/Exception/EncryptionException.php +++ b/src/Exception/EncryptionException.php @@ -5,7 +5,7 @@ /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens * Wraps Bcrypt-SHA2 in Authenticated Encryption. - * Copyright © 2021, Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * Copyright © 2016 Scott Arciszewski, Paragon Initiative Enterprises, https://paragonie.com. * Portions copyright © 2016 Taylor Hornby, Defuse Security Research and Development, https://defuse.ca. * diff --git a/src/Exception/InvalidTokenException.php b/src/Exception/InvalidTokenException.php index 7c36660..d558e02 100644 --- a/src/Exception/InvalidTokenException.php +++ b/src/Exception/InvalidTokenException.php @@ -6,7 +6,7 @@ /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens - * Copyright © 2021 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/Exception/IridiumException.php b/src/Exception/IridiumException.php index 933e665..c899380 100644 --- a/src/Exception/IridiumException.php +++ b/src/Exception/IridiumException.php @@ -7,7 +7,7 @@ /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens - * Copyright © 2021, Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/Exception/OsstException.php b/src/Exception/OsstException.php index 9c8c745..74d7cad 100644 --- a/src/Exception/OsstException.php +++ b/src/Exception/OsstException.php @@ -4,7 +4,7 @@ /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens - * Copyright © 2021 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/Exception/PasswordException.php b/src/Exception/PasswordException.php index 5425ef8..e1f01a4 100644 --- a/src/Exception/PasswordException.php +++ b/src/Exception/PasswordException.php @@ -4,7 +4,7 @@ /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens - * Copyright © 2021, Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * Copyright © 2016 Scott Arciszewski, Paragon Initiative Enterprises, https://paragonie.com. * * Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/src/Exception/SharedKeyException.php b/src/Exception/SharedKeyException.php new file mode 100644 index 0000000..84420e6 --- /dev/null +++ b/src/Exception/SharedKeyException.php @@ -0,0 +1,42 @@ +rawKey = Base64::decode($key); + } catch (Base64Exception $e) { + throw new SharedKeyException(sprintf('Unable to decode provided key: %s.', $e->getMessage()), $e); + } + + if (mb_strlen($this->rawKey, Crypt::STRING_ENCODING_8BIT) !== self::KEY_SIZE) { + throw new SharedKeyException('Invalid key given.'); + } + + $this->key = $key; + } else { + $this->rawKey = random_bytes(self::KEY_SIZE); + $this->key = Base64::encode($this->rawKey); + } + } + + /** + * Get the key in raw binary form. + * @return string The key in binary form as a string + */ + public function getRawKey(): string + { + return $this->rawKey; + } + + /** + * Get the key in readable and storable form. + * @return string The key in readable form as a string + */ + public function getKey(): string + { + return $this->key; + } + + /** + * Derive encryption and authentication keys for encrypt-then-MAC. + * @param string|null $salt Salt for key derivation. Provide this only for decryption! + * @return DerivedKeys A derived keys object containing salt, encryptionKey and authenticationKey + */ + public function deriveKeys(?string $salt = null): DerivedKeys + { + if ($salt) { + if (mb_strlen($salt, Crypt::STRING_ENCODING_8BIT) !== DerivedKeys::SALT_SIZE) { + throw new SharedKeyException('Given salt is of incorrect length.'); + } + } else { + $salt = random_bytes(DerivedKeys::SALT_SIZE); + } + + $encryptionKey = hash_hkdf(Crypt::HASH_FUNCTION, $this->rawKey, 0, self::ENCRYPTION_INFO, $salt); + + if ($encryptionKey === false) { + throw SharedKeyException::encryptionKeyFailed(); + } + + $authenticationKey = hash_hkdf(Crypt::HASH_FUNCTION, $this->rawKey, 0, self::AUTHENTICATION_INFO, $salt); + + if ($authenticationKey === false) { + throw SharedKeyException::authenticationKeyFailed(); + } + + return new DerivedKeys($salt, $encryptionKey, $authenticationKey); + } + + /** + * Get key object as string. + * @return string Returns the key in readable and storable form + */ + public function __toString(): string + { + return $this->key; + } +} diff --git a/src/Key/SymmetricKey.php b/src/Key/SymmetricKey.php index aac20c4..86303d6 100644 --- a/src/Key/SymmetricKey.php +++ b/src/Key/SymmetricKey.php @@ -32,6 +32,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + +/** + * @deprecated 1.2 Use Oire\Iridium\Key\SharedKey instead + *@psalm-suppress UnusedClass + */ final class SymmetricKey { public const KEY_SIZE = 32; @@ -43,6 +48,7 @@ final class SymmetricKey /** * Instantiate a new Symmetric Key object. * @param string|null $key A key saved before (for example, from a .env file). If empty, a new key will be generated + * @psalm-suppress DeprecatedClass */ public function __construct(?string $key = null) { @@ -86,6 +92,7 @@ public function getKey(): string * Derive encryption and authentication keys for encrypt-then-MAC. * @param string|null $salt Salt for key derivation. Provide this only for decryption! * @return DerivedKeys A derived keys object containing salt, encryptionKey and authenticationKey + * @psalm-suppress DeprecatedClass */ public function deriveKeys(?string $salt = null): DerivedKeys { diff --git a/src/Osst.php b/src/Osst.php index 322efe9..444492d 100644 --- a/src/Osst.php +++ b/src/Osst.php @@ -8,7 +8,7 @@ use Oire\Iridium\Exception\CryptException; use Oire\Iridium\Exception\InvalidTokenException; use Oire\Iridium\Exception\OsstException; -use Oire\Iridium\Key\SymmetricKey; +use Oire\Iridium\Key\SharedKey; use PDO; use PDOException; use Throwable; @@ -16,7 +16,7 @@ /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens * Implements the split token authentication model proposed by Paragon Initiatives. - * Copyright © 2021 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * Idea Copyright © 2017 Paragon Initiatives. * @see https://paragonie.com/blog/2017/02/split-tokens-token-based-authentication-protocols-without-side-channels * @@ -58,14 +58,14 @@ final class Osst /** * Instantiate a new Osst object. - * @param PDO $dbConnection Connection to your database - * @param string|null $token A user-provided token - * @param SymmetricKey|null $additionalInfoKey The Iridium key to decrypt additional info for the token + * @param PDO $dbConnection Connection to your database + * @param string|null $token A user-provided token + * @param SharedKey|null $additionalInfoKey The Iridium key to decrypt additional info for the token */ public function __construct( PDO $dbConnection, ?string $token = null, - ?SymmetricKey $additionalInfoKey = null + ?SharedKey $additionalInfoKey = null ) { $this->dbConnection = $dbConnection; @@ -121,10 +121,10 @@ public function getToken(): string /** * Set and validate a user-provided token. * @param string $token The token provided by the user - * @param SymmetricKey|null $additionalInfoKey If not empty, the encrypted additional info will be decrypted + * @param SharedKey|null $additionalInfoKey If not empty, the encrypted additional info will be decrypted * @throws InvalidTokenException */ - private function setToken(string $token, ?SymmetricKey $additionalInfoKey = null): void + private function setToken(string $token, ?SharedKey $additionalInfoKey = null): void { try { $rawToken = Base64::decode($token); @@ -425,11 +425,11 @@ public function getAdditionalInfo(): ?string /** * Set the additional info for the token. - * @param string|null $additionalInfo Any additional info you want to convey along with the token, as string - * @param SymmetricKey|null $encryptionKey If not empty, the data will be encrypted + * @param string|null $additionalInfo Any additional info you want to convey along with the token, as string + * @param SharedKey|null $encryptionKey If not empty, the data will be encrypted * @return $this */ - public function setAdditionalInfo(?string $additionalInfo, ?SymmetricKey $encryptionKey = null): self + public function setAdditionalInfo(?string $additionalInfo, ?SharedKey $encryptionKey = null): self { if ($additionalInfo) { if ($encryptionKey) { diff --git a/src/Password.php b/src/Password.php index 77fed29..385ff38 100644 --- a/src/Password.php +++ b/src/Password.php @@ -5,13 +5,13 @@ use Oire\Iridium\Exception\DecryptionException; use Oire\Iridium\Exception\EncryptionException; use Oire\Iridium\Exception\PasswordException; -use Oire\Iridium\Exception\SymmetricKeyException; -use Oire\Iridium\Key\SymmetricKey; +use Oire\Iridium\Exception\SharedKeyException; +use Oire\Iridium\Key\SharedKey; /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens * Performs Authenticated Encryption. - * Copyright © 2021, Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * Copyright © 2016 Scott Arciszewski, Paragon Initiative Enterprises, https://paragonie.com. * Portions copyright © 2016 Taylor Hornby, Defuse Security Research and Development, https://defuse.ca. * @@ -39,11 +39,11 @@ final class Password * Hash password, encrypt-then-MAC the hash * * @param string $password The password to hash - * @param SymmetricKey $key The Iridium key for encryption + * @param SharedKey $key The Iridium key for encryption * @throws PasswordException * @return string Returns encrypted result */ - public static function lock(string $password, SymmetricKey $key): string + public static function lock(string $password, SharedKey $key): string { if (!$password) { throw new PasswordException('Password must not be empty.'); @@ -58,7 +58,7 @@ public static function lock(string $password, SymmetricKey $key): string try { return Crypt::encrypt($hash, $key); - } catch (SymmetricKeyException $e) { + } catch (SharedKeyException $e) { throw new PasswordException(sprintf('Invalid key given: %s', $e->getMessage()), $e); } catch (EncryptionException $e) { throw new PasswordException(sprintf('Encryption failed: %s.', $e->getMessage()), $e); @@ -70,11 +70,11 @@ public static function lock(string $password, SymmetricKey $key): string * * @param string $password The password to check * @param string $cipherText The hash to match against - * @param SymmetricKey $key The Iridium key used for encryption` + * @param SharedKey $key The Iridium key used for encryption * @throws PasswordException * @return bool Returns true if the password is valid, false otherwise */ - public static function check(string $password, string $cipherText, SymmetricKey $key): bool + public static function check(string $password, string $cipherText, SharedKey $key): bool { if (!$password) { throw new PasswordException('Password must not be empty.'); @@ -82,7 +82,7 @@ public static function check(string $password, string $cipherText, SymmetricKey try { $hash = Crypt::decrypt($cipherText, $key); - } catch (SymmetricKeyException $e) { + } catch (SharedKeyException $e) { throw new PasswordException(sprintf('Invalid key given: %s', $e->getMessage()), $e); } catch (DecryptionException $e) { throw new PasswordException(sprintf('Decryption failed: %s.', $e->getMessage()), $e); diff --git a/tests/Base64Test.php b/tests/Base64Test.php index da737cc..f7c01b4 100644 --- a/tests/Base64Test.php +++ b/tests/Base64Test.php @@ -8,7 +8,7 @@ /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens - * Copyright © 2021, Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * Portions copyright © 2016 Paragon Initiative Enterprises. * Portions copyright © 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com) * diff --git a/tests/CryptTest.php b/tests/CryptTest.php index 6ca9971..d84f4ad 100644 --- a/tests/CryptTest.php +++ b/tests/CryptTest.php @@ -4,12 +4,12 @@ use Oire\Iridium\Crypt; use Oire\Iridium\Exception\DecryptionException; -use Oire\Iridium\Key\SymmetricKey; +use Oire\Iridium\Key\SharedKey; use PHPUnit\Framework\TestCase; /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens - * Copyright © 2021, Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * Copyright © 2016 Scott Arciszewski, Paragon Initiative Enterprises, https://paragonie.com. * Portions copyright © 2016 Taylor Hornby, Defuse Security Research and Development, https://defuse.ca. * @@ -42,7 +42,7 @@ class CryptTest extends TestCase public function testEncryptAndDecryptWithKnownKey(): void { - $key = new SymmetricKey(self::TEST_KEY); + $key = new SharedKey(self::TEST_KEY); $encrypted = Crypt::encrypt(self::DECRYPTABLE_DATA, $key); self::assertSame(self::DECRYPTABLE_DATA, Crypt::decrypt($encrypted, $key)); @@ -50,7 +50,7 @@ public function testEncryptAndDecryptWithKnownKey(): void public function testEncryptAndDecryptWithRandomKey(): void { - $key = new SymmetricKey(); + $key = new SharedKey(); $encrypted = Crypt::encrypt(self::DECRYPTABLE_DATA, $key); self::assertSame(self::DECRYPTABLE_DATA, Crypt::decrypt($encrypted, $key)); @@ -58,7 +58,7 @@ public function testEncryptAndDecryptWithRandomKey(): void public function testTryDecryptingCorruptData(): void { - $key = new SymmetricKey(); + $key = new SharedKey(); $this->expectException(DecryptionException::class); @@ -67,8 +67,8 @@ public function testTryDecryptingCorruptData(): void public function testSwapKnownKeys(): void { - $oldKey = new SymmetricKey(self::TEST_KEY); - $newKey = new SymmetricKey(self::NEW_KEY); + $oldKey = new SharedKey(self::TEST_KEY); + $newKey = new SharedKey(self::NEW_KEY); $encrypted = Crypt::encrypt(self::DECRYPTABLE_DATA, $oldKey); self::assertSame(self::DECRYPTABLE_DATA, Crypt::decrypt($encrypted, $oldKey)); @@ -80,8 +80,8 @@ public function testSwapKnownKeys(): void public function testSwapRandomKeys(): void { - $oldKey = new SymmetricKey(); - $newKey = new SymmetricKey(); + $oldKey = new SharedKey(); + $newKey = new SharedKey(); $encrypted = Crypt::encrypt(self::DECRYPTABLE_DATA, $oldKey); self::assertSame(self::DECRYPTABLE_DATA, Crypt::decrypt($encrypted, $oldKey)); diff --git a/tests/OsstTest.php b/tests/OsstTest.php index 4d6edf3..6059d8e 100644 --- a/tests/OsstTest.php +++ b/tests/OsstTest.php @@ -11,7 +11,7 @@ /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens - * Copyright © 2021 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/tests/PasswordTest.php b/tests/PasswordTest.php index 99ac814..cca5619 100644 --- a/tests/PasswordTest.php +++ b/tests/PasswordTest.php @@ -4,13 +4,13 @@ use Oire\Iridium\Crypt; use Oire\Iridium\Exception\PasswordException; -use Oire\Iridium\Key\SymmetricKey; +use Oire\Iridium\Key\SharedKey; use Oire\Iridium\Password; use PHPUnit\Framework\TestCase; /** * Iridium, a security library for hashing passwords, encrypting data and managing secure tokens - * Copyright © 2021, Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire + * Copyright © 2021-2022 Andre Polykanine also known as Menelion Elensúlë, https://github.com/Oire * Copyright © 2016 Scott Arciszewski, Paragon Initiative Enterprises, https://paragonie.com. * Portions copyright © 2016 Taylor Hornby, Defuse Security Research and Development, https://defuse.ca. * @@ -45,7 +45,7 @@ class PasswordTest extends TestCase public function testLockWithKnownKey(): void { - $key = new SymmetricKey(self::TEST_KEY); + $key = new SharedKey(self::TEST_KEY); $locked = Password::lock(self::CORRECT_PASSWORD, $key); self::assertTrue(Password::check(self::CORRECT_PASSWORD, $locked, $key)); @@ -54,7 +54,7 @@ public function testLockWithKnownKey(): void public function testLockWithRandomKey(): void { - $key = new SymmetricKey(); + $key = new SharedKey(); $locked = Password::lock(self::CORRECT_PASSWORD, $key); self::assertTrue(Password::check(self::CORRECT_PASSWORD, $locked, $key)); @@ -63,8 +63,8 @@ public function testLockWithRandomKey(): void public function testSwapKeys(): void { - $oldKey = new SymmetricKey(self::TEST_KEY); - $newKey = new SymmetricKey(self::NEW_KEY); + $oldKey = new SharedKey(self::TEST_KEY); + $newKey = new SharedKey(self::NEW_KEY); $locked = Password::lock(self::CORRECT_PASSWORD, $oldKey); self::assertTrue(Password::check(self::CORRECT_PASSWORD, $locked, $oldKey)); @@ -76,8 +76,8 @@ public function testSwapKeys(): void public function testTryDecryptWithWrongKey(): void { - $key = new SymmetricKey(); - $wrongKey = new SymmetricKey(); + $key = new SharedKey(); + $wrongKey = new SharedKey(); $locked = Password::lock(self::CORRECT_PASSWORD, $key); $this->expectException(PasswordException::class); diff --git a/tests/SharedKeyTest.php b/tests/SharedKeyTest.php new file mode 100644 index 0000000..021d585 --- /dev/null +++ b/tests/SharedKeyTest.php @@ -0,0 +1,72 @@ +getKey()); + self::assertSame(SharedKey::KEY_SIZE, mb_strlen($sharedKey->getRawKey(), Crypt::STRING_ENCODING_8BIT)); + } + + public function testDeriveKeys(): void + { + $sharedKey = new SharedKey(); + $derivedKeys = $sharedKey->deriveKeys(); + + self::assertInstanceOf(DerivedKeys::class, $derivedKeys); + + $key = $sharedKey->getKey(); + $salt = $derivedKeys->getSalt(); + + self::assertTrue($derivedKeys->areValid()); + self::assertSame(DerivedKeys::SALT_SIZE, mb_strlen($salt, Crypt::STRING_ENCODING_8BIT)); + + $derivedKeys = (new SharedKey($key))->deriveKeys($salt); + + self::assertTrue($derivedKeys->areValid()); + self::assertSame($salt, $derivedKeys->getSalt()); + } + + public function testTrySetInvalidKey(): void + { + $this->expectException(SharedKeyException::class); + + new SharedKey('abc'); + } +} diff --git a/tests/SymmetricKeyTest.php b/tests/SymmetricKeyTest.php index 8323f58..8e923d3 100644 --- a/tests/SymmetricKeyTest.php +++ b/tests/SymmetricKeyTest.php @@ -36,6 +36,7 @@ class SymmetricKeyTest extends TestCase // Oire\Iridium\Base64::encode(hex2bin('000102030405060708090a0b0c0d0e0f')); private const TEST_KEY = 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8'; + /** @psalm-suppress DeprecatedClass */ public function testSetKnownKey(): void { $symmetricKey = new SymmetricKey(self::TEST_KEY); @@ -44,6 +45,7 @@ public function testSetKnownKey(): void self::assertSame(SymmetricKey::KEY_SIZE, mb_strlen($symmetricKey->getRawKey(), Crypt::STRING_ENCODING_8BIT)); } + /** @psalm-suppress DeprecatedClass */ public function testDeriveKeys(): void { $symmetricKey = new SymmetricKey(); @@ -63,6 +65,7 @@ public function testDeriveKeys(): void self::assertSame($salt, $derivedKeys->getSalt()); } + /** @psalm-suppress DeprecatedClass */ public function testTrySetInvalidKey(): void { $this->expectException(SymmetricKeyException::class);