Пример #1
0
 /**
  * Convert a hexadecimal string into a binary string without cache-timing 
  * leaks
  *
  * @param string $hex_string
  * @return string (raw binary)
  */
 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 \RangeException('Encoding::hexToBin() only expects hexadecimal characters');
         }
         $c_val = $c_num0 & $c_num | $c_alpha & $c_alpha0;
         if ($state === 0) {
             $c_acc = $c_val * 16;
         } else {
             $bin .= \chr($c_acc | $c_val);
         }
         $state = $state ? 0 : 1;
         ++$hex_pos;
     }
     return $bin;
 }
Пример #2
0
 /**
  * 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\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) {
         $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) {
         /* 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.');
     }
 }
Пример #3
0
 /**
  * @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);
 }
Пример #4
0
 public function __construct($config_array)
 {
     $expected_keys = array("cipher_method", "block_byte_size", "key_byte_size", "salt_byte_size", "mac_byte_size", "hash_function_name", "encryption_info_string", "authentication_info_string");
     if (sort($expected_keys) !== true) {
         throw Ex\CannotPerformOperationException("sort() failed.");
     }
     $actual_keys = array_keys($config_array);
     if (sort($actual_keys) !== true) {
         throw Ex\CannotPerformOperationException("sort() failed.");
     }
     if ($expected_keys !== $actual_keys) {
         throw new Ex\CannotPerformOperationException("Trying to instantiate a bad configuration.");
     }
     $this->cipher_method = $config_array["cipher_method"];
     $this->block_byte_size = $config_array["block_byte_size"];
     $this->key_byte_size = $config_array["key_byte_size"];
     $this->salt_byte_size = $config_array["salt_byte_size"];
     $this->mac_byte_size = $config_array["mac_byte_size"];
     $this->hash_function_name = $config_array["hash_function_name"];
     $this->encryption_info_string = $config_array["encryption_info_string"];
     $this->authentication_info_string = $config_array["authentication_info_string"];
     Core::ensureFunctionExists('openssl_get_cipher_methods');
     if (\in_array($this->cipher_method, \openssl_get_cipher_methods()) === false) {
         throw new Ex\CannotPerformOperationException("Configuration contains an invalid OpenSSL cipher method.");
     }
     if (!\is_int($this->block_byte_size) || $this->block_byte_size <= 0) {
         throw new Ex\CannotPerformOperationException("Configuration contains an invalid block byte size.");
     }
     if (!\is_int($this->key_byte_size) || $this->key_byte_size <= 0) {
         throw new Ex\CannotPerformOperationException("Configuration contains an invalid key byte size.");
     }
     if ($this->salt_byte_size !== false) {
         if (!is_int($this->salt_byte_size) || $this->salt_byte_size <= 0) {
             throw new Ex\CannotPerformOperationException("Configuration contains an invalid salt byte size.");
         }
     }
     if (!\is_int($this->mac_byte_size) || $this->mac_byte_size <= 0) {
         throw new Ex\CannotPerformOperationException("Configuration contains an invalid MAC byte size.");
     }
     if (\in_array($this->hash_function_name, \hash_algos()) === false) {
         throw new Ex\CannotPerformOperationException("Configuration contains an invalid hash function name.");
     }
     if (!\is_string($this->encryption_info_string) || $this->encryption_info_string === "") {
         throw new Ex\CannotPerformOperationException("Configuration contains an invalid encryption info string.");
     }
     if (!\is_string($this->authentication_info_string) || $this->authentication_info_string === "") {
         throw new Ex\CannotPerformOperationException("Configuration contains an invalid authentication info string.");
     }
 }
Пример #5
0
 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));
 }
Пример #6
0
 /**
  * Verifies an HMAC without leaking information through side-channels.
  *
  * @param string $correct_hmac
  * @param string $message
  * @param string $key
  *
  * @throws Ex\EnvironmentIsBrokenException
  *
  * @return bool
  */
 protected static function verifyHMAC($correct_hmac, $message, $key)
 {
     $message_hmac = \hash_hmac(Core::HASH_FUNCTION_NAME, $message, $key, true);
     return Core::hashEquals($correct_hmac, $message_hmac);
 }
Пример #7
0
 public function testCreateNewRandomKey()
 {
     $key = Key::createNewRandomKey();
     $this->assertSame(32, Core::ourStrlen($key->getRawBytes()));
 }
Пример #8
0
 /**
  * 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);
 }
Пример #9
0
 /**
  * Test HKDF against test vectors.
  *
  * @throws Ex\EnvironmentIsBrokenException
  */
 private static function HKDFTestVector()
 {
     // HKDF test vectors from RFC 5869
     // Test Case 1
     $ikm = \str_repeat("\v", 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("\f", 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();
     }
 }
Пример #10
0
 /**
  * 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;
 }
Пример #11
0
 /**
  * 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;
 }
Пример #12
0
 public function getRawBytes()
 {
     if (is_null($this->key_bytes) || Core::ourStrlen($this->key_bytes) < self::MIN_SAFE_KEY_BYTE_SIZE) {
         throw new CannotPerformOperationException("An attempt was made to use an uninitialzied or too-short key");
     }
     return $this->key_bytes;
 }