Preserve XLSX output before authentication

This commit is contained in:
^^
2026-09-03 17:15:19 +08:00
parent 300f16de32
commit 111c088841
2 changed files with 42 additions and 17 deletions
@@ -230,12 +230,6 @@ final class AgileEncryption
if ($input === false) {
throw new Exception('Could not open XLSX package for decryption.');
}
$output = @fopen($outputFilename, 'wb');
if ($output === false) {
fclose($input);
throw new Exception('Could not open XLSX package for decryption.');
}
try {
$hash = self::passwordHash($password, $info['passwordSalt'], $info['spinCount'], $info['hashAlgorithm']);
@@ -249,21 +243,29 @@ final class AgileEncryption
throw new Exception('Malformed XLSX encryption information.');
}
self::verifyIntegrityFile($info, $secretKey, $input);
rewind($input);
$size = self::unpackSize(self::read($input, 8));
for ($block = 0; $size > 0; ++$block) {
$length = min(self::SEGMENT_SIZE, $size);
$cipher = self::read($input, self::paddedLength($length));
$iv = self::iv($info['keyDataSalt'], pack('V', $block), $info['hashAlgorithm']);
self::write($output, substr(self::aesDecrypt($cipher, $secretKey, $iv, $info['keyBits']), 0, $length));
$size -= $length;
$output = @fopen($outputFilename, 'wb');
if ($output === false) {
throw new Exception('Could not open XLSX package for decryption.');
}
if (fread($input, 1) !== '') {
throw new Exception('Malformed encrypted XLSX package.');
rewind($input);
try {
$size = self::unpackSize(self::read($input, 8));
for ($block = 0; $size > 0; ++$block) {
$length = min(self::SEGMENT_SIZE, $size);
$cipher = self::read($input, self::paddedLength($length));
$iv = self::iv($info['keyDataSalt'], pack('V', $block), $info['hashAlgorithm']);
self::write($output, substr(self::aesDecrypt($cipher, $secretKey, $iv, $info['keyBits']), 0, $length));
$size -= $length;
}
if (fread($input, 1) !== '') {
throw new Exception('Malformed encrypted XLSX package.');
}
} finally {
fclose($output);
}
} finally {
fclose($input);
fclose($output);
}
}
@@ -298,6 +298,27 @@ class AgileEncryptionTest extends TestCase
}
}
public function testDecryptFileDoesNotTruncateOutputWhenPasswordIsIncorrect(): void
{
$package = AgileEncryption::encrypt('package', 'password', 128, 'SHA1', 10);
$inputFilename = tempnam(sys_get_temp_dir(), 'phpspreadsheet-input-');
$outputFilename = tempnam(sys_get_temp_dir(), 'phpspreadsheet-output-');
self::assertNotFalse($inputFilename);
self::assertNotFalse($outputFilename);
file_put_contents($inputFilename, $package['encryptedPackage']);
file_put_contents($outputFilename, 'preserve this content');
try {
$this->expectException(Exception::class);
$this->expectExceptionMessage('XLSX encryption password is incorrect');
AgileEncryption::decryptFile(AgileEncryption::parse($package['encryptionInfo']), $inputFilename, $outputFilename, 'wrong');
} finally {
self::assertSame('preserve this content', file_get_contents($outputFilename));
unlink($inputFilename);
unlink($outputFilename);
}
}
public function testDecryptFileClosesInputWhenOutputCannotBeOpened(): void
{
$package = AgileEncryption::encrypt('package', 'password', 128, 'SHA1', 10);
@@ -327,12 +348,14 @@ class AgileEncryptionTest extends TestCase
self::assertNotFalse($inputFilename);
self::assertNotFalse($outputFilename);
file_put_contents($inputFilename, $package['encryptedPackage']);
file_put_contents($outputFilename, 'preserve this content');
try {
$this->expectException(Exception::class);
$this->expectExceptionMessage('integrity check failed');
AgileEncryption::decryptFile(AgileEncryption::parse($package['encryptionInfo']), $inputFilename, $outputFilename, 'password');
} finally {
self::assertSame('preserve this content', file_get_contents($outputFilename));
unlink($inputFilename);
unlink($outputFilename);
}