diff --git a/inc/Exts/php-encryption/Core.php b/inc/Exts/php-encryption/Core.php new file mode 100644 index 00000000..c489f390 --- /dev/null +++ b/inc/Exts/php-encryption/Core.php @@ -0,0 +1,434 @@ + 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 + * + * @return string + */ + public static function HKDF($hash, $ikm, $length, $info = '', $salt = null) + { + $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 + $orm = Core::ourSubstr($t, 0, $length); + if ($orm === false) { + 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 + * + * @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 + * + * @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 + */ + 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 \InvalidArgumentException( + 'pbkdf2(): algorithm must be a string' + ); + } + if (! \is_string($password)) { + throw new \InvalidArgumentException( + 'pbkdf2(): password must be a string' + ); + } + if (! \is_string($salt)) { + throw new \InvalidArgumentException( + '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 Core::ourSubstr($output, 0, $key_length); + } else { + return Encoding::binToHex(Core::ourSubstr($output, 0, $key_length)); + } + } +} diff --git a/inc/Exts/php-encryption/Crypto.php b/inc/Exts/php-encryption/Crypto.php new file mode 100644 index 00000000..3acd6112 --- /dev/null +++ b/inc/Exts/php-encryption/Crypto.php @@ -0,0 +1,372 @@ +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. + $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.' + ); + } + } + + /** + * 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'); + $ciphertext = \openssl_encrypt( + $plaintext, + Core::CIPHER_METHOD, + $key, + OPENSSL_RAW_DATA, + $iv + ); + + if ($ciphertext === false) { + 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'); + $plaintext = \openssl_decrypt( + $ciphertext, + $cipherMethod, + $key, + OPENSSL_RAW_DATA, + $iv + ); + if ($plaintext === false) { + throw new Ex\EnvironmentIsBrokenException( + 'openssl_decrypt() failed.' + ); + } + + return $plaintext; + } + + /** + * 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); + } +} diff --git a/inc/Exts/php-encryption/DerivedKeys.php b/inc/Exts/php-encryption/DerivedKeys.php new file mode 100644 index 00000000..fcfc0430 --- /dev/null +++ b/inc/Exts/php-encryption/DerivedKeys.php @@ -0,0 +1,37 @@ +akey; + } + + /** + * Returns the encryption key. + */ + 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; + } +} diff --git a/inc/Exts/php-encryption/Encoding.php b/inc/Exts/php-encryption/Encoding.php new file mode 100644 index 00000000..6e022d77 --- /dev/null +++ b/inc/Exts/php-encryption/Encoding.php @@ -0,0 +1,212 @@ +> 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; + } + + /* + * 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.' + ); + } + + $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 + ); + } +} diff --git a/inc/Exts/php-encryption/Exception/BadFormatException.php b/inc/Exts/php-encryption/Exception/BadFormatException.php new file mode 100644 index 00000000..804d9c17 --- /dev/null +++ b/inc/Exts/php-encryption/Exception/BadFormatException.php @@ -0,0 +1,7 @@ +deriveKeys($file_salt); + $ekey = $keys->getEncryptionKey(); + $akey = $keys->getAuthenticationKey(); + + $ivsize = Core::BLOCK_BYTE_SIZE; + $iv = Core::secureRandom($ivsize); + + /* Initialize a streaming HMAC state. */ + $hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey); + if ($hmac === false) { + 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 = 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. */ + $pos = \ftell($inputHandle); + if ($pos === false) { + 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. */ + $encrypted = \openssl_encrypt( + $read, + Core::CIPHER_METHOD, + $ekey, + OPENSSL_RAW_DATA, + $thisIv + ); + + if ($encrypted === false) { + 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 + * + * @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 = 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. */ + $cipher_end = \ftell($inputHandle); + if ($cipher_end === false) { + 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. */ + $stored_mac = self::readBytes($inputHandle, Core::MAC_BYTE_SIZE); + + /* Initialize a streaming HMAC state. */ + $hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey); + if ($hmac === false) { + 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); + $hmac2 = \hash_copy($hmac); + + $break = false; + while (! $break) { + $pos = \ftell($inputHandle); + if ($pos === false) { + 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. */ + $chunk_mac = \hash_copy($hmac); + if ($chunk_mac === false) { + throw new Ex\EnvironmentIsBrokenException( + 'Cannot duplicate a hash context' + ); + } + $macs []= \hash_final($chunk_mac); + } + + /* Get the final HMAC, which should match the stored one. */ + $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) { + $pos = \ftell($inputHandle); + if ($pos === false) { + 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); + $calc_mac = \hash_copy($hmac2); + if ($calc_mac === false) { + 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. */ + $decrypted = \openssl_decrypt( + $read, + Core::CIPHER_METHOD, + $ekey, + OPENSSL_RAW_DATA, + $thisIv + ); + if ($decrypted === false) { + 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. */ + $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 + * + * @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)) { + $read = \fread($stream, $remaining); + + if ($read === false) { + 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 + * + * @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) { + $written = \fwrite($stream, $buf, $remaining); + if ($written === false) { + throw new Ex\IOException( + 'Could not write to the file' + ); + } + $buf = 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']; + } + } +} diff --git a/inc/Exts/php-encryption/Key.php b/inc/Exts/php-encryption/Key.php new file mode 100644 index 00000000..ca7b9b2e --- /dev/null +++ b/inc/Exts/php-encryption/Key.php @@ -0,0 +1,84 @@ +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; + } + +} diff --git a/inc/Exts/php-encryption/KeyOrPassword.php b/inc/Exts/php-encryption/KeyOrPassword.php new file mode 100644 index 00000000..2a46b711 --- /dev/null +++ b/inc/Exts/php-encryption/KeyOrPassword.php @@ -0,0 +1,119 @@ +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.'); + } + } + + /** + * 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; + } +} diff --git a/inc/Exts/php-encryption/KeyProtectedByPassword.php b/inc/Exts/php-encryption/KeyProtectedByPassword.php new file mode 100644 index 00000000..b9a40f15 --- /dev/null +++ b/inc/Exts/php-encryption/KeyProtectedByPassword.php @@ -0,0 +1,112 @@ +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 + * + * @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; + } +} diff --git a/inc/Exts/php-encryption/RuntimeTests.php b/inc/Exts/php-encryption/RuntimeTests.php new file mode 100644 index 00000000..9c0ba339 --- /dev/null +++ b/inc/Exts/php-encryption/RuntimeTests.php @@ -0,0 +1,242 @@ +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 + */ + 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 + */ + 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 + */ + 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 + */ + 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(); + } + } +} diff --git a/inc/Exts/phpseclib/Crypt/Base.php b/inc/Exts/phpseclib/Crypt/Base.php index 6cb35657..f8ce247b 100644 --- a/inc/Exts/phpseclib/Crypt/Base.php +++ b/inc/Exts/phpseclib/Crypt/Base.php @@ -540,7 +540,7 @@ abstract class Base * * Where $hash (default = sha1) currently supports the following hashes: see: Crypt/Hash.php * - * @see Crypt/Hash.php + * @see OldCrypt/Hash.php * @param string $password * @param string $method * @throws \LengthException if pbkdf1 is being used and the derived key length exceeds the hash length diff --git a/inc/Plugins/Authenticator/PreferencesController.class.php b/inc/Plugins/Authenticator/PreferencesController.class.php index 6b38a302..8122dc25 100644 --- a/inc/Plugins/Authenticator/PreferencesController.class.php +++ b/inc/Plugins/Authenticator/PreferencesController.class.php @@ -26,10 +26,11 @@ namespace Plugins\Authenticator; use InvalidArgumentException; use SP\Controller\TabControllerBase; -use SP\Core\Crypt; +use SP\Core\OldCrypt; use SP\Core\Plugin\PluginBase; use SP\Core\Plugin\PluginInterface; use SP\Util\ArrayUtil; +use SP\Util\Util; /** * Class Controller @@ -79,7 +80,7 @@ class PreferencesController $IV = null; if (!$AuthenticatorData->isTwofaEnabled()) { - $IV = Crypt::getIV(); + $IV = Util::generateRandomBytes(); $AuthenticatorData->setIV($IV); Session::setUserData($AuthenticatorData); diff --git a/inc/SP/Account/Account.class.php b/inc/SP/Account/Account.class.php index fd7f7015..714ca089 100644 --- a/inc/SP/Account/Account.class.php +++ b/inc/SP/Account/Account.class.php @@ -24,7 +24,7 @@ namespace SP\Account; -use SP\Core\Crypt; +use SP\Core\OldCrypt; use SP\Core\Exceptions\SPException; use SP\Core\Session; use SP\DataModel\AccountData; @@ -303,10 +303,10 @@ class Account extends AccountBase implements AccountInterface */ protected function setPasswordEncrypted($masterPass = null) { - $pass = Crypt::encryptData($this->accountData->getAccountPass(), $masterPass); + $securedKey = Crypt\Crypt::makeSecuredKey($masterPass); - $this->accountData->setAccountPass($pass['data']); - $this->accountData->setAccountIV($pass['iv']); + $this->accountData->setAccountPass(Crypt\Crypt::encrypt($this->accountData->getAccountPass(), $securedKey)); + $this->accountData->setAccountIV($securedKey); } /** @@ -381,109 +381,6 @@ class Account extends AccountBase implements AccountInterface return DB::getQuery($Data); } - /** - * Actualiza las claves de todas las cuentas con la nueva clave maestra. - * - * @param string $currentMasterPass con la clave maestra actual - * @param string $newMasterPass con la nueva clave maestra - * @param string $newHash con el nuevo hash de la clave maestra - * @return bool - * @throws \phpmailer\phpmailerException - * @throws \SP\Core\Exceptions\SPException - */ - public function updateAccountsMasterPass($currentMasterPass, $newMasterPass, $newHash = null) - { - $accountsOk = []; - $userId = Session::getUserData()->getUserId(); - $demoEnabled = Checks::demoIsEnabled(); - $errorCount = 0; - - $Log = new Log(); - $LogMessage = $Log->getLogMessage(); - $LogMessage->setAction(__('Actualizar Clave Maestra', false)); - $LogMessage->addDescription(__('Inicio', false)); - $Log->writeLog(true); - - if (!Crypt::checkCryptModule()) { - $LogMessage->addDescription(__('Error en el módulo de encriptación', false)); - $Log->setLogLevel(Log::ERROR); - $Log->writeLog(); - return false; - } - - $accountsPass = $this->getAccountsPassData(); - - if (!$accountsPass) { - $LogMessage->addDescription(__('Error al obtener las claves de las cuentas', false)); - $Log->setLogLevel(Log::ERROR); - $Log->writeLog(); - return false; - } - - foreach ($accountsPass as $account) { - $this->accountData->setAccountId($account->account_id); - $this->accountData->setAccountUserEditId($userId); - - // No realizar cambios si está en modo demo - if ($demoEnabled) { - $accountsOk[] = $this->accountData->getAccountId(); - continue; - } - - if (empty($account->account_pass)) { - $LogMessage->addDetails(__('Clave de cuenta vacía', false), sprintf(' % s(%d)', $account->account_name, $account->account_id)); - continue; - } - - if (strlen($account->account_IV) < 32) { - $LogMessage->addDetails(__('IV de encriptación incorrecto', false), sprintf(' % s(%d)', $account->account_name, $account->account_id)); - } - - $decryptedPass = Crypt::getDecrypt($account->account_pass, $account->account_IV, $currentMasterPass); - $this->accountData->setAccountPass($decryptedPass); - $this->setPasswordEncrypted($newMasterPass); - - if ($this->accountData->getAccountPass() === false) { - $errorCount++; - $LogMessage->addDetails(__('No es posible desencriptar la clave de la cuenta', false), sprintf(' % s(%d)', $account->account_name, $account->account_id)); - continue; - } - - try { - $this->updateAccountPass(true); - $accountsOk[] = $this->accountData->getAccountId(); - } catch (SPException $e) { - $errorCount++; - $LogMessage->addDetails(__('Fallo al actualizar la clave de la cuenta', false), sprintf(' % s(%d)', $account->account_name, $account->account_id)); - continue; - } - } - - $LogMessage->addDetails(__('Cuentas actualizadas', false), implode(',', $accountsOk)); - $LogMessage->addDetails(__('Errores', false), $errorCount); - $Log->writeLog(); - - Email::sendEmail($LogMessage); - - return true; - } - - /** - * Obtener los datos relativos a la clave de todas las cuentas. - * - * @return array Con los datos de la clave - */ - protected function getAccountsPassData() - { - $query = /** @lang SQL */ - 'SELECT account_id, account_name, account_pass, account_IV FROM accounts'; - - $Data = new QueryData(); - $Data->setQuery($query); - - return DB::getResultsArray($Data); - } - /** * Actualiza la clave de una cuenta en la BBDD. * diff --git a/inc/SP/Account/AccountBase.class.php b/inc/SP/Account/AccountBase.class.php index 4f7808c2..604a07f5 100644 --- a/inc/SP/Account/AccountBase.class.php +++ b/inc/SP/Account/AccountBase.class.php @@ -108,9 +108,4 @@ abstract class AccountBase * Esta funcion realiza la consulta a la BBDD y devuelve los datos. */ protected abstract function getAccountPassData(); - - /** - * Obtener los datos relativos a la clave de todas las cuentas. - */ - protected abstract function getAccountsPassData(); } \ No newline at end of file diff --git a/inc/SP/Account/AccountCrypt.php b/inc/SP/Account/AccountCrypt.php new file mode 100644 index 00000000..1594665c --- /dev/null +++ b/inc/SP/Account/AccountCrypt.php @@ -0,0 +1,221 @@ +. + */ + +namespace SP\Account; + +use SP\Core\OldCrypt; +use SP\Core\Exceptions\SPException; +use SP\Core\Session; +use SP\DataModel\AccountData; +use SP\Log\Email; +use SP\Log\Log; +use SP\Storage\DB; +use SP\Storage\QueryData; +use SP\Util\Checks; + +/** + * Class AccountCrypt + * + * @package SP\Account + */ +class AccountCrypt +{ + /** + * Actualiza las claves de todas las cuentas con la clave maestra actual + * usando nueva encriptación. + * + * @param $currentMasterPass + * @return bool + * @throws \Defuse\Crypto\Exception\BadFormatException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + */ + public function updateOldPass(&$currentMasterPass) + { + $accountsOk = []; + $userId = Session::getUserData()->getUserId(); + $demoEnabled = Checks::demoIsEnabled(); + $errorCount = 0; + + $Log = new Log(); + $LogMessage = $Log->getLogMessage(); + $LogMessage->setAction(__('Actualizar Clave Maestra', false)); + $LogMessage->addDescription(__('Inicio', false)); + $Log->writeLog(true); + + if (!OldCrypt::checkCryptModule()) { + $LogMessage->addDescription(__('Error en el módulo de encriptación', false)); + $Log->setLogLevel(Log::ERROR); + $Log->writeLog(); + return false; + } + + $accountsPass = $this->getAccountsPassData(); + + if (count($accountsPass) === 0) { + $LogMessage->addDescription(__('Error al obtener las claves de las cuentas', false)); + $Log->setLogLevel(Log::ERROR); + $Log->writeLog(); + return false; + } + + $AccountDataBase = new AccountData(); + + foreach ($accountsPass as $account) { + $AccountData = clone $AccountDataBase; + + $AccountData->setAccountId($account->account_id); + $AccountData->setAccountUserEditId($userId); + + // No realizar cambios si está en modo demo + if ($demoEnabled) { + $accountsOk[] = $account->account_id; + continue; + } elseif (empty($account->account_pass)) { + $LogMessage->addDetails(__('Clave de cuenta vacía', false), sprintf('%s (%d)', $account->account_name, $account->account_id)); + continue; + } elseif (strlen($account->account_IV) < 32) { + $LogMessage->addDetails(__('IV de encriptación incorrecto', false), sprintf('%s (%d)', $account->account_name, $account->account_id)); + } + + $decryptedPass = OldCrypt::getDecrypt($account->account_pass, $account->account_IV, $currentMasterPass); + + $securedKey = Crypt\Crypt::makeSecuredKey($currentMasterPass); + + $AccountData->setAccountPass(Crypt\Crypt::encrypt($decryptedPass, $securedKey)); + $AccountData->setAccountIV($securedKey); + + try { + $Account = new Account($AccountData); + $Account->updateAccountPass(true); + + $accountsOk[] = $account->account_id; + } catch (SPException $e) { + $errorCount++; + $LogMessage->addDetails(__('Fallo al actualizar la clave de la cuenta', false), sprintf('%s (%d)', $account->account_name, $account->account_id)); + } + } + + $LogMessage->addDetails(__('Cuentas actualizadas', false), implode(',', $accountsOk)); + $LogMessage->addDetails(__('Errores', false), $errorCount); + $Log->writeLog(); + + Email::sendEmail($LogMessage); + + return true; + } + + + /** + * Actualiza las claves de todas las cuentas con la nueva clave maestra. + * + * @param $currentMasterPass + * @param $newMasterPass + * @return bool + * @throws \Defuse\Crypto\Exception\BadFormatException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + */ + public function updatePass($currentMasterPass, $newMasterPass) + { + $accountsOk = []; + $userId = Session::getUserData()->getUserId(); + $demoEnabled = Checks::demoIsEnabled(); + $errorCount = 0; + + $Log = new Log(); + $LogMessage = $Log->getLogMessage(); + $LogMessage->setAction(__('Actualizar Clave Maestra', false)); + $LogMessage->addDescription(__('Inicio', false)); + $Log->writeLog(true); + + $accountsPass = $this->getAccountsPassData(); + + if (count($accountsPass) === 0) { + $LogMessage->addDescription(__('Error al obtener las claves de las cuentas', false)); + $Log->setLogLevel(Log::ERROR); + $Log->writeLog(); + return false; + } + + $AccountDataBase = new AccountData(); + + foreach ($accountsPass as $account) { + $AccountData = clone $AccountDataBase; + + $AccountData->setAccountId($account->account_id); + $AccountData->setAccountUserEditId($userId); + + // No realizar cambios si está en modo demo + if ($demoEnabled) { + $accountsOk[] = $account->account_id; + continue; + } elseif (empty($account->account_pass)) { + $LogMessage->addDetails(__('Clave de cuenta vacía', false), sprintf('%s (%d)', $account->account_name, $account->account_id)); + continue; + } elseif (strlen($account->account_IV) < 32) { + $LogMessage->addDetails(__('IV de encriptación incorrecto', false), sprintf('%s (%d)', $account->account_name, $account->account_id)); + } + + $currentSecuredKey = Crypt\Crypt::unlockSecuredKey($account->account_IV, $currentMasterPass); + $decryptedPass = Crypt\Crypt::decrypt($account->account_pass, $currentSecuredKey); + + $newSecuredKey = Crypt\Crypt::makeSecuredKey($newMasterPass); + $AccountData->setAccountPass(Crypt\Crypt::encrypt($decryptedPass, $newSecuredKey)); + $AccountData->setAccountIV($newSecuredKey); + + try { + $Account = new Account($AccountData); + $Account->updateAccountPass(true); + + $accountsOk[] = $account->account_id; + } catch (SPException $e) { + $errorCount++; + $LogMessage->addDetails(__('Fallo al actualizar la clave de la cuenta', false), sprintf('%s (%d)', $account->account_name, $account->account_id)); + } + } + + $LogMessage->addDetails(__('Cuentas actualizadas', false), implode(',', $accountsOk)); + $LogMessage->addDetails(__('Errores', false), $errorCount); + $Log->writeLog(); + + Email::sendEmail($LogMessage); + + return true; + } + + /** + * Obtener los datos relativos a la clave de todas las cuentas. + * + * @return array Con los datos de la clave + */ + protected function getAccountsPassData() + { + $query = /** @lang SQL */ + 'SELECT account_id, account_name, account_pass, account_IV FROM accounts'; + + $Data = new QueryData(); + $Data->setQuery($query); + + return DB::getResultsArray($Data); + } +} \ No newline at end of file diff --git a/inc/SP/Account/AccountHistory.class.php b/inc/SP/Account/AccountHistory.class.php index b9a46783..419e0110 100644 --- a/inc/SP/Account/AccountHistory.class.php +++ b/inc/SP/Account/AccountHistory.class.php @@ -25,7 +25,7 @@ namespace SP\Account; use SP\Config\ConfigDB; -use SP\Core\Crypt; +use SP\Core\OldCrypt; use SP\Core\Exceptions\SPException; use SP\Log\Log; use SP\Storage\DB; @@ -242,116 +242,6 @@ class AccountHistory extends AccountBase implements AccountInterface return DB::getQuery($Data); } - /** - * Actualiza las claves de todas las cuentas en el histórico con la nueva clave maestra. - * - * @param string $currentMasterPass con la clave maestra actual - * @param string $newMasterPass con la nueva clave maestra - * @param string $newHash con el nuevo hash de la clave maestra - * @return bool - * @throws \SP\Core\Exceptions\SPException - */ - public function updateAccountsMasterPass($currentMasterPass, $newMasterPass, $newHash = null) - { - $accountsOk = []; - $errorCount = 0; - $demoEnabled = Checks::demoIsEnabled(); - - $Log = new Log(); - $LogMessage = $Log->getLogMessage(); - $LogMessage->setAction(__('Actualizar Clave Maestra (H)', false)); - $LogMessage->addDescription(__('Inicio', false)); - $Log->writeLog(true); - - if (!Crypt::checkCryptModule()) { - $Log->setLogLevel(Log::ERROR); - $LogMessage->addDescription(__('Error en el módulo de encriptación', false)); - $Log->writeLog(); - return false; - } - - $accountsPass = $this->getAccountsPassData(); - - if (!$accountsPass) { - $Log->setLogLevel(Log::ERROR); - $LogMessage->addDescription(__('Error al obtener las claves de las cuentas', false)); - $Log->writeLog(); - return false; - } - - $AccountDataBase = new \stdClass(); - $AccountDataBase->id = 0; - $AccountDataBase->pass = ''; - $AccountDataBase->iv = ''; - $AccountDataBase->hash = $newHash; - - foreach ($accountsPass as $account) { - $AccountData = clone $AccountDataBase; - $AccountData->id = $account->acchistory_id; - - // No realizar cambios si está en modo demo - if ($demoEnabled) { - $accountsOk[] = $account->acchistory_id; - continue; - } - - if (!$this->checkAccountMPass()) { - $errorCount++; - $LogMessage->addDetails(__('La clave maestra del registro no coincide', false), sprintf('%s (%d)', $account->acchistory_name, $account->acchistory_id)); - continue; - } - - if ($account->acchistory_pass === '') { - $LogMessage->addDetails(__('Clave de cuenta vacía', false), sprintf('%s (%d)', $account->acchistory_name, $account->acchistory_id)); - continue; - } - - if (strlen($account->acchistory_IV) < 32) { - $LogMessage->addDetails(__('IV de encriptación incorrecto', false), sprintf('%s (%d)', $account->acchistory_name, $account->acchistory_id)); - } - - $decryptedPass = Crypt::getDecrypt($account->acchistory_pass, $account->acchistory_IV, $currentMasterPass); - $AccountData->pass = Crypt::mkEncrypt($decryptedPass, $newMasterPass); - $AccountData->iv = Crypt::$strInitialVector; - - if ($AccountData->pass === false) { - $errorCount++; - $LogMessage->addDetails(__('No es posible desencriptar la clave de la cuenta', false), sprintf('%s (%d)', $account->acchistory_name, $account->acchistory_id)); - continue; - } - - try { - $this->updateAccountPass($AccountData); - $accountsOk[] = $account->acchistory_id; - } catch (SPException $e) { - $errorCount++; - $LogMessage->addDetails(__('Fallo al actualizar la clave del histórico', false), sprintf('%s (%d)', $account->acchistory_name, $account->acchistory_id)); - } - } - - $LogMessage->addDetails(__('Cuentas actualizadas', false), implode(',', $accountsOk)); - $LogMessage->addDetails(__('Errores', false), $errorCount); - $Log->writeLog(); - - return true; - } - - /** - * Obtener los datos relativos a la clave de todas las cuentas del histórico. - * - * @return false|array con los datos de la clave - */ - protected function getAccountsPassData() - { - $query = /** @lang SQL */ - 'SELECT acchistory_id, acchistory_name, acchistory_pass, acchistory_IV FROM accHistory'; - - $Data = new QueryData(); - $Data->setQuery($query); - - return DB::getResultsArray($Data); - } - /** * Comprueba el hash de la clave maestra del registro de histórico de una cuenta. * diff --git a/inc/SP/Account/AccountHistoryCrypt.php b/inc/SP/Account/AccountHistoryCrypt.php new file mode 100644 index 00000000..3b816d78 --- /dev/null +++ b/inc/SP/Account/AccountHistoryCrypt.php @@ -0,0 +1,235 @@ +. + */ + +namespace SP\Account; + +use SP\Config\ConfigDB; +use SP\Core\OldCrypt; +use SP\Core\Exceptions\SPException; +use SP\Log\Email; +use SP\Log\Log; +use SP\Storage\DB; +use SP\Storage\QueryData; +use SP\Util\Checks; + +/** + * Class AccountHistoryCrypt + * + * @package SP\Account + */ +class AccountHistoryCrypt +{ + /** + * Actualiza las claves de todas las cuentas con la clave maestra actual + * usando nueva encriptación. + * + * @param $currentMasterPass + * @return bool + * @throws \Defuse\Crypto\Exception\BadFormatException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + */ + public function updateOldPass(&$currentMasterPass) + { + $accountsOk = []; + $demoEnabled = Checks::demoIsEnabled(); + $errorCount = 0; + + $Log = new Log(); + $LogMessage = $Log->getLogMessage(); + $LogMessage->setAction(__('Actualizar Clave Maestra (H)', false)); + $LogMessage->addDescription(__('Inicio', false)); + $Log->writeLog(true); + + if (!OldCrypt::checkCryptModule()) { + $LogMessage->addDescription(__('Error en el módulo de encriptación', false)); + $Log->setLogLevel(Log::ERROR); + $Log->writeLog(); + return false; + } + + $accountsPass = $this->getAccountsPassData(); + + if (count($accountsPass) === 0) { + $LogMessage->addDescription(__('Error al obtener las claves de las cuentas', false)); + $Log->setLogLevel(Log::ERROR); + $Log->writeLog(); + return false; + } + + $currentMPassHash = ConfigDB::getValue('masterPwd'); + + $AccountDataBase = new \stdClass(); + $AccountDataBase->id = 0; + $AccountDataBase->pass = ''; + $AccountDataBase->iv = ''; + $AccountDataBase->hash = Crypt\Hash::hashKey($currentMasterPass); + + foreach ($accountsPass as $account) { + $AccountData = clone $AccountDataBase; + + $AccountData->id = $account->acchistory_id; + + // No realizar cambios si está en modo demo + if ($demoEnabled) { + $accountsOk[] = $account->acchistory_id; + continue; + } elseif ($account->acchistory_mPassHash !== $currentMPassHash) { + $errorCount++; + $LogMessage->addDetails(__('La clave maestra del registro no coincide', false), sprintf('%s (%d)', $account->acchistory_name, $account->acchistory_id)); + continue; + } elseif (empty($account->acchistory_pass)) { + $LogMessage->addDetails(__('Clave de cuenta vacía', false), sprintf('%s (%d)', $account->acchistory_name, $account->acchistory_id)); + continue; + } elseif (strlen($account->acchistory_IV) < 32) { + $LogMessage->addDetails(__('IV de encriptación incorrecto', false), sprintf('%s (%d)', $account->acchistory_name, $account->acchistory_id)); + } + + $decryptedPass = OldCrypt::getDecrypt($account->acchistory_pass, $account->acchistory_IV, $currentMasterPass); + + $securedKey = Crypt\Crypt::makeSecuredKey($currentMasterPass); + + $AccountData->pass = Crypt\Crypt::encrypt($decryptedPass, $securedKey); + $AccountData->iv = $securedKey; + + try { + $Account = new AccountHistory(); + $Account->updateAccountPass($AccountData); + + $accountsOk[] = $account->acchistory_id; + } catch (SPException $e) { + $errorCount++; + $LogMessage->addDetails(__('Fallo al actualizar la clave del histórico', false), sprintf('%s (%d)', $account->acchistory_name, $account->acchistory_id)); + } + } + + $LogMessage->addDetails(__('Cuentas actualizadas', false), implode(',', $accountsOk)); + $LogMessage->addDetails(__('Errores', false), $errorCount); + $Log->writeLog(); + + Email::sendEmail($LogMessage); + + return true; + } + + /** + * Obtener los datos relativos a la clave de todas las cuentas. + * + * @return array Con los datos de la clave + */ + protected function getAccountsPassData() + { + $query = /** @lang SQL */ + 'SELECT acchistory_id, acchistory_name, acchistory_pass, acchistory_IV, acchistory_mPassHash FROM accHistory'; + + $Data = new QueryData(); + $Data->setQuery($query); + + return DB::getResultsArray($Data); + } + + /** + * Actualiza las claves de todas las cuentas con la nueva clave maestra. + * + * @param $currentMasterPass + * @param $newMasterPass + * @return bool + * @throws \Defuse\Crypto\Exception\BadFormatException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + */ + public function updatePass($currentMasterPass, $newMasterPass) + { + $accountsOk = []; + $demoEnabled = Checks::demoIsEnabled(); + $errorCount = 0; + + $Log = new Log(); + $LogMessage = $Log->getLogMessage(); + $LogMessage->setAction(__('Actualizar Clave Maestra (H)', false)); + $LogMessage->addDescription(__('Inicio', false)); + $Log->writeLog(true); + + $accountsPass = $this->getAccountsPassData(); + + if (count($accountsPass) === 0) { + $LogMessage->addDescription(__('Error al obtener las claves de las cuentas', false)); + $Log->setLogLevel(Log::ERROR); + $Log->writeLog(); + return false; + } + + $currentMPassHash = ConfigDB::getValue('masterPwd'); + + $AccountDataBase = new \stdClass(); + $AccountDataBase->id = 0; + $AccountDataBase->pass = ''; + $AccountDataBase->iv = ''; + $AccountDataBase->hash = Crypt\Hash::hashKey($newMasterPass); + + foreach ($accountsPass as $account) { + $AccountData = clone $AccountDataBase; + + $AccountData->id = $account->acchistory_id; + + // No realizar cambios si está en modo demo + if ($demoEnabled) { + $accountsOk[] = $account->acchistory_id; + continue; + } elseif ($account->acchistory_mPassHash !== $currentMPassHash) { + $errorCount++; + $LogMessage->addDetails(__('La clave maestra del registro no coincide', false), sprintf('%s (%d)', $account->acchistory_name, $account->acchistory_id)); + continue; + } elseif (empty($account->acchistory_pass)) { + $LogMessage->addDetails(__('Clave de cuenta vacía', false), sprintf('%s (%d)', $account->acchistory_name, $account->acchistory_id)); + continue; + } elseif (strlen($account->acchistory_IV) < 32) { + $LogMessage->addDetails(__('IV de encriptación incorrecto', false), sprintf('%s (%d)', $account->acchistory_name, $account->acchistory_id)); + } + + $currentSecuredKey = Crypt\Crypt::unlockSecuredKey($account->acchistory_IV, $currentMasterPass); + $decryptedPass = Crypt\Crypt::decrypt($account->acchistory_pass, $currentSecuredKey); + + $newSecuredKey = Crypt\Crypt::makeSecuredKey($newMasterPass); + $AccountData->acchistory_pass = Crypt\Crypt::encrypt($decryptedPass, $newSecuredKey); + $AccountData->acchistory_IV = $newSecuredKey; + + try { + $Account = new AccountHistory(); + $Account->updateAccountPass($AccountData); + + $accountsOk[] = $account->acchistory_id; + } catch (SPException $e) { + $errorCount++; + $LogMessage->addDetails(__('Fallo al actualizar la clave del histórico', false), sprintf('%s (%d)', $account->acchistory_name, $account->acchistory_id)); + } + } + + $LogMessage->addDetails(__('Cuentas actualizadas', false), implode(',', $accountsOk)); + $LogMessage->addDetails(__('Errores', false), $errorCount); + $Log->writeLog(); + + Email::sendEmail($LogMessage); + + return true; + } +} \ No newline at end of file diff --git a/inc/SP/Account/AccountInterface.class.php b/inc/SP/Account/AccountInterface.class.php index 8dba73aa..ff084266 100644 --- a/inc/SP/Account/AccountInterface.class.php +++ b/inc/SP/Account/AccountInterface.class.php @@ -51,12 +51,4 @@ interface AccountInterface * @return mixed */ public function deleteAccount($id); - - /** - * @param $currentMasterPass - * @param $newMasterPass - * @param null $newHash - * @return mixed - */ - public function updateAccountsMasterPass($currentMasterPass, $newMasterPass, $newHash = null); } \ No newline at end of file diff --git a/inc/SP/Api/SyspassApi.class.php b/inc/SP/Api/SyspassApi.class.php index 8361bc2a..15535de8 100644 --- a/inc/SP/Api/SyspassApi.class.php +++ b/inc/SP/Api/SyspassApi.class.php @@ -31,7 +31,7 @@ use SP\Account\AccountUtil; use SP\Core\Acl; use SP\Core\ActionsInterface; use SP\Core\Backup; -use SP\Core\Crypt; +use SP\Core\OldCrypt; use SP\Core\Exceptions\SPException; use SP\DataModel\AccountExtData; use SP\DataModel\CategoryData; @@ -84,9 +84,11 @@ class SyspassApi extends ApiBase $LogMessage->addDetails(__('Origen', false), 'API'); $this->Log->writeLog(); + $securedKey = Crypt\Crypt::unlockSecuredKey($AccountData->getAccountIV(), $this->mPass); + $ret = [ 'itemId' => $accountId, - 'pass' => Crypt::getDecrypt($AccountData->getAccountPass(), $AccountData->getAccountIV(), $this->mPass) + 'pass' => Crypt\Crypt::decrypt($AccountData->getAccountPass(), $securedKey) ]; if ($this->getParam('details', false, 0)) { diff --git a/inc/SP/Controller/AccountController.class.php b/inc/SP/Controller/AccountController.class.php index 9de37705..d8f00b17 100644 --- a/inc/SP/Controller/AccountController.class.php +++ b/inc/SP/Controller/AccountController.class.php @@ -34,7 +34,7 @@ use SP\Account\UserAccounts; use SP\Config\Config; use SP\Core\Acl; use SP\Core\ActionsInterface; -use SP\Core\Crypt; +use SP\Core\OldCrypt; use SP\Core\Exceptions\SPException; use SP\Core\Init; use SP\Core\Session; @@ -149,10 +149,12 @@ class AccountController extends ControllerBase implements ActionsInterface $this->Account->incrementDecryptCounter(); $AccountPassData = $this->Account->getAccountPassData(); + // Obtener la llave de la clave maestra + $securedKey = Crypt\Crypt::unlockSecuredKey($PublicLinkData->getPassIV(), Config::getConfig()->getPasswordSalt() . $PublicLinkData->getLinkHash()); + // Desencriptar la clave de la cuenta - $pass = Crypt::generateAesKey($PublicLinkData->getLinkHash()); - $masterPass = Crypt::getDecrypt($PublicLinkData->getPass(), $PublicLinkData->getPassIV(), $pass); - $accountPass = Crypt::getDecrypt($AccountPassData->getAccountPass(), $AccountPassData->getAccountIV(), $masterPass); + $accountSecuredKey = Crypt\Crypt::unlockSecuredKey($AccountPassData->getAccountIV(), Crypt\Crypt::decrypt($PublicLinkData->getPass(), $securedKey)); + $accountPass = Crypt\Crypt::decrypt($AccountPassData->getAccountPass(), $accountSecuredKey); $this->view->assign('useImage', Config::getConfig()->isPublinksImageEnabled() || Config::getConfig()->isAccountPassToImage()); diff --git a/inc/SP/Controller/ConfigActionController.class.php b/inc/SP/Controller/ConfigActionController.class.php index 1486239c..9e7b52ae 100644 --- a/inc/SP/Controller/ConfigActionController.class.php +++ b/inc/SP/Controller/ConfigActionController.class.php @@ -25,12 +25,15 @@ namespace SP\Controller; use SP\Account\Account; +use SP\Account\AccountCrypt; use SP\Account\AccountHistory; +use SP\Account\AccountHistoryCrypt; use SP\Config\Config; use SP\Config\ConfigDB; use SP\Core\ActionsInterface; use SP\Core\Backup; -use SP\Core\Crypt; +use SP\Core\OldCrypt; +use SP\Core\Crypt\Hash; use SP\Core\CryptMasterPass; use SP\Core\Exceptions\SPException; use SP\Core\Init; @@ -502,7 +505,7 @@ class ConfigActionController implements ItemControllerInterface } elseif ($newMasterPass !== $newMasterPassR) { $this->JsonResponse->setDescription(__('Las claves maestras no coinciden', false)); return; - } elseif (!Crypt::checkHashPass($currentMasterPass, ConfigDB::getValue('masterPwd'), true)) { + } elseif (!Hash::checkHashKey($currentMasterPass, ConfigDB::getValue('masterPwd'))) { $this->JsonResponse->setDescription(__('La clave maestra actual no coincide', false)); return; } @@ -512,7 +515,7 @@ class ConfigActionController implements ItemControllerInterface return; } - $hashMPass = Crypt::mkHashPassword($newMasterPass); + $hashMPass = Hash::hashKey($newMasterPass); if (!$noAccountPassChange) { Util::lockApp(); @@ -522,18 +525,18 @@ class ConfigActionController implements ItemControllerInterface return; } - $Account = new Account(); + $Account = new AccountCrypt(); - if (!$Account->updateAccountsMasterPass($currentMasterPass, $newMasterPass)) { + if (!$Account->updatePass($currentMasterPass, $newMasterPass)) { DB::rollbackTransaction(); $this->JsonResponse->setDescription(__('Errores al actualizar las claves de las cuentas', false)); return; } - $AccountHistory = new AccountHistory(); + $AccountHistory = new AccountHistoryCrypt(); - if (!$AccountHistory->updateAccountsMasterPass($currentMasterPass, $newMasterPass, $hashMPass)) { + if (!$AccountHistory->updatePass($currentMasterPass, $newMasterPass)) { DB::rollbackTransaction(); $this->JsonResponse->setDescription(__('Errores al actualizar las claves de las cuentas del histórico', false)); @@ -574,6 +577,9 @@ class ConfigActionController implements ItemControllerInterface /** * Regenerar el hash de la clave maestra + * + * @throws \Defuse\Crypto\Exception\BadFormatException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException */ protected function masterPassRefreshAction() { @@ -584,7 +590,7 @@ class ConfigActionController implements ItemControllerInterface $this->LogMessage->setAction(__('Actualizar Clave Maestra', false)); - if (ConfigDB::setValue('masterPwd', Crypt::mkHashPassword(SessionUtil::getSessionMPass()))) { + if (ConfigDB::setValue('masterPwd', Hash::hashKey(Crypt\Session::getSessionKey()))) { $this->LogMessage->addDescription(__('Hash de clave maestra actualizado', false)); $this->JsonResponse->setStatus(0); diff --git a/inc/SP/Controller/ItemShowController.class.php b/inc/SP/Controller/ItemShowController.class.php index 05a246c3..8f4ec64c 100644 --- a/inc/SP/Controller/ItemShowController.class.php +++ b/inc/SP/Controller/ItemShowController.class.php @@ -31,7 +31,7 @@ use SP\Account\AccountAcl; use SP\Account\AccountHistory; use SP\Api\ApiTokensUtil; use SP\Core\ActionsInterface; -use SP\Core\Crypt; +use SP\Core\OldCrypt; use SP\Core\Exceptions\ItemException; use SP\Core\Plugin\PluginUtil; use SP\Core\Session; @@ -513,7 +513,8 @@ class ItemShowController extends ControllerBase implements ActionsInterface, Ite throw new ItemException(__('Clave maestra actualizada', false) . '
' . __('Reinicie la sesión para cambiarla', false)); } - $accountClearPass = Crypt::getDecrypt($AccountData->getAccountPass(), $AccountData->getAccountIV()); + $securedKey = Crypt\Crypt::unlockSecuredKey($AccountData->getAccountIV(), Crypt\Session::getSessionKey()); + $accountClearPass = Crypt\Crypt::decrypt($AccountData->getAccountPass(), $securedKey); if (!$isHistory) { $Account->incrementDecryptCounter(); diff --git a/inc/SP/Controller/LoginController.class.php b/inc/SP/Controller/LoginController.class.php index ecc8cb9d..53a1788f 100644 --- a/inc/SP/Controller/LoginController.class.php +++ b/inc/SP/Controller/LoginController.class.php @@ -30,6 +30,8 @@ use SP\Auth\AuthUtil; use SP\Auth\Browser\BrowserAuthData; use SP\Auth\Database\DatabaseAuthData; use SP\Auth\Ldap\LdapAuthData; +use SP\Core\Crypt\Crypt; +use SP\Core\Crypt\Session as CryptSession; use SP\Core\CryptMasterPass; use SP\Core\DiFactory; use SP\Core\Exceptions\AuthException; @@ -264,6 +266,8 @@ class LoginController * * @throws \SP\Core\Exceptions\SPException * @throws \SP\Core\Exceptions\AuthException + * @throws \Defuse\Crypto\Exception\BadFormatException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException */ protected function loadMasterPass() { @@ -284,7 +288,7 @@ class LoginController throw new AuthException(SPException::SP_INFO, __('Clave maestra incorrecta', false), '', self::STATUS_INVALID_MASTER_PASS); } else { - SessionUtil::saveSessionMPass($UserPass->getClearUserMPass()); + CryptSession::saveSessionKey($UserPass->getClearUserMPass()); $this->LogMessage->addDescription(__('Clave maestra actualizada', false)); } @@ -294,7 +298,7 @@ class LoginController throw new AuthException(SPException::SP_INFO, __('Clave maestra incorrecta', false), '', self::STATUS_INVALID_MASTER_PASS); } else { - SessionUtil::saveSessionMPass($UserPass->getClearUserMPass()); + CryptSession::saveSessionKey($UserPass->getClearUserMPass()); $this->LogMessage->addDescription(__('Clave maestra actualizada', false)); } diff --git a/inc/SP/Core/Crypt/Crypt.class.php b/inc/SP/Core/Crypt/Crypt.class.php new file mode 100644 index 00000000..2a8d7c0b --- /dev/null +++ b/inc/SP/Core/Crypt/Crypt.class.php @@ -0,0 +1,103 @@ +. + */ + +namespace SP\Core\Crypt; + +use Defuse\Crypto\Crypto; +use Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException; +use Defuse\Crypto\Key; +use Defuse\Crypto\KeyProtectedByPassword; + +/** + * Class Crypt + * + * @package SP\Core\Crypt + */ +class Crypt +{ + /** + * Encriptar datos con una clave segura + * + * @param $data + * @param $securedKey + * @return string + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + * @throws \Defuse\Crypto\Exception\BadFormatException + */ + public static function encrypt($data, $securedKey) + { + $key = Key::loadFromAsciiSafeString($securedKey); + + return Crypto::encrypt($data, $key); + } + + /** + * Desencriptar datos con una clave segura + * + * @param $data + * @param $securedKey + * @return string + * @throws \Defuse\Crypto\Exception\BadFormatException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + */ + public static function decrypt($data, $securedKey) + { + $key = Key::loadFromAsciiSafeString($securedKey); + + try { + return Crypto::decrypt($data, $key); + } catch (WrongKeyOrModifiedCiphertextException $e) { + return false; + } + + } + + /** + * Securiza una clave de seguridad + * + * @param $password + * @return string + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + */ + public static function makeSecuredKey($password) + { + return KeyProtectedByPassword::createRandomPasswordProtectedKey($password)->saveToAsciiSafeString(); + } + + /** + * @param $key + * @param $password + * @return string + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + * @throws \Defuse\Crypto\Exception\BadFormatException + */ + public static function unlockSecuredKey($key, $password) + { + try { + return KeyProtectedByPassword::loadFromAsciiSafeString($key)->unlockKey($password)->saveToAsciiSafeString(); + } catch (WrongKeyOrModifiedCiphertextException $e) { + return false; + } + } +} \ No newline at end of file diff --git a/inc/SP/Core/Crypt/Hash.class.php b/inc/SP/Core/Crypt/Hash.class.php new file mode 100644 index 00000000..fc08cd68 --- /dev/null +++ b/inc/SP/Core/Crypt/Hash.class.php @@ -0,0 +1,57 @@ +. + */ + +namespace SP\Core\Crypt; + +/** + * Class Hash + * + * @package SP\Core\Crypt + */ +class Hash +{ + /** + * Comprobar el hash de una clave. + * + * @param string $key con la clave a comprobar + * @param string $hash con el hash a comprobar + * @return bool + * @throws \SP\Core\Exceptions\SPException + */ + public static function checkHashKey($key, $hash) + { + return password_verify($key, $hash); + } + + /** + * Generar un hash de una clave criptográficamente segura + * + * @param string $key con la clave a 'hashear' + * @return string con el hash de la clave + */ + public static function hashKey($key) + { + return password_hash($key, PASSWORD_BCRYPT); + } +} \ No newline at end of file diff --git a/inc/SP/Core/Crypt/Session.class.php b/inc/SP/Core/Crypt/Session.class.php new file mode 100644 index 00000000..250827c9 --- /dev/null +++ b/inc/SP/Core/Crypt/Session.class.php @@ -0,0 +1,75 @@ +. + */ + +namespace SP\Core\Crypt; + +use SP\Core\Session as CoreSession; + +/** + * Class Session + * + * @package SP\Core\Crypt + */ +class Session +{ + /** + * Devolver la clave maestra de la sesión + * + * @return string + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + * @throws \Defuse\Crypto\Exception\BadFormatException + */ + public static function getSessionKey() + { + $securedKey = Crypt::unlockSecuredKey(CoreSession::getMPassPwd(), self::getPassword()); + + return Crypt::decrypt(CoreSession::getMPass(), $securedKey); + } + + /** + * Devolver la clave utilizada para generar la llave segura + * + * @return string + */ + private static function getPassword() + { + // FIXME + return session_id() . CoreSession::getUserData()->getUserLogin(); + } + + /** + * Guardar la clave maestra en la sesión + * + * @param $data + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + * @throws \Defuse\Crypto\Exception\BadFormatException + */ + public static function saveSessionKey($data) + { + $securedKey = Crypt::makeSecuredKey(self::getPassword()); + + CoreSession::setMPassPwd($securedKey); + CoreSession::setMPass(Crypt::encrypt($data, $securedKey)); + } +} \ No newline at end of file diff --git a/inc/SP/Core/CryptMasterPass.class.php b/inc/SP/Core/CryptMasterPass.class.php index 7e827877..4ce5eb6a 100644 --- a/inc/SP/Core/CryptMasterPass.class.php +++ b/inc/SP/Core/CryptMasterPass.class.php @@ -2,8 +2,8 @@ /** * sysPass * - * @author nuxsmin - * @link http://syspass.org + * @author nuxsmin + * @link http://syspass.org * @copyright 2012-2017, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. @@ -25,6 +25,9 @@ namespace SP\Core; use SP\Config\ConfigDB; +use SP\Core\Crypt\Session as CryptSession; +use SP\Core\Crypt\Crypt; +use SP\Core\Crypt\Hash; use SP\Log\Log; use SP\Util\Util; @@ -47,20 +50,18 @@ class CryptMasterPass * * @param int $maxTime El tiempo máximo de validez de la clave * @return bool|string + * @throws \Defuse\Crypto\Exception\BadFormatException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException */ public static function setTempMasterPass($maxTime = 14400) { // Encriptar la clave maestra con hash aleatorio generado - $randomKey = Crypt::generateAesKey(Util::generateRandomBytes()); - $pass = Crypt::mkCustomMPassEncrypt($randomKey, SessionUtil::getSessionMPass()); + $randomKey = Util::generateRandomBytes(64); + $securedKey = Crypt::makeSecuredKey($randomKey); - if (!is_array($pass)) { - return false; - } - - ConfigDB::setCacheConfigValue('tempmaster_pass', bin2hex($pass[0])); - ConfigDB::setCacheConfigValue('tempmaster_passiv', bin2hex($pass[1])); - ConfigDB::setCacheConfigValue('tempmaster_passhash', Crypt::mkHashPassword($randomKey)); + ConfigDB::setCacheConfigValue('tempmaster_pass', Crypt::encrypt(CryptSession::getSessionKey(), $securedKey)); + ConfigDB::setCacheConfigValue('tempmaster_passkey', $securedKey); + ConfigDB::setCacheConfigValue('tempmaster_passhash', Hash::hashKey($randomKey)); ConfigDB::setCacheConfigValue('tempmaster_passtime', time()); ConfigDB::setCacheConfigValue('tempmaster_maxtime', time() + $maxTime); ConfigDB::setCacheConfigValue('tempmaster_attempts', 0); @@ -97,7 +98,7 @@ class CryptMasterPass || $attempts >= self::MAX_ATTEMPTS ) { ConfigDB::setCacheConfigValue('tempmaster_pass', ''); - ConfigDB::setCacheConfigValue('tempmaster_passiv', ''); + ConfigDB::setCacheConfigValue('tempmaster_passkey', ''); ConfigDB::setCacheConfigValue('tempmaster_passhash', ''); ConfigDB::setCacheConfigValue('tempmaster_maxtime', 0); ConfigDB::setCacheConfigValue('tempmaster_attempts', 0); @@ -108,7 +109,7 @@ class CryptMasterPass return false; } - $isValid = Crypt::checkHashPass($pass, ConfigDB::getValue('tempmaster_passhash')); + $isValid = Hash::checkHashKey($pass, ConfigDB::getValue('tempmaster_passhash')); if (!$isValid) { ConfigDB::setValue('tempmaster_attempts', $attempts + 1, false); @@ -120,14 +121,15 @@ class CryptMasterPass /** * Devuelve la clave maestra que ha sido encriptada con la clave temporal * - * @param $pass string con la clave utilizada para encriptar + * @param $randomKey string con la clave utilizada para encriptar * @return string con la clave maestra desencriptada + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + * @throws \Defuse\Crypto\Exception\BadFormatException */ - public static function getTempMasterPass($pass) + public static function getTempMasterPass($randomKey) { - $passLogin = hex2bin(ConfigDB::getValue('tempmaster_pass')); - $passLoginIV = hex2bin(ConfigDB::getValue('tempmaster_passiv')); + $securedKey = Crypt::unlockSecuredKey($randomKey, ConfigDB::getValue('tempmaster_passkey')); - return Crypt::getDecrypt($passLogin, $passLoginIV, $pass); + return Crypt::decrypt(ConfigDB::getValue('tempmaster_pass'), $securedKey); } } \ No newline at end of file diff --git a/inc/SP/Core/Init.class.php b/inc/SP/Core/Init.class.php index 116eb9d9..585330f0 100644 --- a/inc/SP/Core/Init.class.php +++ b/inc/SP/Core/Init.class.php @@ -258,6 +258,10 @@ class Init */ private static function loadExtensions() { + $PluginsLoader = new \SplClassLoader('Defuse\Crypto', EXTENSIONS_PATH . DIRECTORY_SEPARATOR . 'php-encryption'); + $PluginsLoader->setPrepend(false); + $PluginsLoader->register(); + $PhpSecLoader = new \SplClassLoader('phpseclib', EXTENSIONS_PATH); $PhpSecLoader->setPrepend(false); $PhpSecLoader->register(); @@ -681,6 +685,9 @@ class Init /** * Inicialiar la sesión de usuario + * + * @throws \Defuse\Crypto\Exception\BadFormatException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException */ private static function initSession() { @@ -705,13 +712,16 @@ class Init Session::setSidStartTime(time()); Session::setStartActivity(time()); } else if (Session::getUserData()->getUserId() > 0 && time() - Session::getSidStartTime() > $sessionLifeTime / 2) { - $sessionMPass = SessionUtil::getSessionMPass(); + $sessionMPass = Crypt\Session::getSessionKey(); + session_regenerate_id(true); + + // Regenerar la clave maestra + Crypt\Session::saveSessionKey($sessionMPass); + Session::setSidStartTime(time()); // Recargar los permisos del perfil de usuario Session::setUserProfile(Profile::getItem()->getById(Session::getUserData()->getUserProfileId())); - // Regenerar la clave maestra - SessionUtil::saveSessionMPass($sessionMPass); } Session::setLastActivity(time()); diff --git a/inc/SP/Core/Installer.class.php b/inc/SP/Core/Installer.class.php index f885f646..507eb6d0 100644 --- a/inc/SP/Core/Installer.class.php +++ b/inc/SP/Core/Installer.class.php @@ -30,6 +30,7 @@ use PDOException; use SP\Config\Config; use SP\Config\ConfigData; use SP\Config\ConfigDB; +use SP\Core\Crypt\Hash; use SP\Core\Exceptions\InvalidArgumentException; use SP\Core\Exceptions\SPException; use SP\DataModel\GroupData; @@ -490,7 +491,7 @@ class Installer } // Guardar el hash de la clave maestra - ConfigDB::setCacheConfigValue('masterPwd', Crypt::mkHashPassword($this->InstallData->getMasterPassword())); + ConfigDB::setCacheConfigValue('masterPwd', Hash::hashKey($this->InstallData->getMasterPassword())); ConfigDB::setCacheConfigValue('lastupdatempass', time()); ConfigDB::writeConfig(true); diff --git a/inc/SP/Core/Crypt.class.php b/inc/SP/Core/OldCrypt.class.php similarity index 78% rename from inc/SP/Core/Crypt.class.php rename to inc/SP/Core/OldCrypt.class.php index b49fe33e..c18b71d0 100644 --- a/inc/SP/Core/Crypt.class.php +++ b/inc/SP/Core/OldCrypt.class.php @@ -36,53 +36,10 @@ defined('APP_ROOT') || die(); /** * Esta clase es la encargada de realizar el encriptado/desencriptado de claves */ -class Crypt +class OldCrypt { public static $strInitialVector; - /** - * Comprobar el hash de una clave. - * - * @param string $pwd con la clave a comprobar - * @param string $checkedHash con el hash a comprobar - * @param bool $isMPass si es la clave maestra - * @return bool - * @throws \SP\Core\Exceptions\SPException - */ - public static function checkHashPass($pwd, $checkedHash, $isMPass = false) - { - if ($isMPass) { - // Comprobar si el hash está en formato anterior a 12002 - if (strlen($checkedHash) === 128) { - $check = (hash('sha256', substr($checkedHash, 0, 64) . $pwd) === substr($checkedHash, 64, 64)); - - if ($check) { - $newHash = self::mkHashPassword($pwd); - - AccountHistory::updateAccountsMPassHash($newHash); - - ConfigDB::setValue('masterPwd', $newHash); - Log::writeNewLog(__('Aviso', false), __('Se ha regenerado el HASH de clave maestra. No es necesaria ninguna acción.', false), Log::NOTICE); - } - - return $check; - - // Hash de clave maestra anterior a 2.0.0.17013101 - } elseif (hash_equals(crypt($pwd, substr($checkedHash, 0, 72)), substr($checkedHash, 72))) { - ConfigDB::setValue('masterPwd', Crypt::mkHashPassword($pwd)); - - Log::writeNewLog(__('Aviso', false), __('Se ha regenerado el HASH de clave maestra. No es necesaria ninguna acción.', false), Log::NOTICE); - return true; - } - } - - // Timing attacks... -// usleep(mt_rand(100000, 300000)); - - // Obtener el hash de la clave y la clave para generar una clave y comparar - return hash_equals(crypt($pwd, substr($checkedHash, 0, 30)), substr($checkedHash, 30)); - } - /** * Generar un hash de una clave utilizando un salt. * @@ -206,7 +163,7 @@ class Crypt } // Comprobar el módulo de encriptación - if (!Crypt::checkCryptModule()) { + if (!OldCrypt::checkCryptModule()) { throw new SPException( SPException::SP_CRITICAL, __('Error interno', false), @@ -214,8 +171,9 @@ class Crypt ); } + // FIXME // Encriptar datos - $encData['data'] = Crypt::mkEncrypt($data, $pwd); + $encData['data'] = OldCrypt::mkEncrypt($data, $pwd); if (!empty($data) && ($encData['data'] === false || null === $encData['data'])) { throw new SPException( @@ -225,7 +183,7 @@ class Crypt ); } - $encData['iv'] = Crypt::$strInitialVector; + $encData['iv'] = OldCrypt::$strInitialVector; return $encData; } diff --git a/inc/SP/Core/SessionUtil.class.php b/inc/SP/Core/SessionUtil.class.php index 8a141d92..05f97c7b 100644 --- a/inc/SP/Core/SessionUtil.class.php +++ b/inc/SP/Core/SessionUtil.class.php @@ -27,6 +27,7 @@ namespace SP\Core; use SP\Config\Config; use SP\DataModel\UserData; use SP\Mgmt\Profiles\Profile; +use SP\Core\Crypt\Session as SessionCrypt; defined('APP_ROOT') || die(); @@ -60,27 +61,16 @@ class SessionUtil Session::setPublicKey($CryptPKI->getPublicKey()); } - /** - * Guardar la clave maestra encriptada en la sesión - * - * @param $masterPass - */ - public static function saveSessionMPass($masterPass) - { - $sessionMasterPass = Crypt::mkCustomMPassEncrypt(Crypt::generateAesKey(session_id()), $masterPass); - - Session::setMPass($sessionMasterPass[0]); - Session::setMPassIV($sessionMasterPass[1]); - } - /** * Desencriptar la clave maestra de la sesión. * * @return string con la clave maestra + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + * @throws \Defuse\Crypto\Exception\BadFormatException */ public static function getSessionMPass() { - return Crypt::getDecrypt(Session::getMPass(), Session::getMPassIV(), Crypt::generateAesKey(session_id())); + return SessionCrypt::getSessionKey(); } /** diff --git a/inc/SP/Core/Upgrade/Crypt.class.php b/inc/SP/Core/Upgrade/Crypt.class.php new file mode 100644 index 00000000..3141ad21 --- /dev/null +++ b/inc/SP/Core/Upgrade/Crypt.class.php @@ -0,0 +1,130 @@ +. + */ + +namespace SP\Core\Upgrade; + +use Defuse\Crypto\Exception\CryptoException; +use SP\Account\AccountCrypt; +use SP\Account\AccountHistory; +use SP\Account\AccountHistoryCrypt; +use SP\Config\ConfigDB; +use SP\Core\Crypt\Hash; +use SP\Core\Exceptions\SPException; +use SP\Log\Log; +use SP\Mgmt\CustomFields\CustomFieldsUtil; + +/** + * Class Crypt + * + * @package SP\Core\Upgrade + */ +class Crypt +{ + /** + * Migrar elementos encriptados + * + * @param $masterPass + * @return bool + */ + public static function migrate(&$masterPass) + { + try { + self::migrateAccounts($masterPass); + self::migrateCustomFields($masterPass); + } catch (CryptoException $e) { + return false; + } catch (SPException $e) { + return false; + } + + return true; + } + + /** + * Migrar claves de cuentas a nuevo formato + * + * @param $masterPass + * @throws \Defuse\Crypto\Exception\BadFormatException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + */ + private static function migrateAccounts(&$masterPass) + { + $AccountCrypt = new AccountCrypt(); + $AccountCrypt->updateOldPass($masterPass); + + $AccountHistoryCrypt = new AccountHistoryCrypt(); + $AccountHistoryCrypt->updateOldPass($masterPass); + } + + /** + * Migrar los datos de los campos personalizados a nuevo formato + * + * @param $masterPass + * @throws \Defuse\Crypto\Exception\BadFormatException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + * @throws \SP\Core\Exceptions\SPException + */ + private static function migrateCustomFields(&$masterPass) + { + CustomFieldsUtil::updateCustomFieldsOldCrypt($masterPass); + } + + /** + * Migrar el hash de clave maestra + * + * @param $masterPass + * @return bool + * @throws \SP\Core\Exceptions\QueryException + * @throws \SP\Core\Exceptions\ConstraintException + */ + public static function migrateHash(&$masterPass) + { + $configHashMPass = ConfigDB::getValue('masterPwd'); + + // Comprobar si el hash está en formato anterior a 12002 + if (strlen($configHashMPass) === 128) { + if (hash('sha256', substr($configHashMPass, 0, 64) . $masterPass) === substr($configHashMPass, 64, 64)) { + $newHash = Hash::hashKey($masterPass); + + AccountHistory::updateAccountsMPassHash($newHash); + + ConfigDB::setValue('masterPwd', $newHash); + Log::writeNewLog(__('Aviso', false), __('Se ha regenerado el HASH de clave maestra. No es necesaria ninguna acción.', false), Log::NOTICE); + + return true; + } + + // Hash de clave maestra anterior a 2.0.0.17013101 + } elseif (hash_equals(crypt($masterPass, substr($configHashMPass, 0, 72)), substr($configHashMPass, 72)) + || hash_equals(crypt($masterPass, substr($configHashMPass, 0, 30)), substr($configHashMPass, 30)) + ) { + ConfigDB::setValue('masterPwd', Hash::hashKey($masterPass)); + + Log::writeNewLog(__('Aviso', false), __('Se ha regenerado el HASH de clave maestra. No es necesaria ninguna acción.', false), Log::NOTICE); + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/inc/SP/Core/XmlExport.class.php b/inc/SP/Core/XmlExport.class.php index c41fb6fc..93f4b272 100644 --- a/inc/SP/Core/XmlExport.class.php +++ b/inc/SP/Core/XmlExport.class.php @@ -26,6 +26,8 @@ namespace SP\Core; use SP\Account\AccountUtil; use SP\Config\Config; +use SP\Core\Crypt\Crypt; +use SP\Core\Crypt\Hash; use SP\Core\Exceptions\SPException; use SP\DataModel\CategoryData; use SP\Log\Email; @@ -270,22 +272,22 @@ class XmlExport $nodeXML = $this->xml->saveXML($node); // Crear los datos encriptados con la información del nodo - $encrypted = Crypt::mkEncrypt($nodeXML, $this->exportPass); - $encryptedIV = Crypt::$strInitialVector; + $securedKey = Crypt::makeSecuredKey($this->exportPass); + $encrypted = Crypt::encrypt($nodeXML, $securedKey); // Buscar si existe ya un nodo para el conjunto de datos encriptados $encryptedNode = $this->root->getElementsByTagName('Encrypted')->item(0); if (!$encryptedNode instanceof \DOMElement) { $encryptedNode = $this->xml->createElement('Encrypted'); - $encryptedNode->setAttribute('hash', Crypt::mkHashPassword($this->exportPass)); + $encryptedNode->setAttribute('hash', Hash::hashKey($this->exportPass)); } // Crear el nodo hijo con los datos encriptados $encryptedData = $this->xml->createElement('Data', base64_encode($encrypted)); - $encryptedDataIV = $this->xml->createAttribute('iv'); - $encryptedDataIV->value = base64_encode($encryptedIV); + $encryptedDataIV = $this->xml->createAttribute('key'); + $encryptedDataIV->value = $securedKey; // Añadir nodos de datos $encryptedData->appendChild($encryptedDataIV); diff --git a/inc/SP/Import/ImportBase.class.php b/inc/SP/Import/ImportBase.class.php index 29c6aad0..1627e3a7 100644 --- a/inc/SP/Import/ImportBase.class.php +++ b/inc/SP/Import/ImportBase.class.php @@ -25,7 +25,7 @@ namespace SP\Import; use SP\Account\Account; -use SP\Core\Crypt; +use SP\Core\OldCrypt; use SP\Core\Exceptions\SPException; use SP\Core\Messages\LogMessage; use SP\DataModel\AccountExtData; @@ -126,7 +126,11 @@ abstract class ImportBase implements ImportInterface } if ($this->ImportParams->getImportMasterPwd() !== '') { - $pass = Crypt::getDecrypt($AccountData->getAccountPass(), $AccountData->getAccountIV(), $this->ImportParams->getImportMasterPwd()); + $securedKey = Crypt\Crypt::unlockSecuredKey($AccountData->getAccountIV(), $this->ImportParams->getImportMasterPwd()); + $pass = Crypt\Crypt::decrypt($AccountData->getAccountPass(), $securedKey); + + // TODO: importar con encriptación anterior +// $pass = Crypt::getDecrypt($AccountData->getAccountPass(), $AccountData->getAccountIV(), $this->ImportParams->getImportMasterPwd()); $AccountData->setAccountPass($pass); } diff --git a/inc/SP/Import/SyspassImport.class.php b/inc/SP/Import/SyspassImport.class.php index 22fa4a9e..21e1b246 100644 --- a/inc/SP/Import/SyspassImport.class.php +++ b/inc/SP/Import/SyspassImport.class.php @@ -2,8 +2,8 @@ /** * sysPass * - * @author nuxsmin - * @link http://syspass.org + * @author nuxsmin + * @link http://syspass.org * @copyright 2012-2017, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. @@ -24,7 +24,8 @@ namespace SP\Import; -use SP\Core\Crypt; +use SP\Core\OldCrypt; +use SP\Core\Crypt\Hash; use SP\Core\Exceptions\SPException; use SP\DataModel\AccountExtData; use SP\DataModel\CategoryData; @@ -105,16 +106,20 @@ class SyspassImport extends ImportBase { $hash = $this->xmlDOM->getElementsByTagName('Encrypted')->item(0)->getAttribute('hash'); - if ($hash !== '' && !Crypt::checkHashPass($this->ImportParams->getImportPwd(), $hash)) { + if ($hash !== '' && !Hash::checkHashKey($this->ImportParams->getImportPwd(), $hash)) { throw new SPException(SPException::SP_ERROR, __('Clave de encriptación incorrecta', false)); } foreach ($this->xmlDOM->getElementsByTagName('Data') as $node) { /** @var $node \DOMElement */ $data = base64_decode($node->nodeValue); - $iv = base64_decode($node->getAttribute('iv')); - $xmlDecrypted = Crypt::getDecrypt($data, $iv, $this->ImportParams->getImportPwd()); + if ($iv = base64_decode($node->getAttribute('iv'))) { + $xmlDecrypted = OldCrypt::getDecrypt($data, $iv, $this->ImportParams->getImportPwd()); + } else { + $securedKey = Crypt\Crypt::unlockSecuredKey($node->getAttribute('key'), $this->ImportParams->getImportPwd()); + $xmlDecrypted = Crypt\Crypt::decrypt($data, $securedKey); + } $newXmlData = new \DOMDocument(); // $newXmlData->preserveWhiteSpace = true; diff --git a/inc/SP/Mgmt/CustomFields/CustomField.class.php b/inc/SP/Mgmt/CustomFields/CustomField.class.php index daab449c..5a56c19a 100644 --- a/inc/SP/Mgmt/CustomFields/CustomField.class.php +++ b/inc/SP/Mgmt/CustomFields/CustomField.class.php @@ -26,7 +26,7 @@ namespace SP\Mgmt\CustomFields; defined('APP_ROOT') || die(); -use SP\Core\Crypt; +use SP\Core\OldCrypt; use SP\DataModel\CustomFieldData; use SP\DataModel\CustomFieldDefData; use SP\Mgmt\ItemInterface; @@ -79,7 +79,7 @@ class CustomField extends CustomFieldBase implements ItemInterface return $this->delete($this->itemData->getId()); } - $cryptData = Crypt::encryptData($this->itemData->getValue()); + $securedKey = Crypt\Crypt::makeSecuredKey(Crypt\Session::getSessionKey()); $query = /** @lang SQL */ 'UPDATE customFieldsData SET @@ -91,8 +91,8 @@ class CustomField extends CustomFieldBase implements ItemInterface $Data = new QueryData(); $Data->setQuery($query); - $Data->addParam($cryptData['data']); - $Data->addParam($cryptData['iv']); + $Data->addParam(Crypt\Crypt::encrypt($this->itemData->getValue(), $securedKey)); + $Data->addParam($securedKey); $Data->addParam($this->itemData->getModule()); $Data->addParam($this->itemData->getId()); $Data->addParam($this->itemData->getDefinitionId()); @@ -136,7 +136,7 @@ class CustomField extends CustomFieldBase implements ItemInterface return true; } - $cryptData = Crypt::encryptData($this->itemData->getValue()); + $securedKey = Crypt\Crypt::makeSecuredKey(Crypt\Session::getSessionKey()); $query = /** @lang SQL */ 'INSERT INTO customFieldsData SET @@ -151,8 +151,8 @@ class CustomField extends CustomFieldBase implements ItemInterface $Data->addParam($this->itemData->getId()); $Data->addParam($this->itemData->getModule()); $Data->addParam($this->itemData->getDefinitionId()); - $Data->addParam($cryptData['data']); - $Data->addParam($cryptData['iv']); + $Data->addParam(Crypt\Crypt::encrypt($this->itemData->getValue(), $securedKey)); + $Data->addParam($securedKey); return DB::getQuery($Data); } @@ -248,7 +248,9 @@ class CustomField extends CustomFieldBase implements ItemInterface protected function unencryptData(CustomFieldData $CustomFieldData) { if ($CustomFieldData->getCustomfielddataData() !== '') { - return $this->formatValue(Crypt::getDecrypt($CustomFieldData->getCustomfielddataData(), $CustomFieldData->getCustomfielddataIv())); + $securedKey = Crypt\Crypt::unlockSecuredKey($CustomFieldData->getCustomfielddataIv(), Crypt\Session::getSessionKey()); + + return $this->formatValue(Crypt\Crypt::decrypt($CustomFieldData->getCustomfielddataData(), $securedKey)); } return ''; diff --git a/inc/SP/Mgmt/CustomFields/CustomFieldsUtil.class.php b/inc/SP/Mgmt/CustomFields/CustomFieldsUtil.class.php index 361fa007..22e2ccab 100644 --- a/inc/SP/Mgmt/CustomFields/CustomFieldsUtil.class.php +++ b/inc/SP/Mgmt/CustomFields/CustomFieldsUtil.class.php @@ -26,7 +26,7 @@ namespace SP\Mgmt\CustomFields; defined('APP_ROOT') || die(); -use SP\Core\Crypt; +use SP\Core\OldCrypt; use SP\Core\Exceptions\SPException; use SP\DataModel\CustomFieldData; use SP\DataModel\CustomFieldDefData; @@ -60,6 +60,8 @@ class CustomFieldsUtil * @param string $currentMasterPass La clave maestra actual * @param string $newMasterPassword La nueva clave maestra * @return bool + * @throws \Defuse\Crypto\Exception\BadFormatException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException * @throws \SP\Core\Exceptions\SPException */ public static function updateCustomFieldsCrypt($currentMasterPass, $newMasterPassword) @@ -90,8 +92,10 @@ class CustomFieldsUtil $success = []; foreach ($queryRes as $CustomField) { - $fieldData = Crypt::getDecrypt($CustomField->getCustomfielddataData(), $CustomField->getCustomfielddataIv(), $currentMasterPass); - $fieldCryptData = Crypt::encryptData($fieldData, $newMasterPassword); + $currentSecuredKey = Crypt\Crypt::unlockSecuredKey($CustomField->getCustomfielddataIv(), $currentMasterPass); + $fieldData = Crypt\Crypt::decrypt($CustomField->getCustomfielddataData(), $currentSecuredKey); + + $securedKey = Crypt\Crypt::makeSecuredKey($newMasterPassword); $query = /** @lang SQL */ 'UPDATE customFieldsData SET @@ -101,8 +105,76 @@ class CustomFieldsUtil $Data = new QueryData(); $Data->setQuery($query); - $Data->addParam($fieldCryptData['data']); - $Data->addParam($fieldCryptData['iv']); + $Data->addParam(Crypt\Crypt::encrypt($fieldData, $securedKey)); + $Data->addParam($securedKey); + $Data->addParam($CustomField->getCustomfielddataId()); + + try { + DB::getQuery($Data); + + $success[] = $CustomField->getCustomfielddataId(); + } catch (SPException $e) { + $errors[] = $CustomField->getCustomfielddataId(); + } + } + + $LogMessage->addDetails(__('Registros no actualizados', false), implode(',', $errors)); + $LogMessage->addDetails(__('Registros actualizados', false), implode(',', $success)); + $Log->writeLog(); + + return (count($errors) === 0); + } + + /** + * Actualizar los datos encriptados con una nueva clave + * + * @param string $currentMasterPass La clave maestra actual + * @return bool + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + * @throws \Defuse\Crypto\Exception\BadFormatException + * @throws \SP\Core\Exceptions\SPException + */ + public static function updateCustomFieldsOldCrypt(&$currentMasterPass) + { + $Log = new Log(); + $LogMessage = $Log->getLogMessage(); + $LogMessage->setAction(__('Campos Personalizados', false)); + + $query = /** @lang SQL */ + 'SELECT customfielddata_id, customfielddata_data, customfielddata_iv FROM customFieldsData'; + + $Data = new QueryData(); + $Data->setMapClassName(CustomFieldData::class); + $Data->setQuery($query); + + /** @var CustomFieldData[] $queryRes */ + $queryRes = DB::getResultsArray($Data); + + if (count($queryRes) === 0) { + $LogMessage->addDescription(__('No hay datos de campos personalizados', false)); + $Log->writeLog(); + return true; + } + + $LogMessage->addDescription(__('Actualizando datos encriptados', false)); + + $errors = []; + $success = []; + + foreach ($queryRes as $CustomField) { + $securedKey = Crypt\Crypt::makeSecuredKey($currentMasterPass); + $fieldData = OldCrypt::getDecrypt($CustomField->getCustomfielddataData(), $CustomField->getCustomfielddataIv(), $currentMasterPass); + + $query = /** @lang SQL */ + 'UPDATE customFieldsData SET + customfielddata_data = ?, + customfielddata_iv = ? + WHERE customfielddata_id = ?'; + + $Data = new QueryData(); + $Data->setQuery($query); + $Data->addParam(Crypt\Crypt::encrypt($fieldData, $securedKey)); + $Data->addParam($securedKey); $Data->addParam($CustomField->getCustomfielddataId()); try { diff --git a/inc/SP/Mgmt/PublicLinks/PublicLinkBase.class.php b/inc/SP/Mgmt/PublicLinks/PublicLinkBase.class.php index e500a0f8..5b9b6793 100644 --- a/inc/SP/Mgmt/PublicLinks/PublicLinkBase.class.php +++ b/inc/SP/Mgmt/PublicLinks/PublicLinkBase.class.php @@ -27,12 +27,13 @@ namespace SP\Mgmt\PublicLinks; defined('APP_ROOT') || die(); use SP\Config\Config; -use SP\Core\Crypt; +use SP\Core\OldCrypt; use SP\Core\Exceptions\SPException; use SP\Core\SessionUtil; use SP\DataModel\PublicLinkData; use SP\Mgmt\ItemBase; use SP\DataModel\PublicLinkBaseData; +use SP\Util\Util; /** * Class PublicLinks para la gestión de enlaces públicos @@ -71,14 +72,15 @@ abstract class PublicLinkBase extends ItemBase * Devolver la clave y el IV para el enlace * * @throws SPException + * @throws \Defuse\Crypto\Exception\BadFormatException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException */ protected final function createLinkPass() { - $pass = Crypt::generateAesKey($this->createLinkHash()); - $cryptPass = Crypt::encryptData(SessionUtil::getSessionMPass(), $pass); + $securedKey = Crypt\Crypt::makeSecuredKey(Config::getConfig()->getPasswordSalt() . $this->createLinkHash()); - $this->itemData->setPass($cryptPass['data']); - $this->itemData->setPassIV($cryptPass['iv']); + $this->itemData->setPass(Crypt\Crypt::encrypt(Crypt\Session::getSessionKey(), $securedKey)); + $this->itemData->setPassIV($securedKey); } /** diff --git a/inc/SP/Mgmt/Users/UserPass.class.php b/inc/SP/Mgmt/Users/UserPass.class.php index 9efe2b23..e7612490 100644 --- a/inc/SP/Mgmt/Users/UserPass.class.php +++ b/inc/SP/Mgmt/Users/UserPass.class.php @@ -27,7 +27,8 @@ namespace SP\Mgmt\Users; defined('APP_ROOT') || die(); use SP\Config\ConfigDB; -use SP\Core\Crypt; +use SP\Core\OldCrypt; +use SP\Core\Crypt\Hash; use SP\Core\Exceptions\SPException; use SP\Core\SessionUtil; use SP\DataModel\UserPassData; @@ -165,15 +166,16 @@ class UserPass extends UserBase */ public static function makeUserPassHash($userPass) { - $salt = Crypt::makeHashSalt(); - - return ['salt' => $salt, 'pass' => crypt($userPass, $salt)]; + return ['salt' => '', 'pass' => Hash::hashKey($userPass)]; } /** * Comprueba la clave maestra del usuario. * * @return bool + * + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + * @throws \Defuse\Crypto\Exception\BadFormatException * @throws \SP\Core\Exceptions\SPException */ public function loadUserMPass() @@ -184,11 +186,11 @@ class UserPass extends UserBase if ($userMPass === false || empty($configHashMPass)) { return false; - // Comprobamos el hash de la clave del usuario con la guardada - } elseif (Crypt::checkHashPass($userMPass, $configHashMPass, true)) { + // Comprobamos el hash de la clave del usuario con la guardada + } elseif (Hash::checkHashKey($userMPass, $configHashMPass)) { $this->clearUserMPass = $userMPass; - SessionUtil::saveSessionMPass($userMPass); + Crypt\Session::saveSessionKey($userMPass); return true; } @@ -224,7 +226,9 @@ class UserPass extends UserBase $this->itemData->setUserMPass($queryRes->user_mPass); $this->itemData->setUserMIV($queryRes->user_mIV); - return Crypt::getDecrypt($queryRes->user_mPass, $queryRes->user_mIV, $this->getCypherPass($cypher)); + $securedKey = Crypt\Crypt::unlockSecuredKey($queryRes->user_mIV, $this->getCypherPass($cypher)); + + return Crypt\Crypt::decrypt($queryRes->user_mPass, $securedKey); } /** @@ -237,7 +241,7 @@ class UserPass extends UserBase { $pass = $cypher === null ? $this->itemData->getUserPass() : $cypher; - return Crypt::generateAesKey($pass . $this->itemData->getUserLogin()); + return Crypt\Crypt::makeSecuredKey($pass . $this->itemData->getUserLogin()); } /** @@ -280,12 +284,15 @@ class UserPass extends UserBase if ($configHashMPass === false) { return false; } elseif (null === $configHashMPass) { - $configHashMPass = Crypt::mkHashPassword($masterPwd); + $configHashMPass = Hash::hashKey($masterPwd); ConfigDB::setValue('masterPwd', $configHashMPass); } - if (Crypt::checkHashPass($masterPwd, $configHashMPass, true)) { - $cryptMPass = Crypt::mkCustomMPassEncrypt($this->getCypherPass(), $masterPwd); + if (Hash::checkHashKey($masterPwd, $configHashMPass) + || \SP\Core\Upgrade\Crypt::migrateHash($masterPwd) + ) { + $securedKey = Crypt\Crypt::makeSecuredKey($this->getCypherPass()); + $cryptMPass = Crypt\Crypt::encrypt($masterPwd, $securedKey); if (!empty($cryptMPass)) { $query = /** @lang SQL */ @@ -297,14 +304,14 @@ class UserPass extends UserBase $Data = new QueryData(); $Data->setQuery($query); - $Data->addParam($cryptMPass[0]); - $Data->addParam($cryptMPass[1]); + $Data->addParam($cryptMPass); + $Data->addParam($securedKey); $Data->addParam($this->itemData->getUserId()); $this->clearUserMPass = $masterPwd; - $this->itemData->setUserMPass($cryptMPass[0]); - $this->itemData->setUserMIV($cryptMPass[1]); + $this->itemData->setUserMPass($cryptMPass); + $this->itemData->setUserMIV($securedKey); DB::getQuery($Data); diff --git a/inc/SP/Util/Util.class.php b/inc/SP/Util/Util.class.php index 378810da..e53c7ce5 100644 --- a/inc/SP/Util/Util.class.php +++ b/inc/SP/Util/Util.class.php @@ -121,9 +121,13 @@ class Util */ public static function generateRandomBytes($length = 30) { + if (function_exists('random_bytes')) { + return bin2hex(random_bytes($length)); + } + // Try to use openssl_random_pseudo_bytes if (function_exists('openssl_random_pseudo_bytes')) { - $pseudo_byte = bin2hex(openssl_random_pseudo_bytes($length)); + $pseudo_byte = bin2hex(openssl_random_pseudo_bytes($length, $strong)); return substr($pseudo_byte, 0, $length); // Truncate it to match the length }