From 20b1b0352b9272e4bfe1fb4c54b97bfc9618379e Mon Sep 17 00:00:00 2001 From: netkas Date: Fri, 13 Sep 2024 13:48:08 -0400 Subject: [PATCH] Add Cryptography class for key management, signing, encryption --- src/Socialbox/Classes/Cryptography.php | 243 +++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 src/Socialbox/Classes/Cryptography.php diff --git a/src/Socialbox/Classes/Cryptography.php b/src/Socialbox/Classes/Cryptography.php new file mode 100644 index 0000000..20d242f --- /dev/null +++ b/src/Socialbox/Classes/Cryptography.php @@ -0,0 +1,243 @@ + self::ALGORITHM, + "private_key_bits" => self::KEY_SIZE, + ]; + + $res = openssl_pkey_new($config); + if (!$res) + { + throw new CryptographyException('Failed to generate private key: ' . openssl_error_string()); + } + + openssl_pkey_export($res, $privateKeyPem); + $publicKeyPem = openssl_pkey_get_details($res)['key']; + + return new KeyPair( + Utilities::base64encode(self::pemToDer($publicKeyPem)), + Utilities::base64encode(self::pemToDer($privateKeyPem)) + ); + } + + /** + * Converts a PEM formatted key to DER format. + * + * @param string $pemKey The PEM formatted key as a string. + * + * @return string The DER formatted key as a binary string. + */ + private static function pemToDer(string $pemKey): string + { + $pemKey = preg_replace('/-----(BEGIN|END) [A-Z ]+-----/', '', $pemKey); + return base64_decode(str_replace(["\n", "\r", " "], '', $pemKey)); + } + + /** + * Converts a DER formatted key to PEM format. + * + * @param string $derKey The DER formatted key. + * @param string $type The type of key, either private or public. Default is private. + * @return string The PEM formatted key. + */ + private static function derToPem(string $derKey, string $type): string + { + $formattedKey = chunk_split(base64_encode($derKey), 64); + $headerFooter = strtoupper($type) === self::PEM_PUBLIC_HEADER + ? "PUBLIC KEY" : "PRIVATE KEY"; + + return "-----BEGIN $headerFooter-----\n$formattedKey-----END $headerFooter-----\n"; + } + + /** + * Signs the given content using the provided private key. + * + * @param string $content The content to be signed. + * @param string $privateKey The private key used to sign the content. + * @return string The Base64 encoded signature of the content. + * @throws CryptographyException If the private key is invalid or if the content signing fails. + */ + public static function signContent(string $content, string $privateKey): string + { + $privateKey = openssl_pkey_get_private(self::derToPem(Utilities::base64decode($privateKey), self::PEM_PRIVATE_HEADER)); + if (!$privateKey) + { + throw new CryptographyException('Invalid private key: ' . openssl_error_string()); + } + + if (!openssl_sign($content, $signature, $privateKey, self::HASH_ALGORITHM)) + { + throw new CryptographyException('Failed to sign content: ' . openssl_error_string()); + } + + return base64_encode($signature); + } + + /** + * Verifies the integrity of the given content using the provided digital signature and public key. + * + * @param string $content The content to be verified. + * @param string $signature The digital signature to verify against. + * @param string $publicKey The public key to use for verification. + * @return bool Returns true if the content verification is successful, false otherwise. + * @throws CryptographyException If the public key is invalid or if the signature verification fails. + */ + public static function verifyContent(string $content, string $signature, string $publicKey): bool + { + $publicKey = openssl_pkey_get_public(self::derToPem(Utilities::base64decode($publicKey), self::PEM_PUBLIC_HEADER)); + + if (!$publicKey) + { + throw new CryptographyException('Invalid public key: ' . openssl_error_string()); + } + + return openssl_verify($content, base64_decode($signature), $publicKey, self::HASH_ALGORITHM) === 1; + } + + /** + * Temporarily signs the provided content by appending a timestamp-based value and signing it. + * + * @param string $content The content to be signed. + * @param string $privateKey The private key used to sign the content. + * @return string The base64 encoded signature of the content with the appended timestamp. + * @throws CryptographyException If the private key is invalid or if the content signing fails. + */ + public static function temporarySignContent(string $content, string $privateKey): string + { + return self::signContent(sprintf('%s|%d', $content, time() / self::TIME_BLOCK), $privateKey); + } + + /** + * Verify the provided temporary signature for the given content using the public key. + * + * @param string $content The content whose signature needs to be verified. + * @param string $signature The signature associated with the content. + * @param string $publicKey The public key to be used for verifying the signature. + * @param int $frames The number of time frames to consider for validating the signature (default is 1). + * @return bool Returns true if the signature is valid within the provided time frames, otherwise false. + * @throws CryptographyException If the public key is invalid or the signature verification fails. + */ + public static function verifyTemporarySignature(string $content, string $signature, string $publicKey, int $frames = 1): bool + { + $currentTime = time() / self::TIME_BLOCK; + for ($i = 0; $i < max(1, $frames); $i++) + { + if (self::verifyContent(sprintf('%s|%d', $content, $currentTime - $i), $signature, $publicKey)) + { + return true; + } + } + return false; + } + + /** + * Encrypts the given content using the provided public key. + * + * @param string $content The content to be encrypted. + * @param string $publicKey The public key used for encryption, in DER-encoded format. + * @return string The encrypted content, encoded in base64 format. + * @throws CryptographyException If the public key is invalid or the encryption fails. + */ + public static function encryptContent(string $content, string $publicKey): string + { + $publicKey = openssl_pkey_get_public(self::derToPem(Utilities::base64decode($publicKey), self::PEM_PUBLIC_HEADER)); + if (!$publicKey) + { + throw new CryptographyException('Invalid public key: ' . openssl_error_string()); + } + + if (!openssl_public_encrypt($content, $encrypted, $publicKey, self::PADDING)) + { + throw new CryptographyException('Failed to encrypt content: ' . openssl_error_string()); + } + + return base64_encode($encrypted); + } + + /** + * Decrypts the provided content using the specified private key. + * + * @param string $content The content to be decrypted, encoded in base64. + * @param string $privateKey The private key for decryption, encoded in base64. + * @return string The decrypted content, encoded in UTF-8. + * @throws CryptographyException If the private key is invalid or the decryption fails. + */ + public static function decryptContent(string $content, string $privateKey): string + { + $privateKey = openssl_pkey_get_private(self::derToPem(Utilities::base64decode($privateKey), self::PEM_PRIVATE_HEADER)); + + if (!$privateKey) + { + throw new CryptographyException('Invalid private key: ' . openssl_error_string()); + } + + if (!openssl_private_decrypt(base64_decode($content), $decrypted, $privateKey, self::PADDING)) + { + throw new CryptographyException('Failed to decrypt content: ' . openssl_error_string()); + } + + return mb_convert_encoding($decrypted, 'UTF-8', 'auto'); + } + + public static function validatePublicKey(string $publicKey): bool + { + try + { + $result = openssl_pkey_get_public(self::derToPem(Utilities::base64decode($publicKey), self::PEM_PUBLIC_HEADER)); + } + catch(InvalidArgumentException) + { + return false; + } + + if($result === false) + { + return false; + } + + return true; + } + + public static function validatePrivateKey(string $privateKey): bool + { + try + { + $result = openssl_pkey_get_private(self::derToPem(Utilities::base64decode($privateKey), self::PEM_PRIVATE_HEADER)); + } + catch(InvalidArgumentException) + { + return false; + } + + if($result === false) + { + return false; + } + + return true; + } +} \ No newline at end of file