/** * @expectedException \Defuse\Crypto\Exception\BadFormatException * @expectedExceptionMessage not a hex string */ public function testBadHexEncoding() { $header = Core::secureRandom(Core::HEADER_VERSION_SIZE); $str = Encoding::saveBytesToChecksummedAsciiSafeString($header, Core::secureRandom(Core::KEY_BYTE_SIZE)); $str[0] = 'Z'; Encoding::loadBytesFromChecksummedAsciiSafeString($header, $str); }
/** * 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.'); } }
/** * INTERNAL USE ONLY: Decodes, verifies the header and checksum, and returns * the encoded byte string. * * @param string $expected_header * @param string $string * * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException * @throws \Defuse\Crypto\Exception\BadFormatException * * @return string */ public static function loadBytesFromChecksummedAsciiSafeString($expected_header, $string) { // Headers must be a constant length to prevent one type's header from // being a prefix of another type's header, leading to ambiguity. if (Core::ourStrlen($expected_header) !== self::SERIALIZE_HEADER_BYTES) { throw new Ex\EnvironmentIsBrokenException('Header must be 4 bytes.'); } $bytes = Encoding::hexToBin($string); /* Make sure we have enough bytes to get the version header and checksum. */ if (Core::ourStrlen($bytes) < self::SERIALIZE_HEADER_BYTES + self::CHECKSUM_BYTE_SIZE) { throw new Ex\BadFormatException('Encoded data is shorter than expected.'); } /* Grab the version header. */ $actual_header = Core::ourSubstr($bytes, 0, self::SERIALIZE_HEADER_BYTES); if ($actual_header !== $expected_header) { throw new Ex\BadFormatException('Invalid header.'); } /* Grab the bytes that are part of the checksum. */ $checked_bytes = Core::ourSubstr($bytes, 0, Core::ourStrlen($bytes) - self::CHECKSUM_BYTE_SIZE); /* Grab the included checksum. */ $checksum_a = Core::ourSubstr($bytes, Core::ourStrlen($bytes) - self::CHECKSUM_BYTE_SIZE, self::CHECKSUM_BYTE_SIZE); /* Re-compute the checksum. */ $checksum_b = \hash(self::CHECKSUM_HASH_ALGO, $checked_bytes, true); /* Check if the checksum matches. */ if (!Core::hashEquals($checksum_a, $checksum_b)) { throw new Ex\BadFormatException("Data is corrupted, the checksum doesn't match"); } return Core::ourSubstr($bytes, self::SERIALIZE_HEADER_BYTES, Core::ourStrlen($bytes) - self::SERIALIZE_HEADER_BYTES - self::CHECKSUM_BYTE_SIZE); }
/** * Test AES against test vectors. * * @throws Ex\EnvironmentIsBrokenException */ private static function AESTestVector() { // AES CTR mode test vector from NIST SP 800-38A $key = Encoding::hexToBin('603deb1015ca71be2b73aef0857d7781' . '1f352c073b6108d72d9810a30914dff4'); $iv = Encoding::hexToBin('f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'); $plaintext = Encoding::hexToBin('6bc1bee22e409f96e93d7e117393172a' . 'ae2d8a571e03ac9c9eb76fac45af8e51' . '30c81c46a35ce411e5fbc1191a0a52ef' . 'f69f2445df4f9b17ad2b417be66c3710'); $ciphertext = Encoding::hexToBin('601ec313775789a5b7a7f504bbf3d228' . 'f443e3ca4d62b59aca84e990cacaf5c5' . '2b0930daa23de94ce87017ba2d84988d' . 'dfc9c58db67aada613c2dd08457941a6'); $computed_ciphertext = Crypto::plainEncrypt($plaintext, $key, $iv); if ($computed_ciphertext !== $ciphertext) { echo \str_repeat("\n", 30); echo \bin2hex($computed_ciphertext); echo "\n---\n"; echo \bin2hex($ciphertext); echo \str_repeat("\n", 30); throw new Ex\EnvironmentIsBrokenException(); } $computed_plaintext = Crypto::plainDecrypt($ciphertext, $key, $iv, Core::CIPHER_METHOD); if ($computed_plaintext !== $plaintext) { throw new Ex\EnvironmentIsBrokenException(); } }
/** * Decrypts a ciphertext. * $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 * @param boolean $raw_binary * @return string * @throws Ex\CannotPerformOperationException * @throws Ex\CryptoTestFailedException * @throws Ex\InvalidCiphertextException */ public static function decrypt($ciphertext, $key, $raw_binary = false) { RuntimeTests::runtimeTest(); /* Attempt to validate that the key was generated safely. */ if (!is_a($key, "\\Defuse\\Crypto\\Key")) { throw new Ex\CannotPerformOperationException("The given key is not a valid Key object."); } $key = $key->getRawBytes(); if (!$raw_binary) { try { $ciphertext = Encoding::hexToBin($ciphertext); } catch (\RangeException $ex) { throw new Ex\InvalidCiphertextException("Ciphertext has invalid hex encoding."); } } // Grab the header tag $version = Core::ourSubstr($ciphertext, 0, Core::HEADER_VERSION_SIZE); // Load the configuration for this version $config = self::getVersionConfigFromHeader($version, Core::CURRENT_VERSION); // Now let's operate on the remainder of the ciphertext as normal $ciphertext = Core::ourSubstr($ciphertext, Core::HEADER_VERSION_SIZE, null); // 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(); } $salt = Core::ourSubstr($ciphertext, $config->macByteSize(), $config->saltByteSize()); if ($salt === false) { throw new Ex\CannotPerformOperationException(); } $ciphertext = Core::ourSubstr($ciphertext, $config->macByteSize() + $config->saltByteSize()); if ($ciphertext === false) { throw new Ex\CannotPerformOperationException(); } // Regenerate the same authentication sub-key. $akey = Core::HKDF($config->hashFunctionName(), $key, $config->keyByteSize(), $config->authenticationInfoString(), $salt, $config); if (self::verifyHMAC($hmac, $version . $salt . $ciphertext, $akey, $config)) { // Regenerate the same encryption sub-key. $ekey = Core::HKDF($config->hashFunctionName(), $key, $config->keyByteSize(), $config->encryptionInfoString(), $salt, $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."); } }
public function saveToAsciiSafeString() { return Encoding::binToHex($this->key_version_header . $this->key_bytes . hash($this->config->checksumHashFunction(), $this->key_version_header . $this->key_bytes, true)); }
/** * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException */ public function testDecryptLegacyCiphertextWrongKey() { $cipher = Encoding::hexToBin('cfdad83ebd506d2c9ada8d48030d0bca' . '2ff94760e6d39c186adb1290d6c47e35' . '821e262673c5631c42ebbaf70774d6ef' . '29aa5eee0e412d646ae380e08189c85d' . '024b5e2009106870f1db25d8b85fd01f'); $plain = Crypto::legacyDecrypt($cipher, "\t\n\v\f\r"); }
/** * @expectedException \Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException * @expectedExceptionMessage Bad version header */ public function testDecryptLegacyWithWrongMethodStraightUpBinary() { $cipher = Encoding::hexToBin('cfdad83ebd506d2c9ada8d48030d0bca' . '2ff94760e6d39c186adb1290d6c47e35' . '821e262673c5631c42ebbaf70774d6ef' . '29aa5eee0e412d646ae380e08189c85d' . '024b5e2009106870f1db25d8b85fd01f' . '00000000000000000000000000000000'); /* This time, treat the binary as binary. */ $plain = Crypto::decrypt($cipher, Key::loadFromRawBytesForTestingPurposesOnlyInsecure("\t\n\v\f\r" . "\t\n\v\f\r"), true); }