Skip to content

Commit 9e02941

Browse files
committed
align with pull request submitted in PHPOffice/Commom
1 parent 2e56251 commit 9e02941

File tree

4 files changed

+58
-41
lines changed

4 files changed

+58
-41
lines changed

src/PhpWord/Metadata/Protection.php

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
namespace PhpOffice\PhpWord\Metadata;
1919

2020
use PhpOffice\PhpWord\SimpleType\DocProtect;
21+
use PhpOffice\PhpWord\Shared\Microsoft\PasswordEncoder;
2122

2223
/**
2324
* Document protection class
@@ -50,11 +51,11 @@ class Protection
5051
private $spinCount = 100000;
5152

5253
/**
53-
* Cryptographic Hashing Algorithm (see to \PhpOffice\PhpWord\Writer\Word2007\Part\Settings::$algorithmMapping)
54+
* Cryptographic Hashing Algorithm (see constants defined in \PhpOffice\PhpWord\Shared\Microsoft\PasswordEncoder)
5455
*
55-
* @var int
56+
* @var string
5657
*/
57-
private $mswordAlgorithmSid = 4;
58+
private $algorithm = PasswordEncoder::ALGORITHM_SHA_1;
5859

5960
/**
6061
* Salt for Password Verifier
@@ -146,24 +147,24 @@ public function setSpinCount($spinCount)
146147
}
147148

148149
/**
149-
* Get algorithm-sid
150+
* Get algorithm
150151
*
151-
* @return int
152+
* @return string
152153
*/
153-
public function getMswordAlgorithmSid()
154+
public function getAlgorithm()
154155
{
155-
return $this->mswordAlgorithmSid;
156+
return $this->algorithm;
156157
}
157158

158159
/**
159-
* Set algorithm-sid (see \PhpOffice\PhpWord\Writer\Word2007\Part\Settings::$algorithmMapping)
160+
* Set algorithm
160161
*
161-
* @param $mswordAlgorithmSid
162+
* @param $algorithm
162163
* @return self
163164
*/
164-
public function setMswordAlgorithmSid($mswordAlgorithmSid)
165+
public function setMswordAlgorithmSid($algorithm)
165166
{
166-
$this->mswordAlgorithmSid = $mswordAlgorithmSid;
167+
$this->algorithm = $algorithm;
167168

168169
return $this;
169170
}

