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
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ secure-spreadsheet run --password=1 --input=/Users/nick/Encryptor/Book1.xlsx --o

In php

Usage Examples

```php
require "vendor/autoload.php";

Expand All @@ -40,15 +42,35 @@ $test->input('Book1.xlsx')
->output('bb.xlsx');
```

Memory-only input and output (no file interaction)

If you want to only use memory/variable output and input, and no file interaction
```php
$test = new Encrypt($nofile = true);
$output = $test->input($binaryData)
->password('111')
->output();
```

Memory output with a temporary folder

```php
$test = new Encrypt(true);
$out = $test->input('Book1.xlsx')
->setTempPathFolder(__DIR__ . DIRECTORY_SEPARATOR . 'tmp')
->password('111')
->output();
```

File input and file output with a temporary folder

```php
$test = new Encrypt;
$test->input('Book1.xlsx')
->setTempPathFolder(__DIR__ . DIRECTORY_SEPARATOR . 'tmp')
->password('111')
->output('bb4.xlsx');
```

## Credits

Thanks to [xlsx-populate](https://github.com/dtjohnson/xlsx-populate) for providing the encryption and password protection.
Expand Down
26 changes: 24 additions & 2 deletions index.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,29 @@

use Nick\SecureSpreadsheet\Encrypt;

$test = new Encrypt();
// output file
$test = new Encrypt;
$test->input('Book1.xlsx')
->password('111')
->output('bb.xlsx');
->output('bb2.xlsx');

// output file with nofile
$test = new Encrypt(true);
$out = $test->input('Book1.xlsx')
->password('111')
->output();

// output file with nofile and set temp path folder
$test = new Encrypt(true);
$out = $test->input('Book1.xlsx')
->setTempPathFolder(__DIR__.DIRECTORY_SEPARATOR.'tmp')
->password('111')
->output();


// output file with set temp path folder
$test = new Encrypt;
$test->input('Book1.xlsx')
->setTempPathFolder(__DIR__.DIRECTORY_SEPARATOR.'tmp')
->password('111')
->output('bb4.xlsx');
33 changes: 25 additions & 8 deletions src/Encrypt.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
use OLE;
use OLE_PPS_File;
use OLE_PPS_Root;
use RuntimeException;

class Encrypt
{
private $data;

private $password;

private $noFile = false;

private $tmpPathFolder = null;

public function __construct(bool $nofile = false)
{
$this->noFile = $nofile;
Expand All @@ -29,10 +34,10 @@ public function input(string $data)
} else {
$this->data = function () use ($data) {
$fp = fopen($data, 'rb');
if (!$fp) {
if (! $fp) {
throw new Exception('file not found');
}
while (!feof($fp)) {
while (! feof($fp)) {
yield unpack('C*', fread($fp, 4096));
}
fclose($fp);
Expand All @@ -45,12 +50,13 @@ public function input(string $data)
public function password(string $password)
{
$this->password = $password;

return $this;
}

public function output(?string $filePath = null)
{
if (!$this->noFile && is_null($filePath)) {
if (! $this->noFile && is_null($filePath)) {
throw new Exception('Output Filepath cannot be NULL when NOFILE is False');
}

Expand All @@ -64,7 +70,8 @@ public function output(?string $filePath = null)
$encryptionInfo['package']['blockSize'],
$encryptionInfo['package']['saltValue'],
$packageKey,
$this->data
$this->data,
$this->tmpPathFolder
);

$encryptionInfo['dataIntegrity'] = $this->createDataIntegrity($encryptionInfo, $packageKey, $encryptedPackage['tmpFile']);
Expand Down Expand Up @@ -103,12 +110,11 @@ public function output(?string $filePath = null)
$OLE2->append(pack('C*', ...$unpackEncryptedPackage));
}

unlink($encryptedPackage['tmpFile']);

$root = new OLE_PPS_Root(1000000000, 1000000000, [$OLE, $OLE2]);

if ($this->noFile) {
$filePath = tempnam(sys_get_temp_dir(), 'NOFILE');
$tmp = new TempFileManager($this->tmpPathFolder);
$filePath = $tmp->path('NOFILE');
}

$root->save($filePath);
Expand Down Expand Up @@ -201,4 +207,15 @@ private function addVerifierHash(array &$encryptionInfo)
$verifierHashValue
);
}
}

public function setTempPathFolder(string $path)
{
if (! is_writable($path)) {
throw new RuntimeException('Temp dir not writable.');
}

$this->tmpPathFolder = $path;

return $this;
}
}
19 changes: 10 additions & 9 deletions src/PackageEncryptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ public static function encrypt(
int $blockSize,
array $saltValue,
array $key,
callable $input
callable $input,
?string $tmpPathFolder = null
) {
$tmpOutputChunk = tempnam(sys_get_temp_dir(), 'outputChunk');
$tmpFileHeaderLength = tempnam(sys_get_temp_dir(), 'fileHeaderLength');
$tmpFile = tempnam(sys_get_temp_dir(), 'file');

$tmp = new TempFileManager($tmpPathFolder);

$tmpOutputChunk = $tmp->path('outputChunk');
$tmpFileHeaderLength = $tmp->path('fileHeaderLength');
$tmpFile = $tmp->path('file');

if (is_callable($input) && is_a($in = $input(), 'Generator')) {
$inputCount = 0;
Expand All @@ -35,14 +39,11 @@ public static function encrypt(
}

file_put_contents($tmpFileHeaderLength, pack('C*', ...CryptoHelper::createUInt32LEBuffer($inputCount, EncryptionConfig::PACKAGE_OFFSET)));
file_put_contents($tmpFile, file_get_contents($tmpFileHeaderLength) . file_get_contents($tmpOutputChunk));

unlink($tmpOutputChunk);
unlink($tmpFileHeaderLength);
file_put_contents($tmpFile, file_get_contents($tmpFileHeaderLength).file_get_contents($tmpOutputChunk));

return ['tmpFile' => $tmpFile];
}

return [];
}
}
}
82 changes: 82 additions & 0 deletions src/TempFileManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace Nick\SecureSpreadsheet;

