Файловый менеджер - Редактировать - /home/wuectly/www/03cbe/Crypto.tar
Назад
KeyProtectedByPassword.php 0000604 00000006747 15245700605 0011713 0 ustar 00 <?php namespace Defuse\Crypto; use Defuse\Crypto\Exception as Ex; final class KeyProtectedByPassword { const PASSWORD_KEY_CURRENT_VERSION = "\xDE\xF1\x00\x00"; /** * @var string */ private $encrypted_key = ''; /** * Creates a random key protected by the provided password. * * @param string $password * * @throws Ex\EnvironmentIsBrokenException * * @return KeyProtectedByPassword */ public static function createRandomPasswordProtectedKey($password) { $inner_key = Key::createNewRandomKey(); /* The password is hashed as a form of poor-man's domain separation * between this use of encryptWithPassword() and other uses of * encryptWithPassword() that the user may also be using as part of the * same protocol. */ $encrypted_key = Crypto::encryptWithPassword( $inner_key->saveToAsciiSafeString(), \hash(Core::HASH_FUNCTION_NAME, $password, true), true ); return new KeyProtectedByPassword($encrypted_key); } /** * Loads a KeyProtectedByPassword from its encoded form. * * @param string $saved_key_string * * @throws Ex\BadFormatException * * @return KeyProtectedByPassword */ public static function loadFromAsciiSafeString($saved_key_string) { $encrypted_key = Encoding::loadBytesFromChecksummedAsciiSafeString( self::PASSWORD_KEY_CURRENT_VERSION, $saved_key_string ); return new KeyProtectedByPassword($encrypted_key); } /** * Encodes the KeyProtectedByPassword into a string of printable ASCII * characters. * * @throws Ex\EnvironmentIsBrokenException * * @return string */ public function saveToAsciiSafeString() { return Encoding::saveBytesToChecksummedAsciiSafeString( self::PASSWORD_KEY_CURRENT_VERSION, $this->encrypted_key ); } /** * Decrypts the protected key, returning an unprotected Key object that can * be used for encryption and decryption. * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\WrongKeyOrModifiedCiphertextException * * @param string $password * @return Key */ public function unlockKey($password) { try { $inner_key_encoded = Crypto::decryptWithPassword( $this->encrypted_key, \hash(Core::HASH_FUNCTION_NAME, $password, true), true ); return Key::loadFromAsciiSafeString($inner_key_encoded); } catch (Ex\BadFormatException $ex) { /* This should never happen unless an attacker replaced the * encrypted key ciphertext with some other ciphertext that was * encrypted with the same password. We transform the exception type * here in order to make the API simpler, avoiding the need to * document that this method might throw an Ex\BadFormatException. */ throw new Ex\WrongKeyOrModifiedCiphertextException( "The decrypted key was found to be in an invalid format. " . "This very likely indicates it was modified by an attacker." ); } } /** * Constructor for KeyProtectedByPassword. * * @param string $encrypted_key */ private function __construct($encrypted_key) { $this->encrypted_key = $encrypted_key; } } DerivedKeys.php 0000604 00000001413 15245700605 0007472 0 ustar 00 <?php namespace Defuse\Crypto; /** * Class DerivedKeys * @package Defuse\Crypto */ final class DerivedKeys { /** * @var string */ private $akey = ''; /** * @var string */ private $ekey = ''; /** * Returns the authentication key. * @return string */ public function getAuthenticationKey() { return $this->akey; } /** * Returns the encryption key. * @return string */ public function getEncryptionKey() { return $this->ekey; } /** * Constructor for DerivedKeys. * * @param string $akey * @param string $ekey */ public function __construct($akey, $ekey) { $this->akey = $akey; $this->ekey = $ekey; } } Core.php 0000604 00000035060 15245700605 0006151 0 ustar 00 <?php namespace Defuse\Crypto; use Defuse\Crypto\Exception as Ex; final class Core { const HEADER_VERSION_SIZE = 4; const MINIMUM_CIPHERTEXT_SIZE = 84; const CURRENT_VERSION = "\xDE\xF5\x02\x00"; const CIPHER_METHOD = 'aes-256-ctr'; const BLOCK_BYTE_SIZE = 16; const KEY_BYTE_SIZE = 32; const SALT_BYTE_SIZE = 32; const MAC_BYTE_SIZE = 32; const HASH_FUNCTION_NAME = 'sha256'; const ENCRYPTION_INFO_STRING = 'DefusePHP|V2|KeyForEncryption'; const AUTHENTICATION_INFO_STRING = 'DefusePHP|V2|KeyForAuthentication'; const BUFFER_BYTE_SIZE = 1048576; const LEGACY_CIPHER_METHOD = 'aes-128-cbc'; const LEGACY_BLOCK_BYTE_SIZE = 16; const LEGACY_KEY_BYTE_SIZE = 16; const LEGACY_HASH_FUNCTION_NAME = 'sha256'; const LEGACY_MAC_BYTE_SIZE = 32; const LEGACY_ENCRYPTION_INFO_STRING = 'DefusePHP|KeyForEncryption'; const LEGACY_AUTHENTICATION_INFO_STRING = 'DefusePHP|KeyForAuthentication'; /* * V2.0 Format: VERSION (4 bytes) || SALT (32 bytes) || IV (16 bytes) || * CIPHERTEXT (varies) || HMAC (32 bytes) * * V1.0 Format: HMAC (32 bytes) || IV (16 bytes) || CIPHERTEXT (varies). */ /** * Adds an integer to a block-sized counter. * * @param string $ctr * @param int $inc * * @throws Ex\EnvironmentIsBrokenException * * @return string */ public static function incrementCounter($ctr, $inc) { if (Core::ourStrlen($ctr) !== Core::BLOCK_BYTE_SIZE) { throw new Ex\EnvironmentIsBrokenException( 'Trying to increment a nonce of the wrong size.' ); } if (! \is_int($inc)) { throw new Ex\EnvironmentIsBrokenException( 'Trying to increment nonce by a non-integer.' ); } if ($inc < 0) { throw new Ex\EnvironmentIsBrokenException( 'Trying to increment nonce by a negative amount.' ); } if ($inc > PHP_INT_MAX - 255) { throw new Ex\EnvironmentIsBrokenException( 'Integer overflow may occur.' ); } /* * We start at the rightmost byte (big-endian) * So, too, does OpenSSL: http://stackoverflow.com/a/3146214/2224584 */ for ($i = Core::BLOCK_BYTE_SIZE - 1; $i >= 0; --$i) { $sum = \ord($ctr[$i]) + $inc; /* Detect integer overflow and fail. */ if (! \is_int($sum)) { throw new Ex\EnvironmentIsBrokenException( 'Integer overflow in CTR mode nonce increment.' ); } $ctr[$i] = \pack('C', $sum & 0xFF); $inc = $sum >> 8; } return $ctr; } /** * Returns a random byte string of the specified length. * * @param int $octets * * @throws Ex\EnvironmentIsBrokenException * * @return string */ public static function secureRandom($octets) { self::ensureFunctionExists('random_bytes'); try { return \random_bytes($octets); } catch (\Exception $ex) { throw new Ex\EnvironmentIsBrokenException( 'Your system does not have a secure random number generator.' ); } } /** * Computes the HKDF key derivation function specified in * http://tools.ietf.org/html/rfc5869. * * @param string $hash Hash Function * @param string $ikm Initial Keying Material * @param int $length How many bytes? * @param string $info What sort of key are we deriving? * @param string $salt * * @throws Ex\EnvironmentIsBrokenException * @psalm-suppress UndefinedFunction - We're checking if the function exists first. * * @return string */ public static function HKDF($hash, $ikm, $length, $info = '', $salt = null) { static $nativeHKDF = null; if ($nativeHKDF === null) { $nativeHKDF = \is_callable('\\hash_hkdf'); } if ($nativeHKDF) { return \hash_hkdf($hash, $ikm, $length, $info, $salt); } $digest_length = Core::ourStrlen(\hash_hmac($hash, '', '', true)); // Sanity-check the desired output length. if (empty($length) || ! \is_int($length) || $length < 0 || $length > 255 * $digest_length) { throw new Ex\EnvironmentIsBrokenException( 'Bad output length requested of HKDF.' ); } // "if [salt] not provided, is set to a string of HashLen zeroes." if (\is_null($salt)) { $salt = \str_repeat("\x00", $digest_length); } // HKDF-Extract: // PRK = HMAC-Hash(salt, IKM) // The salt is the HMAC key. $prk = \hash_hmac($hash, $ikm, $salt, true); // HKDF-Expand: // This check is useless, but it serves as a reminder to the spec. if (Core::ourStrlen($prk) < $digest_length) { throw new Ex\EnvironmentIsBrokenException(); } // T(0) = '' $t = ''; $last_block = ''; for ($block_index = 1; Core::ourStrlen($t) < $length; ++$block_index) { // T(i) = HMAC-Hash(PRK, T(i-1) | info | 0x??) $last_block = \hash_hmac( $hash, $last_block . $info . \chr($block_index), $prk, true ); // T = T(1) | T(2) | T(3) | ... | T(N) $t .= $last_block; } // ORM = first L octets of T /** @var string $orm */ $orm = Core::ourSubstr($t, 0, $length); if (!\is_string($orm)) { throw new Ex\EnvironmentIsBrokenException(); } return $orm; } /** * Checks if two equal-length strings are the same without leaking * information through side channels. * * @param string $expected * @param string $given * * @throws Ex\EnvironmentIsBrokenException * * @return bool */ public static function hashEquals($expected, $given) { static $native = null; if ($native === null) { $native = \function_exists('hash_equals'); } if ($native) { return \hash_equals($expected, $given); } // We can't just compare the strings with '==', since it would make // timing attacks possible. We could use the XOR-OR constant-time // comparison algorithm, but that may not be a reliable defense in an // interpreted language. So we use the approach of HMACing both strings // with a random key and comparing the HMACs. // We're not attempting to make variable-length string comparison // secure, as that's very difficult. Make sure the strings are the same // length. if (Core::ourStrlen($expected) !== Core::ourStrlen($given)) { throw new Ex\EnvironmentIsBrokenException(); } $blind = Core::secureRandom(32); $message_compare = \hash_hmac(Core::HASH_FUNCTION_NAME, $given, $blind); $correct_compare = \hash_hmac(Core::HASH_FUNCTION_NAME, $expected, $blind); return $correct_compare === $message_compare; } /** * Throws an exception if the constant doesn't exist. * * @param string $name * @return void * * @throws Ex\EnvironmentIsBrokenException */ public static function ensureConstantExists($name) { if (! \defined($name)) { throw new Ex\EnvironmentIsBrokenException(); } } /** * Throws an exception if the function doesn't exist. * * @param string $name * @return void * * @throws Ex\EnvironmentIsBrokenException */ public static function ensureFunctionExists($name) { if (! \function_exists($name)) { throw new Ex\EnvironmentIsBrokenException(); } } /* * We need these strlen() and substr() functions because when * 'mbstring.func_overload' is set in php.ini, the standard strlen() and * substr() are replaced by mb_strlen() and mb_substr(). */ /** * Computes the length of a string in bytes. * * @param string $str * * @throws Ex\EnvironmentIsBrokenException * * @return int */ public static function ourStrlen($str) { static $exists = null; if ($exists === null) { $exists = \function_exists('mb_strlen'); } if ($exists) { $length = \mb_strlen($str, '8bit'); if ($length === false) { throw new Ex\EnvironmentIsBrokenException(); } return $length; } else { return \strlen($str); } } /** * Behaves roughly like the function substr() in PHP 7 does. * * @param string $str * @param int $start * @param int $length * * @throws Ex\EnvironmentIsBrokenException * * @return string|bool */ public static function ourSubstr($str, $start, $length = null) { static $exists = null; if ($exists === null) { $exists = \function_exists('mb_substr'); } if ($exists) { // mb_substr($str, 0, NULL, '8bit') returns an empty string on PHP // 5.3, so we have to find the length ourselves. if (! isset($length)) { if ($start >= 0) { $length = Core::ourStrlen($str) - $start; } else { $length = -$start; } } // This is required to make mb_substr behavior identical to substr. // Without this, mb_substr() would return false, contra to what the // PHP documentation says (it doesn't say it can return false.) if ($start === Core::ourStrlen($str) && $length === 0) { return ''; } if ($start > Core::ourStrlen($str)) { return false; } $substr = \mb_substr($str, $start, $length, '8bit'); if (Core::ourStrlen($substr) !== $length) { throw new Ex\EnvironmentIsBrokenException( 'Your version of PHP has bug #66797. Its implementation of mb_substr() is incorrect. See the details here: https://bugs.php.net/bug.php?id=66797' ); } return $substr; } // Unlike mb_substr(), substr() doesn't accept NULL for length if (isset($length)) { return \substr($str, $start, $length); } else { return \substr($str, $start); } } /** * Computes the PBKDF2 password-based key derivation function. * * The PBKDF2 function is defined in RFC 2898. Test vectors can be found in * RFC 6070. This implementation of PBKDF2 was originally created by Taylor * Hornby, with improvements from http://www.variations-of-shadow.com/. * * @param string $algorithm The hash algorithm to use. Recommended: SHA256 * @param string $password The password. * @param string $salt A salt that is unique to the password. * @param int $count Iteration count. Higher is better, but slower. Recommended: At least 1000. * @param int $key_length The length of the derived key in bytes. * @param bool $raw_output If true, the key is returned in raw binary format. Hex encoded otherwise. * * @throws Ex\EnvironmentIsBrokenException * * @return string A $key_length-byte key derived from the password and salt. */ public static function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false) { // Type checks: if (! \is_string($algorithm)) { throw new \Exception( 'pbkdf2(): algorithm must be a string' ); } if (! \is_string($password)) { throw new \Exception( 'pbkdf2(): password must be a string' ); } if (! \is_string($salt)) { throw new \Exception( 'pbkdf2(): salt must be a string' ); } // Coerce strings to integers with no information loss or overflow $count += 0; $key_length += 0; $algorithm = \strtolower($algorithm); if (! \in_array($algorithm, \hash_algos(), true)) { throw new Ex\EnvironmentIsBrokenException( 'Invalid or unsupported hash algorithm.' ); } // Whitelist, or we could end up with people using CRC32. $ok_algorithms = [ 'sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'ripemd160', 'ripemd256', 'ripemd320', 'whirlpool', ]; if (! \in_array($algorithm, $ok_algorithms, true)) { throw new Ex\EnvironmentIsBrokenException( 'Algorithm is not a secure cryptographic hash function.' ); } if ($count <= 0 || $key_length <= 0) { throw new Ex\EnvironmentIsBrokenException( 'Invalid PBKDF2 parameters.' ); } if (\function_exists('hash_pbkdf2')) { // The output length is in NIBBLES (4-bits) if $raw_output is false! if (! $raw_output) { $key_length = $key_length * 2; } return \hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output); } $hash_length = Core::ourStrlen(\hash($algorithm, '', true)); $block_count = \ceil($key_length / $hash_length); $output = ''; for ($i = 1; $i <= $block_count; $i++) { // $i encoded as 4 bytes, big endian. $last = $salt . \pack('N', $i); // first iteration $last = $xorsum = \hash_hmac($algorithm, $last, $password, true); // perform the other $count - 1 iterations for ($j = 1; $j < $count; $j++) { $xorsum ^= ($last = \hash_hmac($algorithm, $last, $password, true)); } $output .= $xorsum; } if ($raw_output) { return (string) Core::ourSubstr($output, 0, $key_length); } else { return Encoding::binToHex((string) Core::ourSubstr($output, 0, $key_length)); } } } Encoding.php 0000604 00000022130 15245700605 0007001 0 ustar 00 <?php namespace Defuse\Crypto; use Defuse\Crypto\Exception as Ex; final class Encoding { const CHECKSUM_BYTE_SIZE = 32; const CHECKSUM_HASH_ALGO = 'sha256'; const SERIALIZE_HEADER_BYTES = 4; /** * Converts a byte string to a hexadecimal string without leaking * information through side channels. * * @param string $byte_string * * @throws Ex\EnvironmentIsBrokenException * * @return string */ public static function binToHex($byte_string) { $hex = ''; $len = Core::ourStrlen($byte_string); for ($i = 0; $i < $len; ++$i) { $c = \ord($byte_string[$i]) & 0xf; $b = \ord($byte_string[$i]) >> 4; $hex .= \pack( 'CC', 87 + $b + ((($b - 10) >> 8) & ~38), 87 + $c + ((($c - 10) >> 8) & ~38) ); } return $hex; } /** * Converts a hexadecimal string into a byte string without leaking * information through side channels. * * @param string $hex_string * * @throws Ex\BadFormatException * @throws Ex\EnvironmentIsBrokenException * * @return string */ public static function hexToBin($hex_string) { $hex_pos = 0; $bin = ''; $hex_len = Core::ourStrlen($hex_string); $state = 0; $c_acc = 0; while ($hex_pos < $hex_len) { $c = \ord($hex_string[$hex_pos]); $c_num = $c ^ 48; $c_num0 = ($c_num - 10) >> 8; $c_alpha = ($c & ~32) - 55; $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8; if (($c_num0 | $c_alpha0) === 0) { throw new Ex\BadFormatException( 'Encoding::hexToBin() input is not a hex string.' ); } $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0); if ($state === 0) { $c_acc = $c_val * 16; } else { $bin .= \pack('C', $c_acc | $c_val); } $state ^= 1; ++$hex_pos; } return $bin; } /** * Remove trialing whitespace without table look-ups or branches. * * Calling this function may leak the length of the string as well as the * number of trailing whitespace characters through side-channels. * * @param string $string * @return string */ public static function trimTrailingWhitespace($string = '') { $length = Core::ourStrlen($string); if ($length < 1) { return ''; } do { $prevLength = $length; $last = $length - 1; $chr = \ord($string[$last]); /* Null Byte (0x00), a.k.a. \0 */ // if ($chr === 0x00) $length -= 1; $sub = (($chr - 1) >> 8 ) & 1; $length -= $sub; $last -= $sub; /* Horizontal Tab (0x09) a.k.a. \t */ $chr = \ord($string[$last]); // if ($chr === 0x09) $length -= 1; $sub = (((0x08 - $chr) & ($chr - 0x0a)) >> 8) & 1; $length -= $sub; $last -= $sub; /* New Line (0x0a), a.k.a. \n */ $chr = \ord($string[$last]); // if ($chr === 0x0a) $length -= 1; $sub = (((0x09 - $chr) & ($chr - 0x0b)) >> 8) & 1; $length -= $sub; $last -= $sub; /* Carriage Return (0x0D), a.k.a. \r */ $chr = \ord($string[$last]); // if ($chr === 0x0d) $length -= 1; $sub = (((0x0c - $chr) & ($chr - 0x0e)) >> 8) & 1; $length -= $sub; $last -= $sub; /* Space */ $chr = \ord($string[$last]); // if ($chr === 0x20) $length -= 1; $sub = (((0x1f - $chr) & ($chr - 0x21)) >> 8) & 1; $length -= $sub; } while ($prevLength !== $length && $length > 0); return (string) Core::ourSubstr($string, 0, $length); } /* * SECURITY NOTE ON APPLYING CHECKSUMS TO SECRETS: * * The checksum introduces a potential security weakness. For example, * suppose we apply a checksum to a key, and that an adversary has an * exploit against the process containing the key, such that they can * overwrite an arbitrary byte of memory and then cause the checksum to * be verified and learn the result. * * In this scenario, the adversary can extract the key one byte at * a time by overwriting it with their guess of its value and then * asking if the checksum matches. If it does, their guess was right. * This kind of attack may be more easy to implement and more reliable * than a remote code execution attack. * * This attack also applies to authenticated encryption as a whole, in * the situation where the adversary can overwrite a byte of the key * and then cause a valid ciphertext to be decrypted, and then * determine whether the MAC check passed or failed. * * By using the full SHA256 hash instead of truncating it, I'm ensuring * that both ways of going about the attack are equivalently difficult. * A shorter checksum of say 32 bits might be more useful to the * adversary as an oracle in case their writes are coarser grained. * * Because the scenario assumes a serious vulnerability, we don't try * to prevent attacks of this style. */ /** * INTERNAL USE ONLY: Applies a version header, applies a checksum, and * then encodes a byte string into a range of printable ASCII characters. * * @param string $header * @param string $bytes * * @throws Ex\EnvironmentIsBrokenException * * @return string */ public static function saveBytesToChecksummedAsciiSafeString($header, $bytes) { // 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($header) !== self::SERIALIZE_HEADER_BYTES) { throw new Ex\EnvironmentIsBrokenException( 'Header must be ' . self::SERIALIZE_HEADER_BYTES . ' bytes.' ); } return Encoding::binToHex( $header . $bytes . \hash( self::CHECKSUM_HASH_ALGO, $header . $bytes, true ) ); } /** * INTERNAL USE ONLY: Decodes, verifies the header and checksum, and returns * the encoded byte string. * * @param string $expected_header * @param string $string * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\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.' ); } /* If you get an exception here when attempting to load from a file, first pass your key to Encoding::trimTrailingWhitespace() to remove newline characters, etc. */ $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 = (string) 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 = (string) Core::ourSubstr( $bytes, 0, Core::ourStrlen($bytes) - self::CHECKSUM_BYTE_SIZE ); /* Grab the included checksum. */ $checksum_a = (string) 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 (string) Core::ourSubstr( $bytes, self::SERIALIZE_HEADER_BYTES, Core::ourStrlen($bytes) - self::SERIALIZE_HEADER_BYTES - self::CHECKSUM_BYTE_SIZE ); } } KeyOrPassword.php 0000604 00000007571 15245700605 0010043 0 ustar 00 <?php namespace Defuse\Crypto; use Defuse\Crypto\Exception as Ex; final class KeyOrPassword { const PBKDF2_ITERATIONS = 100000; const SECRET_TYPE_KEY = 1; const SECRET_TYPE_PASSWORD = 2; /** * @var int */ private $secret_type = 0; /** * @var Key|string */ private $secret; /** * Initializes an instance of KeyOrPassword from a key. * * @param Key $key * * @return KeyOrPassword */ public static function createFromKey(Key $key) { return new KeyOrPassword(self::SECRET_TYPE_KEY, $key); } /** * Initializes an instance of KeyOrPassword from a password. * * @param string $password * * @return KeyOrPassword */ public static function createFromPassword($password) { return new KeyOrPassword(self::SECRET_TYPE_PASSWORD, $password); } /** * Derives authentication and encryption keys from the secret, using a slow * key derivation function if the secret is a password. * * @param string $salt * * @throws Ex\CryptoException * @throws Ex\EnvironmentIsBrokenException * * @return DerivedKeys */ public function deriveKeys($salt) { if (Core::ourStrlen($salt) !== Core::SALT_BYTE_SIZE) { throw new Ex\EnvironmentIsBrokenException('Bad salt.'); } if ($this->secret_type === self::SECRET_TYPE_KEY) { if (!($this->secret instanceof Key)) { throw new Ex\CryptoException('Expected a Key object'); } $akey = Core::HKDF( Core::HASH_FUNCTION_NAME, $this->secret->getRawBytes(), Core::KEY_BYTE_SIZE, Core::AUTHENTICATION_INFO_STRING, $salt ); $ekey = Core::HKDF( Core::HASH_FUNCTION_NAME, $this->secret->getRawBytes(), Core::KEY_BYTE_SIZE, Core::ENCRYPTION_INFO_STRING, $salt ); return new DerivedKeys($akey, $ekey); } elseif ($this->secret_type === self::SECRET_TYPE_PASSWORD) { if (!\is_string($this->secret)) { throw new Ex\CryptoException('Expected a string'); } /* Our PBKDF2 polyfill is vulnerable to a DoS attack documented in * GitHub issue #230. The fix is to pre-hash the password to ensure * it is short. We do the prehashing here instead of in pbkdf2() so * that pbkdf2() still computes the function as defined by the * standard. */ $prehash = \hash(Core::HASH_FUNCTION_NAME, $this->secret, true); $prekey = Core::pbkdf2( Core::HASH_FUNCTION_NAME, $prehash, $salt, self::PBKDF2_ITERATIONS, Core::KEY_BYTE_SIZE, true ); $akey = Core::HKDF( Core::HASH_FUNCTION_NAME, $prekey, Core::KEY_BYTE_SIZE, Core::AUTHENTICATION_INFO_STRING, $salt ); /* Note the cryptographic re-use of $salt here. */ $ekey = Core::HKDF( Core::HASH_FUNCTION_NAME, $prekey, Core::KEY_BYTE_SIZE, Core::ENCRYPTION_INFO_STRING, $salt ); return new DerivedKeys($akey, $ekey); } else { throw new Ex\EnvironmentIsBrokenException('Bad secret type.'); } } /** * Constructor for KeyOrPassword. * * @param int $secret_type * @param mixed $secret (either a Key or a password string) */ private function __construct($secret_type, $secret) { $this->secret_type = $secret_type; $this->secret = $secret; } } RuntimeTests.php 0000604 00000021454 15245700605 0007731 0 ustar 00 <?php namespace Defuse\Crypto; use Defuse\Crypto\Exception as Ex; /* * We're using static class inheritance to get access to protected methods * inside Crypto. To make it easy to know where the method we're calling can be * found, within this file, prefix calls with `Crypto::` or `RuntimeTests::`, * and don't use `self::`. */ class RuntimeTests extends Crypto { /** * Runs the runtime tests. * * @throws Ex\EnvironmentIsBrokenException * @return void */ 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; } /** * High-level tests of Crypto operations. * * @throws Ex\EnvironmentIsBrokenException * @return void */ private static function testEncryptDecrypt() { $key = Key::createNewRandomKey(); $data = "EnCrYpT EvErYThInG\x00\x00"; // Make sure encrypting then decrypting doesn't change the message. $ciphertext = Crypto::encrypt($data, $key, true); try { $decrypted = Crypto::decrypt($ciphertext, $key, true); } catch (Ex\WrongKeyOrModifiedCiphertextException $ex) { // It's important to catch this and change it into a // Ex\EnvironmentIsBrokenException, otherwise a test failure could trick // the user into thinking it's just an invalid ciphertext! throw new Ex\EnvironmentIsBrokenException(); } if ($decrypted !== $data) { throw new Ex\EnvironmentIsBrokenException(); } // Modifying the ciphertext: Appending a string. try { Crypto::decrypt($ciphertext . 'a', $key, true); throw new Ex\EnvironmentIsBrokenException(); } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */ } // Modifying the ciphertext: Changing an HMAC byte. $indices_to_change = [ 0, // The header. Core::HEADER_VERSION_SIZE + 1, // the salt Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + 1, // the IV Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + Core::BLOCK_BYTE_SIZE + 1, // the ciphertext ]; foreach ($indices_to_change as $index) { try { $ciphertext[$index] = \chr((\ord($ciphertext[$index]) + 1) % 256); Crypto::decrypt($ciphertext, $key, true); throw new Ex\EnvironmentIsBrokenException(); } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */ } } // Decrypting with the wrong key. $key = Key::createNewRandomKey(); $data = 'abcdef'; $ciphertext = Crypto::encrypt($data, $key, true); $wrong_key = Key::createNewRandomKey(); try { Crypto::decrypt($ciphertext, $wrong_key, true); throw new Ex\EnvironmentIsBrokenException(); } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */ } // Ciphertext too small. $key = Key::createNewRandomKey(); $ciphertext = \str_repeat('A', Core::MINIMUM_CIPHERTEXT_SIZE - 1); try { Crypto::decrypt($ciphertext, $key, true); throw new Ex\EnvironmentIsBrokenException(); } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */ } } /** * Test HKDF against test vectors. * * @throws Ex\EnvironmentIsBrokenException * @return void */ private static function HKDFTestVector() { // HKDF test vectors from RFC 5869 // Test Case 1 $ikm = \str_repeat("\x0b", 22); $salt = Encoding::hexToBin('000102030405060708090a0b0c'); $info = Encoding::hexToBin('f0f1f2f3f4f5f6f7f8f9'); $length = 42; $okm = Encoding::hexToBin( '3cb25f25faacd57a90434f64d0362f2a' . '2d2d0a90cf1a5a4c5db02d56ecc4c5bf' . '34007208d5b887185865' ); $computed_okm = Core::HKDF('sha256', $ikm, $length, $info, $salt); if ($computed_okm !== $okm) { throw new Ex\EnvironmentIsBrokenException(); } // Test Case 7 $ikm = \str_repeat("\x0c", 22); $length = 42; $okm = Encoding::hexToBin( '2c91117204d745f3500d636a62f64f0a' . 'b3bae548aa53d423b0d1f27ebba6f5e5' . '673a081d70cce7acfc48' ); $computed_okm = Core::HKDF('sha1', $ikm, $length, '', null); if ($computed_okm !== $okm) { throw new Ex\EnvironmentIsBrokenException(); } } /** * Test HMAC against test vectors. * * @throws Ex\EnvironmentIsBrokenException * @return void */ private static function HMACTestVector() { // HMAC test vector From RFC 4231 (Test Case 1) $key = \str_repeat("\x0b", 20); $data = 'Hi There'; $correct = 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7'; if (\hash_hmac(Core::HASH_FUNCTION_NAME, $data, $key) !== $correct) { throw new Ex\EnvironmentIsBrokenException(); } } /** * Test AES against test vectors. * * @throws Ex\EnvironmentIsBrokenException * @return void */ 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(); } } } File.php 0000604 00000061655 15245700605 0006151 0 ustar 00 <?php namespace Defuse\Crypto; use Defuse\Crypto\Exception as Ex; final class File { /** * Encrypts the input file, saving the ciphertext to the output file. * * @param string $inputFilename * @param string $outputFilename * @param Key $key * @return void * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\IOException */ public static function encryptFile($inputFilename, $outputFilename, Key $key) { self::encryptFileInternal( $inputFilename, $outputFilename, KeyOrPassword::createFromKey($key) ); } /** * Encrypts a file with a password, using a slow key derivation function to * make password cracking more expensive. * * @param string $inputFilename * @param string $outputFilename * @param string $password * @return void * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\IOException */ public static function encryptFileWithPassword($inputFilename, $outputFilename, $password) { self::encryptFileInternal( $inputFilename, $outputFilename, KeyOrPassword::createFromPassword($password) ); } /** * Decrypts the input file, saving the plaintext to the output file. * * @param string $inputFilename * @param string $outputFilename * @param Key $key * @return void * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\IOException * @throws Ex\WrongKeyOrModifiedCiphertextException */ public static function decryptFile($inputFilename, $outputFilename, Key $key) { self::decryptFileInternal( $inputFilename, $outputFilename, KeyOrPassword::createFromKey($key) ); } /** * Decrypts a file with a password, using a slow key derivation function to * make password cracking more expensive. * * @param string $inputFilename * @param string $outputFilename * @param string $password * @return void * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\IOException * @throws Ex\WrongKeyOrModifiedCiphertextException */ public static function decryptFileWithPassword($inputFilename, $outputFilename, $password) { self::decryptFileInternal( $inputFilename, $outputFilename, KeyOrPassword::createFromPassword($password) ); } /** * Takes two resource handles and encrypts the contents of the first, * writing the ciphertext into the second. * * @param resource $inputHandle * @param resource $outputHandle * @param Key $key * @return void * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\WrongKeyOrModifiedCiphertextException */ public static function encryptResource($inputHandle, $outputHandle, Key $key) { self::encryptResourceInternal( $inputHandle, $outputHandle, KeyOrPassword::createFromKey($key) ); } /** * Encrypts the contents of one resource handle into another with a * password, using a slow key derivation function to make password cracking * more expensive. * * @param resource $inputHandle * @param resource $outputHandle * @param string $password * @return void * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\IOException * @throws Ex\WrongKeyOrModifiedCiphertextException */ public static function encryptResourceWithPassword($inputHandle, $outputHandle, $password) { self::encryptResourceInternal( $inputHandle, $outputHandle, KeyOrPassword::createFromPassword($password) ); } /** * Takes two resource handles and decrypts the contents of the first, * writing the plaintext into the second. * * @param resource $inputHandle * @param resource $outputHandle * @param Key $key * @return void * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\IOException * @throws Ex\WrongKeyOrModifiedCiphertextException */ public static function decryptResource($inputHandle, $outputHandle, Key $key) { self::decryptResourceInternal( $inputHandle, $outputHandle, KeyOrPassword::createFromKey($key) ); } /** * Decrypts the contents of one resource into another with a password, using * a slow key derivation function to make password cracking more expensive. * * @param resource $inputHandle * @param resource $outputHandle * @param string $password * @return void * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\IOException * @throws Ex\WrongKeyOrModifiedCiphertextException */ public static function decryptResourceWithPassword($inputHandle, $outputHandle, $password) { self::decryptResourceInternal( $inputHandle, $outputHandle, KeyOrPassword::createFromPassword($password) ); } /** * Encrypts a file with either a key or a password. * * @param string $inputFilename * @param string $outputFilename * @param KeyOrPassword $secret * @return void * * @throws Ex\CryptoException * @throws Ex\IOException */ private static function encryptFileInternal($inputFilename, $outputFilename, KeyOrPassword $secret) { /* Open the input file. */ $if = @\fopen($inputFilename, 'rb'); if ($if === false) { throw new Ex\IOException( 'Cannot open input file for encrypting: ' . self::getLastErrorMessage() ); } if (\is_callable('\\stream_set_read_buffer')) { /* This call can fail, but the only consequence is performance. */ \stream_set_read_buffer($if, 0); } /* Open the output file. */ $of = @\fopen($outputFilename, 'wb'); if ($of === false) { \fclose($if); throw new Ex\IOException( 'Cannot open output file for encrypting: ' . self::getLastErrorMessage() ); } if (\is_callable('\\stream_set_write_buffer')) { /* This call can fail, but the only consequence is performance. */ \stream_set_write_buffer($of, 0); } /* Perform the encryption. */ try { self::encryptResourceInternal($if, $of, $secret); } catch (Ex\CryptoException $ex) { \fclose($if); \fclose($of); throw $ex; } /* Close the input file. */ if (\fclose($if) === false) { \fclose($of); throw new Ex\IOException( 'Cannot close input file after encrypting' ); } /* Close the output file. */ if (\fclose($of) === false) { throw new Ex\IOException( 'Cannot close output file after encrypting' ); } } /** * Decrypts a file with either a key or a password. * * @param string $inputFilename * @param string $outputFilename * @param KeyOrPassword $secret * @return void * * @throws Ex\CryptoException * @throws Ex\IOException */ private static function decryptFileInternal($inputFilename, $outputFilename, KeyOrPassword $secret) { /* Open the input file. */ $if = @\fopen($inputFilename, 'rb'); if ($if === false) { throw new Ex\IOException( 'Cannot open input file for decrypting: ' . self::getLastErrorMessage() ); } if (\is_callable('\\stream_set_read_buffer')) { /* This call can fail, but the only consequence is performance. */ \stream_set_read_buffer($if, 0); } /* Open the output file. */ $of = @\fopen($outputFilename, 'wb'); if ($of === false) { \fclose($if); throw new Ex\IOException( 'Cannot open output file for decrypting: ' . self::getLastErrorMessage() ); } if (\is_callable('\\stream_set_write_buffer')) { /* This call can fail, but the only consequence is performance. */ \stream_set_write_buffer($of, 0); } /* Perform the decryption. */ try { self::decryptResourceInternal($if, $of, $secret); } catch (Ex\CryptoException $ex) { \fclose($if); \fclose($of); throw $ex; } /* Close the input file. */ if (\fclose($if) === false) { \fclose($of); throw new Ex\IOException( 'Cannot close input file after decrypting' ); } /* Close the output file. */ if (\fclose($of) === false) { throw new Ex\IOException( 'Cannot close output file after decrypting' ); } } /** * Encrypts a resource with either a key or a password. * * @param resource $inputHandle * @param resource $outputHandle * @param KeyOrPassword $secret * @return void * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\IOException */ private static function encryptResourceInternal($inputHandle, $outputHandle, KeyOrPassword $secret) { if (! \is_resource($inputHandle)) { throw new Ex\IOException( 'Input handle must be a resource!' ); } if (! \is_resource($outputHandle)) { throw new Ex\IOException( 'Output handle must be a resource!' ); } $inputStat = \fstat($inputHandle); $inputSize = $inputStat['size']; $file_salt = Core::secureRandom(Core::SALT_BYTE_SIZE); $keys = $secret->deriveKeys($file_salt); $ekey = $keys->getEncryptionKey(); $akey = $keys->getAuthenticationKey(); $ivsize = Core::BLOCK_BYTE_SIZE; $iv = Core::secureRandom($ivsize); /* Initialize a streaming HMAC state. */ /** @var resource $hmac */ $hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey); if (!\is_resource($hmac)) { throw new Ex\EnvironmentIsBrokenException( 'Cannot initialize a hash context' ); } /* Write the header, salt, and IV. */ self::writeBytes( $outputHandle, Core::CURRENT_VERSION . $file_salt . $iv, Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + $ivsize ); /* Add the header, salt, and IV to the HMAC. */ \hash_update($hmac, Core::CURRENT_VERSION); \hash_update($hmac, $file_salt); \hash_update($hmac, $iv); /* $thisIv will be incremented after each call to the encryption. */ $thisIv = $iv; /* How many blocks do we encrypt at a time? We increment by this value. */ $inc = (int) (Core::BUFFER_BYTE_SIZE / Core::BLOCK_BYTE_SIZE); /* Loop until we reach the end of the input file. */ $at_file_end = false; while (! (\feof($inputHandle) || $at_file_end)) { /* Find out if we can read a full buffer, or only a partial one. */ /** @var int */ $pos = \ftell($inputHandle); if (!\is_int($pos)) { throw new Ex\IOException( 'Could not get current position in input file during encryption' ); } if ($pos + Core::BUFFER_BYTE_SIZE >= $inputSize) { /* We're at the end of the file, so we need to break out of the loop. */ $at_file_end = true; $read = self::readBytes( $inputHandle, $inputSize - $pos ); } else { $read = self::readBytes( $inputHandle, Core::BUFFER_BYTE_SIZE ); } /* Encrypt this buffer. */ /** @var string */ $encrypted = \openssl_encrypt( $read, Core::CIPHER_METHOD, $ekey, OPENSSL_RAW_DATA, $thisIv ); if (!\is_string($encrypted)) { throw new Ex\EnvironmentIsBrokenException( 'OpenSSL encryption error' ); } /* Write this buffer's ciphertext. */ self::writeBytes($outputHandle, $encrypted, Core::ourStrlen($encrypted)); /* Add this buffer's ciphertext to the HMAC. */ \hash_update($hmac, $encrypted); /* Increment the counter by the number of blocks in a buffer. */ $thisIv = Core::incrementCounter($thisIv, $inc); /* WARNING: Usually, unless the file is a multiple of the buffer * size, $thisIv will contain an incorrect value here on the last * iteration of this loop. */ } /* Get the HMAC and append it to the ciphertext. */ $final_mac = \hash_final($hmac, true); self::writeBytes($outputHandle, $final_mac, Core::MAC_BYTE_SIZE); } /** * Decrypts a file-backed resource with either a key or a password. * * @param resource $inputHandle * @param resource $outputHandle * @param KeyOrPassword $secret * @return void * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\IOException * @throws Ex\WrongKeyOrModifiedCiphertextException */ public static function decryptResourceInternal($inputHandle, $outputHandle, KeyOrPassword $secret) { if (! \is_resource($inputHandle)) { throw new Ex\IOException( 'Input handle must be a resource!' ); } if (! \is_resource($outputHandle)) { throw new Ex\IOException( 'Output handle must be a resource!' ); } /* Make sure the file is big enough for all the reads we need to do. */ $stat = \fstat($inputHandle); if ($stat['size'] < Core::MINIMUM_CIPHERTEXT_SIZE) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'Input file is too small to have been created by this library.' ); } /* Check the version header. */ $header = self::readBytes($inputHandle, Core::HEADER_VERSION_SIZE); if ($header !== Core::CURRENT_VERSION) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'Bad version header.' ); } /* Get the salt. */ $file_salt = self::readBytes($inputHandle, Core::SALT_BYTE_SIZE); /* Get the IV. */ $ivsize = Core::BLOCK_BYTE_SIZE; $iv = self::readBytes($inputHandle, $ivsize); /* Derive the authentication and encryption keys. */ $keys = $secret->deriveKeys($file_salt); $ekey = $keys->getEncryptionKey(); $akey = $keys->getAuthenticationKey(); /* We'll store the MAC of each buffer-sized chunk as we verify the * actual MAC, so that we can check them again when decrypting. */ $macs = []; /* $thisIv will be incremented after each call to the decryption. */ $thisIv = $iv; /* How many blocks do we encrypt at a time? We increment by this value. */ $inc = (int) (Core::BUFFER_BYTE_SIZE / Core::BLOCK_BYTE_SIZE); /* Get the HMAC. */ if (\fseek($inputHandle, (-1 * Core::MAC_BYTE_SIZE), SEEK_END) === false) { throw new Ex\IOException( 'Cannot seek to beginning of MAC within input file' ); } /* Get the position of the last byte in the actual ciphertext. */ /** @var int $cipher_end */ $cipher_end = \ftell($inputHandle); if (!\is_int($cipher_end)) { throw new Ex\IOException( 'Cannot read input file' ); } /* We have the position of the first byte of the HMAC. Go back by one. */ --$cipher_end; /* Read the HMAC. */ /** @var string $stored_mac */ $stored_mac = self::readBytes($inputHandle, Core::MAC_BYTE_SIZE); /* Initialize a streaming HMAC state. */ /** @var resource $hmac */ $hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey); if (!\is_resource($hmac)) { throw new Ex\EnvironmentIsBrokenException( 'Cannot initialize a hash context' ); } /* Reset file pointer to the beginning of the file after the header */ if (\fseek($inputHandle, Core::HEADER_VERSION_SIZE, SEEK_SET) === false) { throw new Ex\IOException( 'Cannot read seek within input file' ); } /* Seek to the start of the actual ciphertext. */ if (\fseek($inputHandle, Core::SALT_BYTE_SIZE + $ivsize, SEEK_CUR) === false) { throw new Ex\IOException( 'Cannot seek input file to beginning of ciphertext' ); } /* PASS #1: Calculating the HMAC. */ \hash_update($hmac, $header); \hash_update($hmac, $file_salt); \hash_update($hmac, $iv); /** @var resource $hmac2 */ $hmac2 = \hash_copy($hmac); $break = false; while (! $break) { /** @var int $pos */ $pos = \ftell($inputHandle); if (!\is_int($pos)) { throw new Ex\IOException( 'Could not get current position in input file during decryption' ); } /* Read the next buffer-sized chunk (or less). */ if ($pos + Core::BUFFER_BYTE_SIZE >= $cipher_end) { $break = true; $read = self::readBytes( $inputHandle, $cipher_end - $pos + 1 ); } else { $read = self::readBytes( $inputHandle, Core::BUFFER_BYTE_SIZE ); } /* Update the HMAC. */ \hash_update($hmac, $read); /* Remember this buffer-sized chunk's HMAC. */ /** @var resource $chunk_mac */ $chunk_mac = \hash_copy($hmac); if (!\is_resource($chunk_mac)) { throw new Ex\EnvironmentIsBrokenException( 'Cannot duplicate a hash context' ); } $macs []= \hash_final($chunk_mac); } /* Get the final HMAC, which should match the stored one. */ /** @var string $final_mac */ $final_mac = \hash_final($hmac, true); /* Verify the HMAC. */ if (! Core::hashEquals($final_mac, $stored_mac)) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'Integrity check failed.' ); } /* PASS #2: Decrypt and write output. */ /* Rewind to the start of the actual ciphertext. */ if (\fseek($inputHandle, Core::SALT_BYTE_SIZE + $ivsize + Core::HEADER_VERSION_SIZE, SEEK_SET) === false) { throw new Ex\IOException( 'Could not move the input file pointer during decryption' ); } $at_file_end = false; while (! $at_file_end) { /** @var int $pos */ $pos = \ftell($inputHandle); if (!\is_int($pos)) { throw new Ex\IOException( 'Could not get current position in input file during decryption' ); } /* Read the next buffer-sized chunk (or less). */ if ($pos + Core::BUFFER_BYTE_SIZE >= $cipher_end) { $at_file_end = true; $read = self::readBytes( $inputHandle, $cipher_end - $pos + 1 ); } else { $read = self::readBytes( $inputHandle, Core::BUFFER_BYTE_SIZE ); } /* Recalculate the MAC (so far) and compare it with the one we * remembered from pass #1 to ensure attackers didn't change the * ciphertext after MAC verification. */ \hash_update($hmac2, $read); /** @var resource $calc_mac */ $calc_mac = \hash_copy($hmac2); if (!\is_resource($calc_mac)) { throw new Ex\EnvironmentIsBrokenException( 'Cannot duplicate a hash context' ); } $calc = \hash_final($calc_mac); if (empty($macs)) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'File was modified after MAC verification' ); } elseif (! Core::hashEquals(\array_shift($macs), $calc)) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'File was modified after MAC verification' ); } /* Decrypt this buffer-sized chunk. */ /** @var string $decrypted */ $decrypted = \openssl_decrypt( $read, Core::CIPHER_METHOD, $ekey, OPENSSL_RAW_DATA, $thisIv ); if (!\is_string($decrypted)) { throw new Ex\EnvironmentIsBrokenException( 'OpenSSL decryption error' ); } /* Write the plaintext to the output file. */ self::writeBytes( $outputHandle, $decrypted, Core::ourStrlen($decrypted) ); /* Increment the IV by the amount of blocks in a buffer. */ /** @var string $thisIv */ $thisIv = Core::incrementCounter($thisIv, $inc); /* WARNING: Usually, unless the file is a multiple of the buffer * size, $thisIv will contain an incorrect value here on the last * iteration of this loop. */ } } /** * Read from a stream; prevent partial reads. * * @param resource $stream * @param int $num_bytes * @return string * * @throws Ex\IOException * @throws Ex\EnvironmentIsBrokenException * * @return string */ public static function readBytes($stream, $num_bytes) { if ($num_bytes < 0) { throw new Ex\EnvironmentIsBrokenException( 'Tried to read less than 0 bytes' ); } elseif ($num_bytes === 0) { return ''; } $buf = ''; $remaining = $num_bytes; while ($remaining > 0 && ! \feof($stream)) { /** @var string $read */ $read = \fread($stream, $remaining); if (!\is_string($read)) { throw new Ex\IOException( 'Could not read from the file' ); } $buf .= $read; $remaining -= Core::ourStrlen($read); } if (Core::ourStrlen($buf) !== $num_bytes) { throw new Ex\IOException( 'Tried to read past the end of the file' ); } return $buf; } /** * Write to a stream; prevents partial writes. * * @param resource $stream * @param string $buf * @param int $num_bytes * @return int * * @throws Ex\IOException * * @return string */ public static function writeBytes($stream, $buf, $num_bytes = null) { $bufSize = Core::ourStrlen($buf); if ($num_bytes === null) { $num_bytes = $bufSize; } if ($num_bytes > $bufSize) { throw new Ex\IOException( 'Trying to write more bytes than the buffer contains.' ); } if ($num_bytes < 0) { throw new Ex\IOException( 'Tried to write less than 0 bytes' ); } $remaining = $num_bytes; while ($remaining > 0) { /** @var int $written */ $written = \fwrite($stream, $buf, $remaining); if (!\is_int($written)) { throw new Ex\IOException( 'Could not write to the file' ); } $buf = (string) Core::ourSubstr($buf, $written, null); $remaining -= $written; } return $num_bytes; } /** * Returns the last PHP error's or warning's message string. * * @return string */ private static function getLastErrorMessage() { $error = error_get_last(); if ($error === null) { return '[no PHP error]'; } else { return $error['message']; } } } Key.php 0000604 00000004437 15245700605 0006015 0 ustar 00 <?php namespace Defuse\Crypto; use Defuse\Crypto\Exception as Ex; final class Key { const KEY_CURRENT_VERSION = "\xDE\xF0\x00\x00"; const KEY_BYTE_SIZE = 32; /** * @var string */ private $key_bytes; /** * Creates new random key. * * @throws Ex\EnvironmentIsBrokenException * * @return Key */ public static function createNewRandomKey() { return new Key(Core::secureRandom(self::KEY_BYTE_SIZE)); } /** * Loads a Key from its encoded form. * * By default, this function will call Encoding::trimTrailingWhitespace() * to remove trailing CR, LF, NUL, TAB, and SPACE characters, which are * commonly appended to files when working with text editors. * * @param string $saved_key_string * @param bool $do_not_trim (default: false) * * @throws Ex\BadFormatException * @throws Ex\EnvironmentIsBrokenException * * @return Key */ public static function loadFromAsciiSafeString($saved_key_string, $do_not_trim = false) { if (!$do_not_trim) { $saved_key_string = Encoding::trimTrailingWhitespace($saved_key_string); } $key_bytes = Encoding::loadBytesFromChecksummedAsciiSafeString(self::KEY_CURRENT_VERSION, $saved_key_string); return new Key($key_bytes); } /** * Encodes the Key into a string of printable ASCII characters. * * @throws Ex\EnvironmentIsBrokenException * * @return string */ public function saveToAsciiSafeString() { return Encoding::saveBytesToChecksummedAsciiSafeString( self::KEY_CURRENT_VERSION, $this->key_bytes ); } /** * Gets the raw bytes of the key. * * @return string */ public function getRawBytes() { return $this->key_bytes; } /** * Constructs a new Key object from a string of raw bytes. * * @param string $bytes * * @throws Ex\EnvironmentIsBrokenException */ private function __construct($bytes) { if (Core::ourStrlen($bytes) !== self::KEY_BYTE_SIZE) { throw new Ex\EnvironmentIsBrokenException( 'Bad key length.' ); } $this->key_bytes = $bytes; } } Exception/WrongKeyOrModifiedCiphertextException.php 0000604 00000000214 15245700605 0016636 0 ustar 00 <?php namespace Defuse\Crypto\Exception; class WrongKeyOrModifiedCiphertextException extends \Defuse\Crypto\Exception\CryptoException { } Exception/EnvironmentIsBrokenException.php 0000604 00000000203 15245700605 0015026 0 ustar 00 <?php namespace Defuse\Crypto\Exception; class EnvironmentIsBrokenException extends \Defuse\Crypto\Exception\CryptoException { } Exception/BadFormatException.php 0000604 00000000171 15245700605 0012730 0 ustar 00 <?php namespace Defuse\Crypto\Exception; class BadFormatException extends \Defuse\Crypto\Exception\CryptoException { } Exception/CryptoException.php 0000604 00000000130 15245700605 0012344 0 ustar 00 <?php namespace Defuse\Crypto\Exception; class CryptoException extends \Exception { } Exception/IOException.php 0000604 00000000162 15245700605 0011400 0 ustar 00 <?php namespace Defuse\Crypto\Exception; class IOException extends \Defuse\Crypto\Exception\CryptoException { } Crypto.php 0000604 00000033767 15245700605 0006555 0 ustar 00 <?php namespace Defuse\Crypto; use Defuse\Crypto\Exception as Ex; class Crypto { /** * Encrypts a string with a Key. * * @param string $plaintext * @param Key $key * @param bool $raw_binary * * @throws Ex\EnvironmentIsBrokenException * * @return string */ public static function encrypt($plaintext, Key $key, $raw_binary = false) { if (!\is_string($plaintext)) { throw new \TypeError( 'String expected for argument 1. ' . \ucfirst(\gettype($plaintext)) . ' given instead.' ); } if (!\is_bool($raw_binary)) { throw new \TypeError( 'Boolean expected for argument 3. ' . \ucfirst(\gettype($raw_binary)) . ' given instead.' ); } return self::encryptInternal( $plaintext, KeyOrPassword::createFromKey($key), $raw_binary ); } /** * Encrypts a string with a password, using a slow key derivation function * to make password cracking more expensive. * * @param string $plaintext * @param string $password * @param bool $raw_binary * * @throws Ex\EnvironmentIsBrokenException * * @return string */ public static function encryptWithPassword($plaintext, $password, $raw_binary = false) { if (!\is_string($plaintext)) { throw new \TypeError( 'String expected for argument 1. ' . \ucfirst(\gettype($plaintext)) . ' given instead.' ); } if (!\is_string($password)) { throw new \TypeError( 'String expected for argument 2. ' . \ucfirst(\gettype($password)) . ' given instead.' ); } if (!\is_bool($raw_binary)) { throw new \TypeError( 'Boolean expected for argument 3. ' . \ucfirst(\gettype($raw_binary)) . ' given instead.' ); } return self::encryptInternal( $plaintext, KeyOrPassword::createFromPassword($password), $raw_binary ); } /** * Decrypts a ciphertext to a string with a Key. * * @param string $ciphertext * @param Key $key * @param bool $raw_binary * * @throws \TypeError * @throws Ex\EnvironmentIsBrokenException * @throws Ex\WrongKeyOrModifiedCiphertextException * * @return string */ public static function decrypt($ciphertext, Key $key, $raw_binary = false) { if (!\is_string($ciphertext)) { throw new \TypeError( 'String expected for argument 1. ' . \ucfirst(\gettype($ciphertext)) . ' given instead.' ); } if (!\is_bool($raw_binary)) { throw new \TypeError( 'Boolean expected for argument 3. ' . \ucfirst(\gettype($raw_binary)) . ' given instead.' ); } return self::decryptInternal( $ciphertext, KeyOrPassword::createFromKey($key), $raw_binary ); } /** * Decrypts a ciphertext to a string with a password, using a slow key * derivation function to make password cracking more expensive. * * @param string $ciphertext * @param string $password * @param bool $raw_binary * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\WrongKeyOrModifiedCiphertextException * * @return string */ public static function decryptWithPassword($ciphertext, $password, $raw_binary = false) { if (!\is_string($ciphertext)) { throw new \TypeError( 'String expected for argument 1. ' . \ucfirst(\gettype($ciphertext)) . ' given instead.' ); } if (!\is_string($password)) { throw new \TypeError( 'String expected for argument 2. ' . \ucfirst(\gettype($password)) . ' given instead.' ); } if (!\is_bool($raw_binary)) { throw new \TypeError( 'Boolean expected for argument 3. ' . \ucfirst(\gettype($raw_binary)) . ' given instead.' ); } return self::decryptInternal( $ciphertext, KeyOrPassword::createFromPassword($password), $raw_binary ); } /** * Decrypts a legacy ciphertext produced by version 1 of this library. * * @param string $ciphertext * @param string $key * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\WrongKeyOrModifiedCiphertextException * * @return string */ public static function legacyDecrypt($ciphertext, $key) { if (!\is_string($ciphertext)) { throw new \TypeError( 'String expected for argument 1. ' . \ucfirst(\gettype($ciphertext)) . ' given instead.' ); } if (!\is_string($key)) { throw new \TypeError( 'String expected for argument 2. ' . \ucfirst(\gettype($key)) . ' given instead.' ); } RuntimeTests::runtimeTest(); // Extract the HMAC from the front of the ciphertext. if (Core::ourStrlen($ciphertext) <= Core::LEGACY_MAC_BYTE_SIZE) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'Ciphertext is too short.' ); } /** * @var string */ $hmac = Core::ourSubstr($ciphertext, 0, Core::LEGACY_MAC_BYTE_SIZE); if (!\is_string($hmac)) { throw new Ex\EnvironmentIsBrokenException(); } /** * @var string */ $messageCiphertext = Core::ourSubstr($ciphertext, Core::LEGACY_MAC_BYTE_SIZE); if (!\is_string($messageCiphertext)) { throw new Ex\EnvironmentIsBrokenException(); } // Regenerate the same authentication sub-key. $akey = Core::HKDF( Core::LEGACY_HASH_FUNCTION_NAME, $key, Core::LEGACY_KEY_BYTE_SIZE, Core::LEGACY_AUTHENTICATION_INFO_STRING, null ); if (self::verifyHMAC($hmac, $messageCiphertext, $akey)) { // Regenerate the same encryption sub-key. $ekey = Core::HKDF( Core::LEGACY_HASH_FUNCTION_NAME, $key, Core::LEGACY_KEY_BYTE_SIZE, Core::LEGACY_ENCRYPTION_INFO_STRING, null ); // Extract the IV from the ciphertext. if (Core::ourStrlen($messageCiphertext) <= Core::LEGACY_BLOCK_BYTE_SIZE) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'Ciphertext is too short.' ); } /** * @var string */ $iv = Core::ourSubstr($messageCiphertext, 0, Core::LEGACY_BLOCK_BYTE_SIZE); if (!\is_string($iv)) { throw new Ex\EnvironmentIsBrokenException(); } /** * @var string */ $actualCiphertext = Core::ourSubstr($messageCiphertext, Core::LEGACY_BLOCK_BYTE_SIZE); if (!\is_string($actualCiphertext)) { throw new Ex\EnvironmentIsBrokenException(); } // Do the decryption. $plaintext = self::plainDecrypt($actualCiphertext, $ekey, $iv, Core::LEGACY_CIPHER_METHOD); return $plaintext; } else { throw new Ex\WrongKeyOrModifiedCiphertextException( 'Integrity check failed.' ); } } /** * Encrypts a string with either a key or a password. * * @param string $plaintext * @param KeyOrPassword $secret * @param bool $raw_binary * * @return string */ private static function encryptInternal($plaintext, KeyOrPassword $secret, $raw_binary) { RuntimeTests::runtimeTest(); $salt = Core::secureRandom(Core::SALT_BYTE_SIZE); $keys = $secret->deriveKeys($salt); $ekey = $keys->getEncryptionKey(); $akey = $keys->getAuthenticationKey(); $iv = Core::secureRandom(Core::BLOCK_BYTE_SIZE); $ciphertext = Core::CURRENT_VERSION . $salt . $iv . self::plainEncrypt($plaintext, $ekey, $iv); $auth = \hash_hmac(Core::HASH_FUNCTION_NAME, $ciphertext, $akey, true); $ciphertext = $ciphertext . $auth; if ($raw_binary) { return $ciphertext; } return Encoding::binToHex($ciphertext); } /** * 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. /** @var string $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. /** @var string $salt */ $salt = Core::ourSubstr( $ciphertext, Core::HEADER_VERSION_SIZE, Core::SALT_BYTE_SIZE ); if (!\is_string($salt)) { throw new Ex\EnvironmentIsBrokenException(); } // Get the IV. /** @var string $iv */ $iv = Core::ourSubstr( $ciphertext, Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE, Core::BLOCK_BYTE_SIZE ); if (!\is_string($iv)) { throw new Ex\EnvironmentIsBrokenException(); } // Get the HMAC. /** @var string $hmac */ $hmac = Core::ourSubstr( $ciphertext, Core::ourStrlen($ciphertext) - Core::MAC_BYTE_SIZE, Core::MAC_BYTE_SIZE ); if (!\is_string($hmac)) { throw new Ex\EnvironmentIsBrokenException(); } // Get the actual encrypted ciphertext. /** @var string $encrypted */ $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 (!\is_string($encrypted)) { 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.' ); } } /** * Raw unauthenticated encryption (insecure on its own). * * @param string $plaintext * @param string $key * @param string $iv * * @throws Ex\EnvironmentIsBrokenException * * @return string */ protected static function plainEncrypt($plaintext, $key, $iv) { Core::ensureConstantExists('OPENSSL_RAW_DATA'); Core::ensureFunctionExists('openssl_encrypt'); /** @var string $ciphertext */ $ciphertext = \openssl_encrypt( $plaintext, Core::CIPHER_METHOD, $key, OPENSSL_RAW_DATA, $iv ); if (!\is_string($ciphertext)) { throw new Ex\EnvironmentIsBrokenException( 'openssl_encrypt() failed.' ); } return $ciphertext; } /** * Raw unauthenticated decryption (insecure on its own). * * @param string $ciphertext * @param string $key * @param string $iv * @param string $cipherMethod * * @throws Ex\EnvironmentIsBrokenException * * @return string */ protected static function plainDecrypt($ciphertext, $key, $iv, $cipherMethod) { Core::ensureConstantExists('OPENSSL_RAW_DATA'); Core::ensureFunctionExists('openssl_decrypt'); /** @var string $plaintext */ $plaintext = \openssl_decrypt( $ciphertext, $cipherMethod, $key, OPENSSL_RAW_DATA, $iv ); if (!\is_string($plaintext)) { throw new Ex\EnvironmentIsBrokenException( 'openssl_decrypt() failed.' ); } return $plaintext; } /** * Verifies an HMAC without leaking information through side-channels. * * @param string $expected_hmac * @param string $message * @param string $key * * @throws Ex\EnvironmentIsBrokenException * * @return bool */ protected static function verifyHMAC($expected_hmac, $message, $key) { $message_hmac = \hash_hmac(Core::HASH_FUNCTION_NAME, $message, $key, true); return Core::hashEquals($message_hmac, $expected_hmac); } }
| ver. 1.4 |
Github
|
.
| PHP 8.1.34 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка