public function testOurSubstrOutOfBorders() { // See: https://secure.php.net/manual/en/function.mb-substr.php#50275 // We want to be like substr, so confirm that behavior. $this->assertSame(false, substr('abc', 5, 2)); // Confirm that mb_substr does not have that behavior. if (function_exists('mb_substr')) { if (ini_get('mbstring.func_overload') == 0) { $this->assertSame('', \mb_substr('abc', 5, 2)); } else { $this->assertSame(false, \mb_substr('abc', 5, 2)); } // YES, THE BEHAVIOR OF mb_substr IS REALLY THIS INSANE!!!! } // Check if we actually have that behavior. $this->assertSame(false, Core::ourSubstr('abc', 5, 2)); }
/** * 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); }
/** * Write to a stream; prevent partial writes * * @param resource $stream * @param string $buf * @param int $num (number of bytes) * @return string * @throws Ex\CannotPerformOperationException */ public static final function writeBytes($stream, $buf, $num = null) { $bufSize = Core::ourStrlen($buf); if ($num === null) { $num = $bufSize; } if ($num > $bufSize) { throw new Ex\CannotPerformOperationException('Trying to write more bytes than the buffer contains.'); } if ($num < 0) { throw new Ex\CannotPerformOperationException('Tried to write less than 0 bytes'); } $remaining = $num; while ($remaining > 0) { $written = \fwrite($stream, $buf, $remaining); if ($written === false) { throw new Ex\CannotPerformOperationException('Could not write to the file'); } $buf = Core::ourSubstr($buf, $written, null); $remaining -= $written; } return $num; }
/** * Get the encryption configuration based on the version in a header. * * @param string $header The header to read the version number from. * @param string $min_ver_header The header of the minimum version number allowed. * @return array * @throws Ex\InvalidCiphertextException */ public static function getVersionConfigFromHeader($header, $min_ver_header) { if (Core::ourSubstr($header, 0, 2) !== Core::ourSubstr(Core::HEADER_MAGIC, 0, 2)) { throw new Ex\InvalidCiphertextException("Ciphertext has a bad magic number."); } $major = \ord($header[2]); $minor = \ord($header[3]); $min_major = \ord($min_ver_header[2]); $min_minor = \ord($min_ver_header[3]); if ($major < $min_major || $major === $min_major && $minor < $min_minor) { throw new Ex\InvalidCiphertextException("Ciphertext is requesting an insecure fallback."); } $config = self::getVersionConfigFromMajorMinor($major, $minor); return $config; }
public static function LoadFromAsciiSafeString($savedKeyString) { try { $bytes = Encoding::hexToBin($savedKeyString); } catch (\RangeException $ex) { throw new Ex\CannotPerformOperationException("Key has invalid hex encoding."); } /* Make sure we have enough bytes to get the version header. */ if (Core::ourStrlen($bytes) < self::KEY_HEADER_SIZE) { throw new Ex\CannotPerformOperationException("Saved Key is shorter than the version header."); } /* Grab the version header. */ $version_header = Core::ourSubstr($bytes, 0, self::KEY_HEADER_SIZE); /* Grab the config for that version. */ $config = self::GetKeyVersionConfigFromKeyHeader($version_header); /* Now that we know the version, check the length is correct. */ if (Core::ourStrlen($bytes) !== self::KEY_HEADER_SIZE + $config->keyByteSize() + $config->checksumByteSize()) { throw new Ex\CannotPerformOperationException("Saved Key is not the correct size."); } /* Grab the bytes that are part of the checksum. */ $checked_bytes = Core::ourSubstr($bytes, 0, self::KEY_HEADER_SIZE + $config->keyByteSize()); /* Grab the included checksum. */ $checksum_a = Core::ourSubstr($bytes, self::KEY_HEADER_SIZE + $config->keyByteSize(), $config->checksumByteSize()); /* Re-compute the checksum. */ $checksum_b = hash($config->checksumHashFunction(), $checked_bytes, true); /* Validate it. It *is* important for this to be constant time. */ if (!Core::hashEquals($checksum_a, $checksum_b)) { throw new Ex\CannotPerformOperationException("Saved key is corrupted -- checksums don't match."); } /* Everything checks out. Grab the key and create a Key object. */ $key_bytes = Core::ourSubstr($bytes, self::KEY_HEADER_SIZE, $config->keyByteSize()); return new Key($version_header, $key_bytes); }