src/PhpWord/Shared/Microsoft/PasswordEncoder.php

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,36 @@
2222
*/
2323
class PasswordEncoder
2424
{
25+
const ALGORITHM_MD2 = 'MD2';
26+
const ALGORITHM_MD4 = 'MD4';
27+
const ALGORITHM_MD5 = 'MD5';
28+
const ALGORITHM_SHA_1 = 'SHA-1';
29+
const ALGORITHM_SHA_256 = 'SHA-256';
30+
const ALGORITHM_SHA_384 = 'SHA-384';
31+
const ALGORITHM_SHA_512 = 'SHA-512';
32+
const ALGORITHM_RIPEMD = 'RIPEMD';
33+
const ALGORITHM_RIPEMD_160 = 'RIPEMD-160';
34+
const ALGORITHM_MAC = 'MAC';
35+
const ALGORITHM_HMAC= 'HMAC';
36+
37+
/**
38+
* Mapping between algorithm name and algorithm ID
39+
*
40+
* @var array
41+
* @see https://msdn.microsoft.com/en-us/library/documentformat.openxml.wordprocessing.writeprotection.cryptographicalgorithmsid(v=office.14).aspx
42+
*/
2543
private static $algorithmMapping = array(
26-
1 => 'md2',
27-
2 => 'md4',
28-
3 => 'md5',
29-
4 => 'sha1',
30-
5 => '', // 'mac' -> not possible with hash()
31-
6 => 'ripemd',
32-
7 => 'ripemd160',
33-
8 => '',
34-
9 => '', //'hmac' -> not possible with hash()
35-
10 => '',
36-
11 => '',
37-
12 => 'sha256',
38-
13 => 'sha384',
39-
14 => 'sha512',
44+
self::ALGORITHM_MD2 => array(1, 'md2'),
45+
self::ALGORITHM_MD4 => array(2, 'md4'),
46+
self::ALGORITHM_MD5 => array(3, 'md5'),
47+
self::ALGORITHM_SHA_1 => array(4, 'sha1'),
48+
self::ALGORITHM_MAC => array(5, ''), // 'mac' -> not possible with hash()
49+
self::ALGORITHM_RIPEMD => array(6, 'ripemd'),
50+
self::ALGORITHM_RIPEMD_160 => array(7, 'ripemd160'),
51+
self::ALGORITHM_HMAC => array(9, ''), //'hmac' -> not possible with hash()
52+
self::ALGORITHM_SHA_256 => array(12, 'sha256'),
53+
self::ALGORITHM_SHA_384 => array(13, 'sha384'),
54+
self::ALGORITHM_SHA_512 => array(14, 'sha512'),
4055
);
4156

4257
private static $initialCodeArray = array(
@@ -82,12 +97,12 @@ class PasswordEncoder
8297
* @see https://blogs.msdn.microsoft.com/vsod/2010/04/05/how-to-set-the-editing-restrictions-in-word-using-open-xml-sdk-2-0/
8398
*
8499
* @param string $password
85-
* @param number $algorithmSid
100+
* @param string $algorithmName
86101
* @param string $salt
87-
* @param number $spinCount
102+
* @param integer $spinCount
88103
* @return string
89104
*/
90-
public static function hashPassword($password, $algorithmSid = 4, $salt = null, $spinCount = 10000)
105+
public static function hashPassword($password, $algorithmName = PasswordEncoder::ALGORITHM_SHA_1, $salt = null, $spinCount = 10000)
91106
{
92107
$origEncoding = mb_internal_encoding();
93108
mb_internal_encoding('UTF-8');
@@ -118,7 +133,7 @@ public static function hashPassword($password, $algorithmSid = 4, $salt = null,
118133
// Implementation Notes List:
119134
// Word requires that the initial hash of the password with the salt not be considered in the count.
120135
// The initial hash of salt + key is not included in the iteration count.
121-
$algorithm = self::getAlgorithm($algorithmSid);
136+
$algorithm = self::getAlgorithm($algorithmName);
122137
$generatedKey = hash($algorithm, $salt . $generatedKey, true);
123138

124139
for ($i = 0; $i < $spinCount; $i++) {
@@ -134,12 +149,12 @@ public static function hashPassword($password, $algorithmSid = 4, $salt = null,
134149
/**
135150
* Get algorithm from self::$algorithmMapping
136151
*
137-
* @param int $sid
152+
* @param string $algorithmName
138153
* @return string
139154
*/
140-
private static function getAlgorithm($sid)
155+
private static function getAlgorithm($algorithmName)
141156
{
142-
$algorithm = self::$algorithmMapping[$sid];
157+
$algorithm = self::$algorithmMapping[$algorithmName][1];
143158
if ($algorithm == '') {
144159
$algorithm = 'sha1';
145160
}
@@ -155,16 +170,17 @@ private static function getAlgorithm($sid)
155170
*/
156171
private static function buildCombinedKey($byteChars)
157172
{
173+
$byteCharsLength = count($byteChars);
158174
// Compute the high-order word
159175
// Initialize from the initial code array (see above), depending on the passwords length.
160-
$highOrderWord = self::$initialCodeArray[count($byteChars) - 1];
176+
$highOrderWord = self::$initialCodeArray[$byteCharsLength - 1];
161177

162178
// For each character in the password:
163179
// For every bit in the character, starting with the least significant and progressing to (but excluding)
164180
// the most significant, if the bit is set, XOR the key’s high-order word with the corresponding word from
165181
// the Encryption Matrix
166-
for ($i = 0; $i < count($byteChars); $i++) {
167-
$tmp = self::$passwordMaxLength - count($byteChars) + $i;
182+
for ($i = 0; $i < $byteCharsLength; $i++) {
183+
$tmp = self::$passwordMaxLength - $byteCharsLength + $i;
168184
$matrixRow = self::$encryptionMatrix[$tmp];
169185
for ($intBit = 0; $intBit < 7; $intBit++) {
170186
if (($byteChars[$i] & (0x0001 << $intBit)) != 0) {
@@ -177,12 +193,12 @@ private static function buildCombinedKey($byteChars)
177193
// Initialize with 0
178194
$lowOrderWord = 0;
179195
// For each character in the password, going backwards
180-
for ($i = count($byteChars) - 1; $i >= 0; $i--) {
196+
for ($i = $byteCharsLength - 1; $i >= 0; $i--) {
181197
// low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR character
182198
$lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteChars[$i]);
183199
}
184200
// Lastly, low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR strPassword length XOR 0xCE4B.
185-
$lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ count($byteChars) ^ 0xCE4B);
201+
$lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteCharsLength ^ 0xCE4B);
186202

187203
// Combine the Low and High Order Word
188204
return self::int32(($highOrderWord << 16) + $lowOrderWord);

src/PhpWord/Writer/Word2007/Part/Settings.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ private function setDocumentProtection($documentProtection)
193193
if ($documentProtection->getSalt() == null) {
194194
$documentProtection->setSalt(openssl_random_pseudo_bytes(16));
195195
}
196-
$passwordHash = PasswordEncoder::hashPassword($documentProtection->getPassword(), $documentProtection->getMswordAlgorithmSid(), $documentProtection->getSalt(), $documentProtection->getSpinCount());
196+
$passwordHash = PasswordEncoder::hashPassword($documentProtection->getPassword(), $documentProtection->getAlgorithm(), $documentProtection->getSalt(), $documentProtection->getSpinCount());
197197
$this->settings['w:documentProtection'] = array(
198198
'@attributes' => array(
199199
'w:enforcement' => 1,

tests/PhpWord/Shared/Microsoft/PasswordEncoderTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public function testEncodePasswordWithSalt()
5151
$salt = base64_decode('uq81pJRRGFIY5U+E9gt8tA==');
5252

5353
//when
54-
$hashPassword = PasswordEncoder::hashPassword($password, 4, $salt);
54+
$hashPassword = PasswordEncoder::hashPassword($password, PasswordEncoder::ALGORITHM_SHA_1, $salt);
5555

5656
//then
5757
TestCase::assertEquals('QiDOcpia1YzSVJPiKPwWebl9p/0=', $hashPassword);
@@ -67,7 +67,7 @@ public function testDafaultsToSha1IfUnsupportedAlgorithm()
6767
$salt = base64_decode('uq81pJRRGFIY5U+E9gt8tA==');
6868

6969
//when
70-
$hashPassword = PasswordEncoder::hashPassword($password, 5, $salt);
70+
$hashPassword = PasswordEncoder::hashPassword($password, PasswordEncoder::ALGORITHM_MAC, $salt);
7171

7272
//then
7373
TestCase::assertEquals('QiDOcpia1YzSVJPiKPwWebl9p/0=', $hashPassword);
@@ -83,7 +83,7 @@ public function testEncodePasswordWithNullAsciiCodeInPassword()
8383
$salt = base64_decode('uq81pJRRGFIY5U+E9gt8tA==');
8484

8585
//when
86-
$hashPassword = PasswordEncoder::hashPassword($password, 5, $salt, 1);
86+
$hashPassword = PasswordEncoder::hashPassword($password, PasswordEncoder::ALGORITHM_MAC, $salt, 1);
8787

8888
//then
8989
TestCase::assertEquals('rDV9sgdDsztoCQlvRCb1lF2wxNg=', $hashPassword);

0 commit comments

Comments
 (0)