Add support protection of worksheet by a specific hash algorithm
This commit is contained in:
parent
c434e9b137
commit
dfa6f77178
|
@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
|||
|
||||
- Support writing to streams in all writers [#1292](https://github.com/PHPOffice/PhpSpreadsheet/issues/1292)
|
||||
- Support CSV files with data wrapping a lot of lines [#1468](https://github.com/PHPOffice/PhpSpreadsheet/pull/1468)
|
||||
- Support protection of worksheet by a specific hash algorithm
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
@ -765,6 +765,10 @@ class Xlsx extends BaseReader
|
|||
|
||||
if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) {
|
||||
$docSheet->getProtection()->setPassword((string) $xmlSheet->sheetProtection['password'], true);
|
||||
$docSheet->getProtection()->setAlgorithmName((string) $xmlSheet->sheetProtection['algorithmName']);
|
||||
$docSheet->getProtection()->setHashValue((string) $xmlSheet->sheetProtection['hashValue']);
|
||||
$docSheet->getProtection()->setSaltValue((string) $xmlSheet->sheetProtection['saltValue']);
|
||||
$docSheet->getProtection()->setSpinCount((int) $xmlSheet->sheetProtection['spinCount']);
|
||||
if ($xmlSheet->protectedRanges->protectedRange) {
|
||||
foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) {
|
||||
$docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true);
|
||||
|
|
|
@ -4,6 +4,51 @@ namespace PhpOffice\PhpSpreadsheet\Shared;
|
|||
|
||||
class PasswordHasher
|
||||
{
|
||||
const ALGORITHM_MD2 = 'MD2';
|
||||
const ALGORITHM_MD4 = 'MD4';
|
||||
const ALGORITHM_MD5 = 'MD5';
|
||||
const ALGORITHM_SHA_1 = 'SHA-1';
|
||||
const ALGORITHM_SHA_256 = 'SHA-256';
|
||||
const ALGORITHM_SHA_384 = 'SHA-384';
|
||||
const ALGORITHM_SHA_512 = 'SHA-512';
|
||||
const ALGORITHM_RIPEMD_128 = 'RIPEMD-128';
|
||||
const ALGORITHM_RIPEMD_160 = 'RIPEMD-160';
|
||||
const ALGORITHM_WHIRLPOOL = 'WHIRLPOOL';
|
||||
|
||||
/**
|
||||
* Mapping between algorithm name in Excel and algorithm name in PHP.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $algorithmArray = [
|
||||
self::ALGORITHM_MD2 => 'md2',
|
||||
self::ALGORITHM_MD4 => 'md4',
|
||||
self::ALGORITHM_MD5 => 'md5',
|
||||
self::ALGORITHM_SHA_1 => 'sha1',
|
||||
self::ALGORITHM_SHA_256 => 'sha256',
|
||||
self::ALGORITHM_SHA_384 => 'sha384',
|
||||
self::ALGORITHM_SHA_512 => 'sha512',
|
||||
self::ALGORITHM_RIPEMD_128 => 'ripemd128',
|
||||
self::ALGORITHM_RIPEMD_160 => 'ripemd160',
|
||||
self::ALGORITHM_WHIRLPOOL => 'whirlpool',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get algorithm from self::$algorithmArray.
|
||||
*
|
||||
* @param string $pAlgorithmName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getAlgorithm($pAlgorithmName)
|
||||
{
|
||||
if (array_key_exists($pAlgorithmName, self::$algorithmArray)) {
|
||||
return self::$algorithmArray[$pAlgorithmName];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a password hash from a given string.
|
||||
*
|
||||
|
@ -15,7 +60,7 @@ class PasswordHasher
|
|||
*
|
||||
* @return string Hashed password
|
||||
*/
|
||||
public static function hashPassword($pPassword)
|
||||
public static function defaultHashPassword($pPassword)
|
||||
{
|
||||
$password = 0x0000;
|
||||
$charPos = 1; // char position
|
||||
|
@ -34,4 +79,48 @@ class PasswordHasher
|
|||
|
||||
return strtoupper(dechex($password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a password hash from a given string by a specific algorithm.
|
||||
*
|
||||
* 2.4.2.4 ISO Write Protection Method
|
||||
*
|
||||
* @see https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/1357ea58-646e-4483-92ef-95d718079d6f
|
||||
*
|
||||
* @param string $pPassword Password to hash
|
||||
* @param string $pAlgorithmName Hash algorithm used to compute the password hash value
|
||||
* @param string $pSaltValue Pseudorandom string
|
||||
* @param string $pSpinCount Number of times to iterate on a hash of a password
|
||||
*
|
||||
* @return string Hashed password
|
||||
*/
|
||||
public static function hashPassword($pPassword, $pAlgorithmName = '', $pSaltValue = '', $pSpinCount = 10000)
|
||||
{
|
||||
$algorithmName = self::getAlgorithm($pAlgorithmName);
|
||||
if (!$pAlgorithmName) {
|
||||
return self::defaultHashPassword($pPassword);
|
||||
}
|
||||
|
||||
$saltValue = base64_decode($pSaltValue);
|
||||
$password = mb_convert_encoding($pPassword, 'UCS-2LE', 'UTF-8');
|
||||
|
||||
$hashValue = hash($algorithmName, $saltValue . $password, true);
|
||||
for ($i = 0; $i < $pSpinCount; ++$i) {
|
||||
$hashValue = hash($algorithmName, $hashValue . pack('L', $i), true);
|
||||
}
|
||||
|
||||
return base64_encode($hashValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a pseudorandom string.
|
||||
*
|
||||
* @param int $pSize Length of the output string in bytes
|
||||
*
|
||||
* @return string Pseudorandom string
|
||||
*/
|
||||
public static function generateSalt($pSize = 16)
|
||||
{
|
||||
return base64_encode(random_bytes($pSize));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,6 +125,34 @@ class Protection
|
|||
*/
|
||||
private $password = '';
|
||||
|
||||
/**
|
||||
* Algorithm name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $algorithmName = '';
|
||||
|
||||
/**
|
||||
* Hash value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $hashValue = '';
|
||||
|
||||
/**
|
||||
* Salt value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $saltValue = '';
|
||||
|
||||
/**
|
||||
* Spin count.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $spinCount = '';
|
||||
|
||||
/**
|
||||
* Create a new Protection.
|
||||
*/
|
||||
|
@ -569,6 +597,102 @@ class Protection
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get AlgorithmName.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAlgorithmName()
|
||||
{
|
||||
return $this->algorithmName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set AlgorithmName.
|
||||
*
|
||||
* @param string $pValue
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAlgorithmName($pValue)
|
||||
{
|
||||
$this->algorithmName = $pValue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HashValue.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHashValue()
|
||||
{
|
||||
return $this->hashValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set HashValue.
|
||||
*
|
||||
* @param string $pValue
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setHashValue($pValue)
|
||||
{
|
||||
$this->hashValue = $pValue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SaltValue.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSaltValue()
|
||||
{
|
||||
return $this->saltValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SaltValue.
|
||||
*
|
||||
* @param string $pValue
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSaltValue($pValue)
|
||||
{
|
||||
$this->saltValue = $pValue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SpinCount.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSpinCount()
|
||||
{
|
||||
return $this->spinCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SpinCount.
|
||||
*
|
||||
* @param int $pValue
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSpinCount($pValue)
|
||||
{
|
||||
$this->spinCount = $pValue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement PHP __clone to create a deep clone, not just a shallow copy.
|
||||
*/
|
||||
|
|
|
@ -424,6 +424,22 @@ class Worksheet extends WriterPart
|
|||
$objWriter->writeAttribute('password', $pSheet->getProtection()->getPassword());
|
||||
}
|
||||
|
||||
if ($pSheet->getProtection()->getHashValue() !== '') {
|
||||
$objWriter->writeAttribute('hashValue', $pSheet->getProtection()->getHashValue());
|
||||
}
|
||||
|
||||
if ($pSheet->getProtection()->getAlgorithmName() !== '') {
|
||||
$objWriter->writeAttribute('algorithmName', $pSheet->getProtection()->getAlgorithmName());
|
||||
}
|
||||
|
||||
if ($pSheet->getProtection()->getSaltValue() !== '') {
|
||||
$objWriter->writeAttribute('saltValue', $pSheet->getProtection()->getSaltValue());
|
||||
}
|
||||
|
||||
if ($pSheet->getProtection()->getSpinCount() !== '') {
|
||||
$objWriter->writeAttribute('spinCount', $pSheet->getProtection()->getSpinCount());
|
||||
}
|
||||
|
||||
$objWriter->writeAttribute('sheet', ($pSheet->getProtection()->getSheet() ? 'true' : 'false'));
|
||||
$objWriter->writeAttribute('objects', ($pSheet->getProtection()->getObjects() ? 'true' : 'false'));
|
||||
$objWriter->writeAttribute('scenarios', ($pSheet->getProtection()->getScenarios() ? 'true' : 'false'));
|
||||
|
|
|
@ -25,4 +25,30 @@ return [
|
|||
'CE4B',
|
||||
'',
|
||||
],
|
||||
[
|
||||
'O6EXRLpLEDNJDL/AzYtnnA4O4bY=',
|
||||
'',
|
||||
'SHA-1',
|
||||
],
|
||||
[
|
||||
'GYvlIMljDI1Czc4jfWrGaxU5pxl9n5Og0KUzyAfYxwk=',
|
||||
'PhpSpreadsheet',
|
||||
'SHA-256',
|
||||
'Php_salt',
|
||||
1000,
|
||||
],
|
||||
[
|
||||
'sSHdxQv9qgpkr4LDT0bYQxM9hOQJFRhJ4D752/NHQtDDR1EVy67NCEW9cPd6oWvCoBGd96MqKpuma1A7pN1nEA==',
|
||||
'Mark Baker',
|
||||
'SHA-512',
|
||||
'Mark_salt',
|
||||
10000,
|
||||
],
|
||||
[
|
||||
'r9KVLLCKIYOILvE2rcby+g==',
|
||||
'!+&=()~§±æþ',
|
||||
'MD5',
|
||||
'Symbols_salt',
|
||||
100000,
|
||||
],
|
||||
];
|
||||
|
|
Loading…
Reference in New Issue