/** * Runs the runtime tests. * * @throws Ex\EnvironmentIsBrokenException */ public static function runtimeTest() { // 0: Tests haven't been run yet. // 1: Tests have passed. // 2: Tests are running right now. // 3: Tests have failed. static $test_state = 0; if ($test_state === 1 || $test_state === 2) { return; } if ($test_state === 3) { /* If an intermittent problem caused a test to fail previously, we * want that to be indicated to the user with every call to this * library. This way, if the user first does something they really * don't care about, and just ignores all exceptions, they won't get * screwed when they then start to use the library for something * they do care about. */ throw new Ex\EnvironmentIsBrokenException('Tests failed previously.'); } try { $test_state = 2; Core::ensureFunctionExists('openssl_get_cipher_methods'); if (\in_array(Core::CIPHER_METHOD, \openssl_get_cipher_methods()) === false) { throw new Ex\EnvironmentIsBrokenException('Cipher method not supported. This is normally caused by an outdated ' . 'version of OpenSSL (and/or OpenSSL compiled for FIPS compliance). ' . 'Please upgrade to a newer version of OpenSSL that supports ' . Core::CIPHER_METHOD . ' to use this library.'); } RuntimeTests::AESTestVector(); RuntimeTests::HMACTestVector(); RuntimeTests::HKDFTestVector(); RuntimeTests::testEncryptDecrypt(); if (Core::ourStrlen(Key::createNewRandomKey()->getRawBytes()) != Core::KEY_BYTE_SIZE) { throw new Ex\EnvironmentIsBrokenException(); } if (Core::ENCRYPTION_INFO_STRING == Core::AUTHENTICATION_INFO_STRING) { throw new Ex\EnvironmentIsBrokenException(); } } catch (Ex\EnvironmentIsBrokenException $ex) { // Do this, otherwise it will stay in the "tests are running" state. $test_state = 3; throw $ex; } // Change this to '0' make the tests always re-run (for benchmarking). $test_state = 1; }
/** * Decrypts a ciphertext to a string with either a key or a password. * * @param string $ciphertext * @param KeyOrPassword $secret * @param bool $raw_binary * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\WrongKeyOrModifiedCiphertextException * * @return string */ private static function decryptInternal($ciphertext, KeyOrPassword $secret, $raw_binary) { RuntimeTests::runtimeTest(); if (!$raw_binary) { try { $ciphertext = Encoding::hexToBin($ciphertext); } catch (Ex\BadFormatException $ex) { throw new Ex\WrongKeyOrModifiedCiphertextException('Ciphertext has invalid hex encoding.'); } } if (Core::ourStrlen($ciphertext) < Core::MINIMUM_CIPHERTEXT_SIZE) { throw new Ex\WrongKeyOrModifiedCiphertextException('Ciphertext is too short.'); } // Get and check the version header. $header = Core::ourSubstr($ciphertext, 0, Core::HEADER_VERSION_SIZE); if ($header !== Core::CURRENT_VERSION) { throw new Ex\WrongKeyOrModifiedCiphertextException('Bad version header.'); } // Get the salt. $salt = Core::ourSubstr($ciphertext, Core::HEADER_VERSION_SIZE, Core::SALT_BYTE_SIZE); if ($salt === false) { throw new Ex\EnvironmentIsBrokenException(); } // Get the IV. $iv = Core::ourSubstr($ciphertext, Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE, Core::BLOCK_BYTE_SIZE); if ($iv === false) { throw new Ex\EnvironmentIsBrokenException(); } // Get the HMAC. $hmac = Core::ourSubstr($ciphertext, Core::ourStrlen($ciphertext) - Core::MAC_BYTE_SIZE, Core::MAC_BYTE_SIZE); if ($hmac === false) { throw new Ex\EnvironmentIsBrokenException(); } // Get the actual encrypted ciphertext. $encrypted = Core::ourSubstr($ciphertext, Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + Core::BLOCK_BYTE_SIZE, Core::ourStrlen($ciphertext) - Core::MAC_BYTE_SIZE - Core::SALT_BYTE_SIZE - Core::BLOCK_BYTE_SIZE - Core::HEADER_VERSION_SIZE); if ($encrypted === false) { throw new Ex\EnvironmentIsBrokenException(); } // Derive the separate encryption and authentication keys from the key // or password, whichever it is. $keys = $secret->deriveKeys($salt); if (self::verifyHMAC($hmac, $header . $salt . $iv . $encrypted, $keys->getAuthenticationKey())) { $plaintext = self::plainDecrypt($encrypted, $keys->getEncryptionKey(), $iv, Core::CIPHER_METHOD); return $plaintext; } else { throw new Ex\WrongKeyOrModifiedCiphertextException('Integrity check failed.'); } }
public function testRuntimeTest() { RuntimeTests::runtimeTest(); }
/** * Decrypts a ciphertext (legacy -- before version tagging) * * $ciphertext is the ciphertext to decrypt. * $key is the key that the ciphertext was encrypted with. * You MUST catch exceptions thrown by this function. Read the docs. * * @param string $ciphertext * @param string $key * @return string * @throws Ex\CannotPerformOperationException * @throws Ex\CryptoTestFailedException * @throws Ex\InvalidCiphertextException */ public static function legacyDecrypt($ciphertext, $key) { RuntimeTests::runtimeTest(); $config = self::getVersionConfigFromHeader(Core::LEGACY_VERSION, Core::LEGACY_VERSION); // Extract the HMAC from the front of the ciphertext. if (Core::ourStrlen($ciphertext) <= $config->macByteSize()) { throw new Ex\InvalidCiphertextException("Ciphertext is too short."); } $hmac = Core::ourSubstr($ciphertext, 0, $config->macByteSize()); if ($hmac === false) { throw new Ex\CannotPerformOperationException(); } $ciphertext = Core::ourSubstr($ciphertext, $config->macByteSize()); if ($ciphertext === false) { throw new Ex\CannotPerformOperationException(); } // Regenerate the same authentication sub-key. $akey = Core::HKDF($config->hashFunctionName(), $key, $config->keyByteSize(), $config->authenticationInfoString(), null, $config); if (self::verifyHMAC($hmac, $ciphertext, $akey, $config)) { // Regenerate the same encryption sub-key. $ekey = Core::HKDF($config->hashFunctionName(), $key, $config->keyByteSize(), $config->encryptionInfoString(), null, $config); // Extract the initialization vector from the ciphertext. Core::EnsureFunctionExists("openssl_cipher_iv_length"); $ivsize = \openssl_cipher_iv_length($config->cipherMethod()); if ($ivsize === false || $ivsize <= 0) { throw new Ex\CannotPerformOperationException("Could not get the IV length from OpenSSL"); } if (Core::ourStrlen($ciphertext) <= $ivsize) { throw new Ex\InvalidCiphertextException("Ciphertext is too short."); } $iv = Core::ourSubstr($ciphertext, 0, $ivsize); if ($iv === false) { throw new Ex\CannotPerformOperationException(); } $ciphertext = Core::ourSubstr($ciphertext, $ivsize); if ($ciphertext === false) { throw new Ex\CannotPerformOperationException(); } $plaintext = self::plainDecrypt($ciphertext, $ekey, $iv, $config); return $plaintext; } else { /* * We throw an exception instead of returning false because we want * a script that doesn't handle this condition to CRASH, instead * of thinking the ciphertext decrypted to the value false. */ throw new Ex\InvalidCiphertextException("Integrity check failed."); } }