Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@
'modernize_strpos' => true,
'modernize_types_casting' => true,
'modifier_keywords' => ['elements' => ['property', 'method']], // not const

'multiline_comment_opening_closing' => true,
'multiline_whitespace_before_semicolons' => true,
'native_constant_invocation' => false, // Micro optimization that look messy
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). Thia is a
- Missing array keys x,o,v for Xml Reader. [Issue #4668](https://github.com/PHPOffice/PhpSpreadsheet/issues/4668) [PR #4669](https://github.com/PHPOffice/PhpSpreadsheet/pull/4669)
- Missing array key x for Xlsx Reader VML. [Issue #4505](https://github.com/PHPOffice/PhpSpreadsheet/issues/4505) [PR #4676](https://github.com/PHPOffice/PhpSpreadsheet/pull/4676)
- Better support for Style Alignment Read Order. [Issue #850](https://github.com/PHPOffice/PhpSpreadsheet/issues/850) [PR #4655](https://github.com/PHPOffice/PhpSpreadsheet/pull/4655)
- More sophisticated workbook password algorithms (Xlsx only). [Issue #4673](https://github.com/PHPOffice/PhpSpreadsheet/issues/4673) [PR #4675](https://github.com/PHPOffice/PhpSpreadsheet/pull/4675)

## 2025-09-03 - 5.1.0

Expand Down
141 changes: 129 additions & 12 deletions src/PhpSpreadsheet/Document/Security.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,21 @@ class Security
*/
private string $workbookPassword = '';

/**
* Create a new Document Security instance.
*/
public function __construct()
{
}
private string $workbookAlgorithmName = '';

private string $workbookHashValue = '';

private string $workbookSaltValue = '';

private int $workbookSpinCount = 0;

private string $revisionsAlgorithmName = '';

private string $revisionsHashValue = '';

private string $revisionsSaltValue = '';

private int $revisionsSpinCount = 0;

/**
* Is some sort of document security enabled?
Expand Down Expand Up @@ -105,10 +114,18 @@ public function getRevisionsPassword(): string
public function setRevisionsPassword(?string $password, bool $alreadyHashed = false): static
{
if ($password !== null) {
if (!$alreadyHashed) {
$password = PasswordHasher::hashPassword($password);
if ($this->advancedRevisionsPassword()) {
if (!$alreadyHashed) {
$password = PasswordHasher::hashPassword($password, $this->revisionsAlgorithmName, $this->revisionsSaltValue, $this->revisionsSpinCount);
}
$this->revisionsHashValue = $password;
$this->revisionsPassword = '';
} else {
if (!$alreadyHashed) {
$password = PasswordHasher::hashPassword($password);
}
$this->revisionsPassword = $password;
}
$this->revisionsPassword = $password;
}

return $this;
Expand All @@ -129,12 +146,112 @@ public function getWorkbookPassword(): string
public function setWorkbookPassword(?string $password, bool $alreadyHashed = false): static
{
if ($password !== null) {
if (!$alreadyHashed) {
$password = PasswordHasher::hashPassword($password);
if ($this->advancedPassword()) {
if (!$alreadyHashed) {
$password = PasswordHasher::hashPassword($password, $this->workbookAlgorithmName, $this->workbookSaltValue, $this->workbookSpinCount);
}
$this->workbookHashValue = $password;
$this->workbookPassword = '';
} else {
if (!$alreadyHashed) {
$password = PasswordHasher::hashPassword($password);
}
$this->workbookPassword = $password;
}
$this->workbookPassword = $password;
}

return $this;
}

public function getWorkbookHashValue(): string
{
return $this->advancedPassword() ? $this->workbookHashValue : '';
}

public function advancedPassword(): bool
{
return $this->workbookAlgorithmName !== '' && $this->workbookSaltValue !== '' && $this->workbookSpinCount > 0;
}

public function getWorkbookAlgorithmName(): string
{
return $this->workbookAlgorithmName;
}

public function setWorkbookAlgorithmName(string $workbookAlgorithmName): static
{
$this->workbookAlgorithmName = $workbookAlgorithmName;

return $this;
}

public function getWorkbookSpinCount(): int
{
return $this->workbookSpinCount;
}

public function setWorkbookSpinCount(int $workbookSpinCount): static
{
$this->workbookSpinCount = $workbookSpinCount;

return $this;
}

public function getWorkbookSaltValue(): string
{
return $this->workbookSaltValue;
}

public function setWorkbookSaltValue(string $workbookSaltValue, bool $base64Required): static
{
$this->workbookSaltValue = $base64Required ? base64_encode($workbookSaltValue) : $workbookSaltValue;

return $this;
}

public function getRevisionsHashValue(): string
{
return $this->advancedRevisionsPassword() ? $this->revisionsHashValue : '';
}

public function advancedRevisionsPassword(): bool
{
return $this->revisionsAlgorithmName !== '' && $this->revisionsSaltValue !== '' && $this->revisionsSpinCount > 0;
}

public function getRevisionsAlgorithmName(): string
{
return $this->revisionsAlgorithmName;
}

public function setRevisionsAlgorithmName(string $revisionsAlgorithmName): static
{
$this->revisionsAlgorithmName = $revisionsAlgorithmName;

return $this;
}

public function getRevisionsSpinCount(): int
{
return $this->revisionsSpinCount;
}

public function setRevisionsSpinCount(int $revisionsSpinCount): static
{
$this->revisionsSpinCount = $revisionsSpinCount;

return $this;
}

public function getRevisionsSaltValue(): string
{
return $this->revisionsSaltValue;
}

public function setRevisionsSaltValue(string $revisionsSaltValue, bool $base64Required): static
{
$this->revisionsSaltValue = $base64Required ? base64_encode($revisionsSaltValue) : $revisionsSaltValue;

return $this;
}
}
66 changes: 61 additions & 5 deletions src/PhpSpreadsheet/Reader/Xlsx.php
Original file line number Diff line number Diff line change
Expand Up @@ -2205,23 +2205,79 @@ private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkboo
return;
}

$excel->getSecurity()->setLockRevision(self::getLockValue($xmlWorkbook->workbookProtection, 'lockRevision'));
$excel->getSecurity()->setLockStructure(self::getLockValue($xmlWorkbook->workbookProtection, 'lockStructure'));
$excel->getSecurity()->setLockWindows(self::getLockValue($xmlWorkbook->workbookProtection, 'lockWindows'));
$security = $excel->getSecurity();
$security->setLockRevision(
self::getLockValue($xmlWorkbook->workbookProtection, 'lockRevision')
);
$security->setLockStructure(
self::getLockValue($xmlWorkbook->workbookProtection, 'lockStructure')
);
$security->setLockWindows(
self::getLockValue($xmlWorkbook->workbookProtection, 'lockWindows')
);

if ($xmlWorkbook->workbookProtection['revisionsPassword']) {
$excel->getSecurity()->setRevisionsPassword(
$security->setRevisionsPassword(
(string) $xmlWorkbook->workbookProtection['revisionsPassword'],
true
);
}
if ($xmlWorkbook->workbookProtection['revisionsAlgorithmName']) {
$security->setRevisionsAlgorithmName(
(string) $xmlWorkbook->workbookProtection['revisionsAlgorithmName']
);
}
if ($xmlWorkbook->workbookProtection['revisionsSaltValue']) {
$security->setRevisionsSaltValue(
(string) $xmlWorkbook->workbookProtection['revisionsSaltValue'],
false
);
}
if ($xmlWorkbook->workbookProtection['revisionsSpinCount']) {
$security->setRevisionsSpinCount(
(int) $xmlWorkbook->workbookProtection['revisionsSpinCount']
);
}
if ($xmlWorkbook->workbookProtection['revisionsHashValue']) {
if ($security->advancedRevisionsPassword()) {
$security->setRevisionsPassword(
(string) $xmlWorkbook->workbookProtection['revisionsHashValue'],
true
);
}
}

if ($xmlWorkbook->workbookProtection['workbookPassword']) {
$excel->getSecurity()->setWorkbookPassword(
$security->setWorkbookPassword(
(string) $xmlWorkbook->workbookProtection['workbookPassword'],
true
);
}

if ($xmlWorkbook->workbookProtection['workbookAlgorithmName']) {
$security->setWorkbookAlgorithmName(
(string) $xmlWorkbook->workbookProtection['workbookAlgorithmName']
);
}
if ($xmlWorkbook->workbookProtection['workbookSaltValue']) {
$security->setWorkbookSaltValue(
(string) $xmlWorkbook->workbookProtection['workbookSaltValue'],
false
);
}
if ($xmlWorkbook->workbookProtection['workbookSpinCount']) {
$security->setWorkbookSpinCount(
(int) $xmlWorkbook->workbookProtection['workbookSpinCount']
);
}
if ($xmlWorkbook->workbookProtection['workbookHashValue']) {
if ($security->advancedPassword()) {
$security->setWorkbookPassword(
(string) $xmlWorkbook->workbookProtection['workbookHashValue'],
true
);
}
}
}

private static function getLockValue(SimpleXMLElement $protection, string $key): ?bool
Expand Down
2 changes: 1 addition & 1 deletion src/PhpSpreadsheet/Shared/PasswordHasher.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private static function defaultHashPassword(string $password): string
*
* @param string $password Password to hash
* @param string $algorithm Hash algorithm used to compute the password hash value
* @param string $salt Pseudorandom string
* @param string $salt Pseudorandom base64-encoded string
* @param int $spinCount Number of times to iterate on a hash of a password
*
* @return string Hashed password
Expand Down
35 changes: 26 additions & 9 deletions src/PhpSpreadsheet/Writer/Xlsx/Workbook.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,18 +125,35 @@ private function writeBookViews(XMLWriter $objWriter, Spreadsheet $spreadsheet):
*/
private function writeWorkbookProtection(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
{
if ($spreadsheet->getSecurity()->isSecurityEnabled()) {
$security = $spreadsheet->getSecurity();
if ($security->isSecurityEnabled()) {
$objWriter->startElement('workbookProtection');
$objWriter->writeAttribute('lockRevision', ($spreadsheet->getSecurity()->getLockRevision() ? 'true' : 'false'));
$objWriter->writeAttribute('lockStructure', ($spreadsheet->getSecurity()->getLockStructure() ? 'true' : 'false'));
$objWriter->writeAttribute('lockWindows', ($spreadsheet->getSecurity()->getLockWindows() ? 'true' : 'false'));

if ($spreadsheet->getSecurity()->getRevisionsPassword() != '') {
$objWriter->writeAttribute('revisionsPassword', $spreadsheet->getSecurity()->getRevisionsPassword());
$objWriter->writeAttribute('lockRevision', ($security->getLockRevision() ? 'true' : 'false'));
$objWriter->writeAttribute('lockStructure', ($security->getLockStructure() ? 'true' : 'false'));
$objWriter->writeAttribute('lockWindows', ($security->getLockWindows() ? 'true' : 'false'));

if ($security->getRevisionsPassword() !== '') {
$objWriter->writeAttribute('revisionsPassword', $security->getRevisionsPassword());
} else {
$hashValue = $security->getRevisionsHashValue();
if ($hashValue !== '') {
$objWriter->writeAttribute('revisionsAlgorithmName', $security->getRevisionsAlgorithmName());
$objWriter->writeAttribute('revisionsHashValue', $hashValue);
$objWriter->writeAttribute('revisionsSaltValue', $security->getRevisionsSaltValue());
$objWriter->writeAttribute('revisionsSpinCount', (string) $security->getRevisionsSpinCount());
}
}

if ($spreadsheet->getSecurity()->getWorkbookPassword() != '') {
$objWriter->writeAttribute('workbookPassword', $spreadsheet->getSecurity()->getWorkbookPassword());
if ($security->getWorkbookPassword() !== '') {
$objWriter->writeAttribute('workbookPassword', $security->getWorkbookPassword());
} else {
$hashValue = $security->getWorkbookHashValue();
if ($hashValue !== '') {
$objWriter->writeAttribute('workbookAlgorithmName', $security->getWorkbookAlgorithmName());
$objWriter->writeAttribute('workbookHashValue', $hashValue);
$objWriter->writeAttribute('workbookSaltValue', $security->getWorkbookSaltValue());
$objWriter->writeAttribute('workbookSpinCount', (string) $security->getWorkbookSpinCount());
}
}

$objWriter->endElement();
Expand Down
Loading