Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Shared;
   4  
   5  use PhpOffice\PhpSpreadsheet\Exception as SpException;
   6  use PhpOffice\PhpSpreadsheet\Worksheet\Protection;
   7  
   8  class PasswordHasher
   9  {
  10      const MAX_PASSWORD_LENGTH = 255;
  11  
  12      /**
  13       * Get algorithm name for PHP.
  14       */
  15      private static function getAlgorithm(string $algorithmName): string
  16      {
  17          if (!$algorithmName) {
  18              return '';
  19          }
  20  
  21          // Mapping between algorithm name in Excel and algorithm name in PHP
  22          $mapping = [
  23              Protection::ALGORITHM_MD2 => 'md2',
  24              Protection::ALGORITHM_MD4 => 'md4',
  25              Protection::ALGORITHM_MD5 => 'md5',
  26              Protection::ALGORITHM_SHA_1 => 'sha1',
  27              Protection::ALGORITHM_SHA_256 => 'sha256',
  28              Protection::ALGORITHM_SHA_384 => 'sha384',
  29              Protection::ALGORITHM_SHA_512 => 'sha512',
  30              Protection::ALGORITHM_RIPEMD_128 => 'ripemd128',
  31              Protection::ALGORITHM_RIPEMD_160 => 'ripemd160',
  32              Protection::ALGORITHM_WHIRLPOOL => 'whirlpool',
  33          ];
  34  
  35          if (array_key_exists($algorithmName, $mapping)) {
  36              return $mapping[$algorithmName];
  37          }
  38  
  39          throw new SpException('Unsupported password algorithm: ' . $algorithmName);
  40      }
  41  
  42      /**
  43       * Create a password hash from a given string.
  44       *
  45       * This method is based on the spec at:
  46       * https://interoperability.blob.core.windows.net/files/MS-OFFCRYPTO/[MS-OFFCRYPTO].pdf
  47       * 2.3.7.1 Binary Document Password Verifier Derivation Method 1
  48       *
  49       * It replaces a method based on the algorithm provided by
  50       * Daniel Rentz of OpenOffice and the PEAR package
  51       * Spreadsheet_Excel_Writer by Xavier Noguer <xnoguer@rezebra.com>.
  52       *
  53       * Scrutinizer will squawk at the use of bitwise operations here,
  54       * but it should ultimately pass.
  55       *
  56       * @param string $password Password to hash
  57       */
  58      private static function defaultHashPassword(string $password): string
  59      {
  60          $verifier = 0;
  61          $pwlen = strlen($password);
  62          $passwordArray = pack('c', $pwlen) . $password;
  63          for ($i = $pwlen; $i >= 0; --$i) {
  64              $intermediate1 = (($verifier & 0x4000) === 0) ? 0 : 1;
  65              $intermediate2 = 2 * $verifier;
  66              $intermediate2 = $intermediate2 & 0x7fff;
  67              $intermediate3 = $intermediate1 | $intermediate2;
  68              $verifier = $intermediate3 ^ ord($passwordArray[$i]);
  69          }
  70          $verifier ^= 0xCE4B;
  71  
  72          return strtoupper(dechex($verifier));
  73      }
  74  
  75      /**
  76       * Create a password hash from a given string by a specific algorithm.
  77       *
  78       * 2.4.2.4 ISO Write Protection Method
  79       *
  80       * @see https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/1357ea58-646e-4483-92ef-95d718079d6f
  81       *
  82       * @param string $password Password to hash
  83       * @param string $algorithm Hash algorithm used to compute the password hash value
  84       * @param string $salt Pseudorandom string
  85       * @param int $spinCount Number of times to iterate on a hash of a password
  86       *
  87       * @return string Hashed password
  88       */
  89      public static function hashPassword(string $password, string $algorithm = '', string $salt = '', int $spinCount = 10000): string
  90      {
  91          if (strlen($password) > self::MAX_PASSWORD_LENGTH) {
  92              throw new SpException('Password exceeds ' . self::MAX_PASSWORD_LENGTH . ' characters');
  93          }
  94          $phpAlgorithm = self::getAlgorithm($algorithm);
  95          if (!$phpAlgorithm) {
  96              return self::defaultHashPassword($password);
  97          }
  98  
  99          $saltValue = base64_decode($salt);
 100          $encodedPassword = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8');
 101  
 102          $hashValue = hash($phpAlgorithm, $saltValue . $encodedPassword, true);
 103          for ($i = 0; $i < $spinCount; ++$i) {
 104              $hashValue = hash($phpAlgorithm, $hashValue . pack('L', $i), true);
 105          }
 106  
 107          return base64_encode($hashValue);
 108      }
 109  }