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 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 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
|
### Fixed
|
||||||
|
|
||||||
|
|
|
@ -765,6 +765,10 @@ class Xlsx extends BaseReader
|
||||||
|
|
||||||
if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) {
|
if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) {
|
||||||
$docSheet->getProtection()->setPassword((string) $xmlSheet->sheetProtection['password'], true);
|
$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) {
|
if ($xmlSheet->protectedRanges->protectedRange) {
|
||||||
foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) {
|
foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) {
|
||||||
$docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true);
|
$docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true);
|
||||||
|
|
|
@ -4,6 +4,51 @@ namespace PhpOffice\PhpSpreadsheet\Shared;
|
||||||
|
|
||||||
class PasswordHasher
|
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.
|
* Create a password hash from a given string.
|
||||||
*
|
*
|
||||||
|
@ -15,7 +60,7 @@ class PasswordHasher
|
||||||
*
|
*
|
||||||
* @return string Hashed password
|
* @return string Hashed password
|
||||||
*/
|
*/
|
||||||
public static function hashPassword($pPassword)
|
public static function defaultHashPassword($pPassword)
|
||||||
{
|
{
|
||||||
$password = 0x0000;
|
$password = 0x0000;
|
||||||
$charPos = 1; // char position
|
$charPos = 1; // char position
|
||||||
|
@ -34,4 +79,48 @@ class PasswordHasher
|
||||||
|
|
||||||
return strtoupper(dechex($password));
|
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 = '';
|
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.
|
* Create a new Protection.
|
||||||
*/
|
*/
|
||||||
|
@ -569,6 +597,102 @@ class Protection
|
||||||
return $this;
|
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.
|
* 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());
|
$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('sheet', ($pSheet->getProtection()->getSheet() ? 'true' : 'false'));
|
||||||
$objWriter->writeAttribute('objects', ($pSheet->getProtection()->getObjects() ? 'true' : 'false'));
|
$objWriter->writeAttribute('objects', ($pSheet->getProtection()->getObjects() ? 'true' : 'false'));
|
||||||
$objWriter->writeAttribute('scenarios', ($pSheet->getProtection()->getScenarios() ? 'true' : 'false'));
|
$objWriter->writeAttribute('scenarios', ($pSheet->getProtection()->getScenarios() ? 'true' : 'false'));
|
||||||
|
|
|
@ -25,4 +25,30 @@ return [
|
||||||
'CE4B',
|
'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