use RuntimeException;

class TempFileManager
{
private $baseDir;

private $jobDir;

private $cleaned = false;

public function __construct(?string $preferredDir = null)
{
$this->baseDir = $this->resolveBaseDir($preferredDir);
$this->jobDir = $this->createJobDir();

// request shutdown function to cleanup temp files
register_shutdown_function([$this, 'cleanup']);
}

private function resolveBaseDir(?string $preferredDir): string
{
$candidates = array_filter([
$preferredDir,
getenv('TMPDIR'),
getenv('TMP'),
getenv('TEMP'),
sys_get_temp_dir(),
'/tmp',
getcwd().DIRECTORY_SEPARATOR.'tmp',
]);

foreach ($candidates as $dir) {
if ($this->isUsableDir($dir)) {
return rtrim($dir, DIRECTORY_SEPARATOR);
}
}

throw new RuntimeException('No usable temp directory.');
}

private function isUsableDir(string $dir): bool
{
return is_dir($dir)
&& is_writable($dir)
&& ! is_link($dir);
}

private function createJobDir(): string
{
$jobDir = $this->baseDir.DIRECTORY_SEPARATOR.'job_'.bin2hex(random_bytes(8));

if (! mkdir($jobDir, 0700)) {
throw new RuntimeException('Failed to create job temp directory');
}

return $jobDir;
}

public function path(string $name): string
{
return $this->jobDir.DIRECTORY_SEPARATOR.$name;
}

public function cleanup(): void
{
if ($this->cleaned || ! is_dir($this->jobDir)) {
return;
}

$files = glob($this->jobDir.DIRECTORY_SEPARATOR.'*') ?: [];
foreach ($files as $file) {
@unlink($file);
}

@rmdir($this->jobDir);
$this->cleaned = true;
}
}
18 changes: 13 additions & 5 deletions tests/EncryptorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@

namespace Illuminate\Tests\Auth;

use PHPUnit\Framework\TestCase;
use Nick\SecureSpreadsheet\Encrypt;
use PHPUnit\Framework\TestCase;

class EncryptorTest extends TestCase
{
protected function setUp(): void
{
if (file_exists('bb.xlsx')) unlink('bb.xlsx');
if (file_exists('bb.xlsx')) {
unlink('bb.xlsx');
}
}

public function testEncryptor()
public function test_encryptor()
{
(new Encrypt())->input('Book1.xlsx')->password('111')->output('bb.xlsx');
(new Encrypt)->input('Book1.xlsx')->password('111')->output('bb.xlsx');
$this->assertFileExists('bb.xlsx');
}

public function testEncryptorWithBinaryData()
public function test_encryptor_with_binary_data()
{
$data = 'Book1.xlsx';
$fp = fopen($data, 'rb');
Expand All @@ -27,4 +29,10 @@ public function testEncryptorWithBinaryData()
$str = (new Encrypt($nofile = true))->input($binaryData)->password('111')->output();
$this->assertEquals(12288, strlen($str));
}

public function test_encryptor_with_set_temp_path_folder()
{
(new Encrypt)->input('Book1.xlsx')->setTempPathFolder('/tmp')->password('111')->output('bb.xlsx');
$this->assertFileExists('bb.xlsx');
}
}