Completed
Push — master ( c434e9...dcf3b9 )
by Adrien
09:01
created

PasswordHasher::hashPassword()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 4
dl 0
loc 16
ccs 0
cts 0
cp 0
crap 12
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Shared;
4
5
use PhpOffice\PhpSpreadsheet\Exception;
6
use PhpOffice\PhpSpreadsheet\Worksheet\Protection;
7
8
class PasswordHasher
9
{
10
    /**
11
     * Get algorithm name for PHP.
12
     */
13
    private static function getAlgorithm(string $algorithmName): string
14
    {
15
        if (!$algorithmName) {
16
            return '';
17
        }
18 19
19
        // Mapping between algorithm name in Excel and algorithm name in PHP
20 19
        $mapping = [
21 19
            Protection::ALGORITHM_MD2 => 'md2',
22
            Protection::ALGORITHM_MD4 => 'md4',
23
            Protection::ALGORITHM_MD5 => 'md5',
24 19
            Protection::ALGORITHM_SHA_1 => 'sha1',
25 19
            Protection::ALGORITHM_SHA_256 => 'sha256',
26 18
            Protection::ALGORITHM_SHA_384 => 'sha384',
27 18
            Protection::ALGORITHM_SHA_512 => 'sha512',
28 18
            Protection::ALGORITHM_RIPEMD_128 => 'ripemd128',
29 18
            Protection::ALGORITHM_RIPEMD_160 => 'ripemd160',
30
            Protection::ALGORITHM_WHIRLPOOL => 'whirlpool',
31
        ];
32 19
33 19
        if (array_key_exists($algorithmName, $mapping)) {
34
            return $mapping[$algorithmName];
35 19
        }
36
37
        throw new Exception('Unsupported password algorithm: ' . $algorithmName);
38
    }
39
40
    /**
41
     * Create a password hash from a given string.
42
     *
43
     * This method is based on the algorithm provided by
44
     * Daniel Rentz of OpenOffice and the PEAR package
45
     * Spreadsheet_Excel_Writer by Xavier Noguer <[email protected]>.
46
     *
47
     * @param string $pPassword Password to hash
48
     */
49
    private static function defaultHashPassword(string $pPassword): string
50
    {
51
        $password = 0x0000;
52
        $charPos = 1; // char position
53
54
        // split the plain text password in its component characters
55
        $chars = preg_split('//', $pPassword, -1, PREG_SPLIT_NO_EMPTY);
56
        foreach ($chars as $char) {
57
            $value = ord($char) << $charPos++; // shifted ASCII value
58
            $rotated_bits = $value >> 15; // rotated bits beyond bit 15
59
            $value &= 0x7fff; // first 15 bits
60
            $password ^= ($value | $rotated_bits);
61
        }
62
63
        $password ^= strlen($pPassword);
64
        $password ^= 0xCE4B;
65
66
        return strtoupper(dechex($password));
67
    }
68
69
    /**
70
     * Create a password hash from a given string by a specific algorithm.
71
     *
72
     * 2.4.2.4 ISO Write Protection Method
73
     *
74
     * @see https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/1357ea58-646e-4483-92ef-95d718079d6f
75
     *
76
     * @param string $password Password to hash
77
     * @param string $algorithm Hash algorithm used to compute the password hash value
78
     * @param string $salt Pseudorandom string
79
     * @param int $spinCount Number of times to iterate on a hash of a password
80
     *
81
     * @return string Hashed password
82
     */
83
    public static function hashPassword(string $password, string $algorithm = '', string $salt = '', int $spinCount = 10000): string
84
    {
85
        $phpAlgorithm = self::getAlgorithm($algorithm);
86
        if (!$phpAlgorithm) {
87
            return self::defaultHashPassword($password);
88
        }
89
90
        $saltValue = base64_decode($salt);
91
        $encodedPassword = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8');
92
93
        $hashValue = hash($phpAlgorithm, $saltValue . $encodedPassword, true);
94
        for ($i = 0; $i < $spinCount; ++$i) {
95
            $hashValue = hash($phpAlgorithm, $hashValue . pack('L', $i), true);
96
        }
97
98
        return base64_encode($hashValue);
99
    }
100
}
101