summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2015-12-04 00:46:58 -0500
committerJesse Luehrs <doy@tozt.net>2015-12-04 00:52:50 -0500
commitf1f3c514a88f9577b20a5166ccf5a801a9acb614 (patch)
tree70b1f8f9a0c48909dfe0ea2f537ddc1fff9d0c16
parent963983123e5166b3e8bead9ed8d7d6ac175c1cb1 (diff)
downloadspreadsheet-parsexlsx-f1f3c514a88f9577b20a5166ccf5a801a9acb614.tar.gz
spreadsheet-parsexlsx-f1f3c514a88f9577b20a5166ccf5a801a9acb614.zip
split the decryptor modules out into their own files
-rw-r--r--lib/Spreadsheet/ParseXLSX.pm416
-rw-r--r--lib/Spreadsheet/ParseXLSX/Decryptor.pm222
-rw-r--r--lib/Spreadsheet/ParseXLSX/Decryptor/Agile.pm107
-rw-r--r--lib/Spreadsheet/ParseXLSX/Decryptor/Standard.pm87
4 files changed, 418 insertions, 414 deletions
diff --git a/lib/Spreadsheet/ParseXLSX.pm b/lib/Spreadsheet/ParseXLSX.pm
index d750e9b..dd247b7 100644
--- a/lib/Spreadsheet/ParseXLSX.pm
+++ b/lib/Spreadsheet/ParseXLSX.pm
@@ -10,14 +10,7 @@ use Scalar::Util 'openhandle';
use Spreadsheet::ParseExcel 0.61;
use XML::Twig;
-use Crypt::Mode::CBC;
-use Crypt::Mode::ECB;
-use Digest::SHA ();
-
-use OLE::Storage_Lite;
-use MIME::Base64 ();
-use Encode ();
-use File::Temp 'tempfile';
+use Spreadsheet::ParseXLSX::Decryptor;
=head1 SYNOPSIS
@@ -94,7 +87,7 @@ sub parse {
}
if ($signature eq "\xd0\xcf") {
- $tempfile = $file = Spreadsheet::ParseXLSX::decryptor->open($file, $self->{Password});
+ $tempfile = $file = Spreadsheet::ParseXLSX::Decryptor->open($file, $self->{Password});
}
eval {
@@ -953,411 +946,6 @@ sub _apply_tint {
return scalar hls2rgb($h, $l, $s);
}
-package Spreadsheet::ParseXLSX::decryptor;
-
-use strict;
-use warnings;
-
-sub open {
- my $class = shift;
-
- my ($filename, $password) = @_;
-
- $password = $password || 'VelvetSweatshop';
-
- my ($infoFile, $packageFile) = _getCompoundData($filename, ['EncryptionInfo', 'EncryptedPackage']);
-
- my $xlsx;
-
- eval {
- my $infoFH = IO::File->new();
- $infoFH->open($infoFile);
- $infoFH->binmode();
-
- my $buffer;
- $infoFH->read($buffer, 8);
- my ($majorVers, $minorVers) = unpack('SS', $buffer);
-
- if ($majorVers == 4 && $minorVers == 4) {
- $xlsx = agileDecryption($infoFH, $packageFile, $password);
- } else {
- $xlsx = standardDecryption($infoFH, $packageFile, $password);
- }
- $infoFH->close();
- };
- unlink $infoFile, $packageFile;
- die $@ if $@;
-
- return $xlsx;
-}
-
-sub _getCompoundData {
- my $filename = shift;
- my $names = shift;
-
- my @files;
-
- my $storage = OLE::Storage_Lite->new($filename);
-
- foreach my $name (@{$names}) {
- my @data = $storage->getPpsSearch([OLE::Storage_Lite::Asc2Ucs($name)], 1, 1);
- if ($#data < 0) {
- push @files, undef;
- } else {
- my ($fh, $filename) = File::Temp::tempfile();
- my $out = IO::Handle->new_from_fd($fh, 'w') || die "TempFile error!";
- $out->write($data[0]->{Data});
- $out->close();
- push @files, $filename;
- }
- }
-
- return @files;
-}
-
-sub standardDecryption {
- my ($infoFH, $packageFile, $password) = @_;
-
- my $buffer;
- my $n = $infoFH->read($buffer, 24);
-
- my ($encryptionHeaderSize, undef, undef, $algID, $algIDHash, $keyBits) = unpack('LLLLLL', $buffer);
-
- $infoFH->seek($encryptionHeaderSize - 0x14, IO::File::SEEK_CUR);
-
- $infoFH->read($buffer, 4);
-
- my $saltSize = unpack('L', $buffer);
-
- my ($salt, $encryptedVerifier, $verifierHashSize, $encryptedVerifierHash);
-
- $infoFH->read($salt, 16);
- $infoFH->read($encryptedVerifier, 16);
-
- $infoFH->read($buffer, 4);
- $verifierHashSize = unpack('L', $buffer);
-
- $infoFH->read($encryptedVerifierHash, 32);
- $infoFH->close();
-
- my ($cipherAlgorithm, $hashAlgorithm);
-
- if ($algID == 0x0000660E || $algID == 0x0000660F || $algID == 0x0000660E) {
- $cipherAlgorithm = 'AES';
- } else {
- die sprintf('Unsupported encryption algorithm: 0x%.8x', $algID);
- }
-
- if ($algIDHash == 0x00008004) {
- $hashAlgorithm = 'SHA-1';
- } else {
- die sprintf('Unsupported hash algorithm: 0x%.8x', $algIDHash);
- }
-
- my $decryptor = Spreadsheet::ParseXLSX::decryptor::Standard->new({
- cipherAlgorithm => $cipherAlgorithm,
- cipherChaining => 'ECB',
- hashAlgorithm => $hashAlgorithm,
- salt => $salt,
- password => $password,
- keyBits => $keyBits,
- spinCount => 50000
- });
-
- $decryptor->verifyPassword($encryptedVerifier, $encryptedVerifierHash);
-
- my $in = new IO::File;
- $in->open("<$packageFile") || die 'File/handle opening error';
- $in->binmode();
-
- my ($fh, $filename) = File::Temp::tempfile();
- binmode($fh);
- my $out = IO::Handle->new_from_fd($fh, 'w') || die "TempFile error!";
-
- my $inbuf;
- $in->read($inbuf, 8);
- my $fileSize = unpack('L', $inbuf);
-
- $decryptor->decryptFile($in, $out, 1024, $fileSize);
-
- $in->close();
- $out->close();
-
- return $filename;
-}
-
-sub agileDecryption {
- my ($infoFH, $packageFile, $password) = @_;
-
- my $xml = XML::Twig->new;
- $xml->parse($infoFH);
-
- my ($info) = $xml->find_nodes('//encryption/keyEncryptors/keyEncryptor/p:encryptedKey');
-
- my $encryptedVerifierHashInput = MIME::Base64::decode($info->att('encryptedVerifierHashInput'));
- my $encryptedVerifierHashValue = MIME::Base64::decode($info->att('encryptedVerifierHashValue'));
- my $encryptedKeyValue = MIME::Base64::decode($info->att('encryptedKeyValue'));
-
- my $keyDecryptor = Spreadsheet::ParseXLSX::decryptor::Agile->new({
- cipherAlgorithm => $info->att('cipherAlgorithm'),
- cipherChaining => $info->att('cipherChaining'),
- hashAlgorithm => $info->att('hashAlgorithm'),
- salt => MIME::Base64::decode($info->att('saltValue')),
- password => $password,
- keyBits => 0 + $info->att('keyBits'),
- spinCount => 0 + $info->att('spinCount'),
- blockSize => 0 + $info->att('blockSize')
- });
-
- $keyDecryptor->verifyPassword($encryptedVerifierHashInput, $encryptedVerifierHashValue);
-
- my $key = $keyDecryptor->decrypt($encryptedKeyValue, "\x14\x6e\x0b\xe7\xab\xac\xd0\xd6");
-
- ($info) = $xml->find_nodes('//encryption/keyData');
-
- my $fileDecryptor = Spreadsheet::ParseXLSX::decryptor::Agile->new({
- cipherAlgorithm => $info->att('cipherAlgorithm'),
- cipherChaining => $info->att('cipherChaining'),
- hashAlgorithm => $info->att('hashAlgorithm'),
- salt => MIME::Base64::decode($info->att('saltValue')),
- password => $password,
- keyBits => 0 + $info->att('keyBits'),
- blockSize => 0 + $info->att('blockSize')
- });
-
- my $in = new IO::File;
- $in->open("<$packageFile") || die 'File/handle opening error';
- $in->binmode();
-
- my ($fh, $filename) = File::Temp::tempfile();
- binmode($fh);
- my $out = IO::Handle->new_from_fd($fh, 'w') || die "TempFile error!";
-
- my $inbuf;
- $in->read($inbuf, 8);
- my $fileSize = unpack('L', $inbuf);
-
- $fileDecryptor->decryptFile($in, $out, 4096, $key, $fileSize);
-
- $in->close();
- $out->close();
-
- return $filename;
-}
-
-sub new {
- my $class = shift;
- my $self = shift;
-
- $self->{keyLength} = $self->{keyBits} / 8;
-
- if ($self->{hashAlgorithm} eq 'SHA512') {
- $self->{hashProc} = \&Digest::SHA::sha512;
- } elsif ($self->{hashAlgorithm} eq 'SHA-1') {
- $self->{hashProc} = \&Digest::SHA::sha1;
- } elsif ($self->{hashAlgorithm} eq 'SHA256') {
- $self->{hashProc} = \&Digest::SHA::sha256;
- } else {
- die "Unsupported hash algorithm: $self->{hashAlgorithm}";
- }
-
- return bless $self, $class;
-}
-
-package Spreadsheet::ParseXLSX::decryptor::Agile;
-
-use strict;
-use warnings;
-
-use parent -norequire, 'Spreadsheet::ParseXLSX::decryptor';
-
-sub new {
- my $class = shift;
- my $self = Spreadsheet::ParseXLSX::decryptor->new(@_);
- bless $self, $class;
-}
-
-sub decrypt {
- my $self = shift;
- my ($encryptedValue, $blockKey) = @_;
-
- my $key = $self->_generateDecryptionKey($blockKey);
- my $iv = $self->_generateInitializationVector('', $self->{blockSize});
- my $cbc = Crypt::Mode::CBC->new($self->{cipherAlgorithm}, 0);
- return $cbc->decrypt($encryptedValue, $key, $iv);
-}
-
-sub _generateDecryptionKey {
- my $self = shift;
- my ($blockKey) = @_;
-
- my $hash;
-
- unless ($self->{pregeneratedKey}) {
- $hash = $self->{hashProc}->($self->{salt} . Encode::encode('UTF-16LE', $self->{password}));
- for (my $i = 0; $i < $self->{spinCount}; $i++) {
- $hash = $self->{hashProc}->(pack('L', $i) . $hash);
- }
- $self->{pregeneratedKey} = $hash;
- }
-
- $hash = $self->{hashProc}->($self->{pregeneratedKey} . $blockKey);
-
- if (length($hash) > $self->{keyLength}) {
- $hash = substr($hash, 0, $self->{keyLength});
- } elsif (length($hash) < $self->{keyLength}) {
- $hash .= "\x36" x ($self->{keyLength} - length($hash));
- }
- return $hash;
-}
-
-sub _generateInitializationVector {
- my $self = shift;
- my ($blockKey, $blockSize) = @_;
-
- my $iv;
- if ($blockKey) {
- $iv = $self->{hashProc}->($self->{salt} . $blockKey);
- } else {
- $iv = $self->{salt};
- }
-
- if (length($iv) > $blockSize) {
- $iv = substr($iv, 0, $blockSize);
- } elsif (length($iv) < $blockSize) {
- $iv = $iv . ("\x36" x ($blockSize - length($iv)));
- }
-
- return $iv;
-}
-
-sub decryptFile {
- my $self = shift;
- my ($inFile, $outFile, $bufferLength, $key, $fileSize) = @_;
-
- my $cbc = Crypt::Mode::CBC->new($self->{cipherAlgorithm}, 0);
-
- my $inbuf;
- my $i = 0;
-
- while (($fileSize > 0) && (my $inlen = $inFile->read($inbuf, $bufferLength))) {
- my $blockId = pack('L', $i);
-
- my $iv = $self->_generateInitializationVector($blockId, $self->{blockSize});
-
- if ($inlen < $bufferLength) {
- $inbuf .= "\x00" x ($bufferLength - $inlen);
- }
-
- my $outbuf = $cbc->decrypt($inbuf, $key, $iv);
- if ($fileSize < $inlen) {
- $inlen = $fileSize;
- }
-
- $outFile->write($outbuf, $inlen);
- $i++;
- $fileSize -= $inlen;
- }
-}
-
-sub verifyPassword {
- my $self = shift;
-
- my ($encryptedVerifier, $encryptedVerifierHash) = @_;
-
- my $encryptedVerifierHash0 = $self->{hashProc}->($self->decrypt($encryptedVerifier, "\xfe\xa7\xd2\x76\x3b\x4b\x9e\x79"));
- $encryptedVerifierHash = $self->decrypt($encryptedVerifierHash, "\xd7\xaa\x0f\x6d\x30\x61\x34\x4e");
-
- die "Wrong password: $self" unless ($encryptedVerifierHash0 eq $encryptedVerifierHash);
-}
-
-package Spreadsheet::ParseXLSX::decryptor::Standard;
-
-use strict;
-use warnings;
-
-use parent -norequire, 'Spreadsheet::ParseXLSX::decryptor';
-
-sub new {
- my $class = shift;
- my $self = Spreadsheet::ParseXLSX::decryptor->new(@_);
- bless $self, $class;
-}
-
-sub decrypt {
- my $self = shift;
- my ($encryptedValue) = @_;
-
- my $key = $self->_generateDecryptionKey("\x00" x 4);
- my $ecb = Crypt::Mode::ECB->new($self->{cipherAlgorithm}, 0);
- return $ecb->decrypt($encryptedValue, $key);
-}
-
-sub decryptFile {
- my $self = shift;
- my ($inFile, $outFile, $bufferLength, $fileSize) = @_;
-
- my $key = $self->_generateDecryptionKey("\x00" x 4);
- my $ecb = Crypt::Mode::ECB->new($self->{cipherAlgorithm}, 0);
-
- my $inbuf;
- my $i = 0;
-
- while (($fileSize > 0) && (my $inlen = $inFile->read($inbuf, $bufferLength))) {
- if ($inlen < $bufferLength) {
- $inbuf .= "\x00" x ($bufferLength - $inlen);
- }
-
- my $outbuf = $ecb->decrypt($inbuf, $key);
- if ($fileSize < $inlen) {
- $inlen = $fileSize;
- }
-
- $outFile->write($outbuf, $inlen);
- $i++;
- $fileSize -= $inlen;
- }
-}
-
-sub _generateDecryptionKey {
- my $self = shift;
- my ($blockKey) = @_;
-
- my $hash;
- unless ($self->{pregeneratedKey}) {
- $hash = $self->{hashProc}->($self->{salt} . Encode::encode('UTF-16LE', $self->{password}));
- for (my $i = 0; $i < $self->{spinCount}; $i++) {
- $hash = $self->{hashProc}->(pack('L', $i) . $hash);
- }
- $self->{pregeneratedKey} = $hash;
- }
-
- $hash = $self->{hashProc}->($self->{pregeneratedKey} . $blockKey);
-
- my $x1 = $self->{hashProc}->(("\x36" x 64) ^ $hash);
- if (length($x1) >= $self->{keyLength}) {
- $hash = substr($x1, 0, $self->{keyLength});
- } else {
- my $x2 = $self->{hashProc}->(("\x5C" x 64) ^ $hash);
- $hash = substr($x1 . $x2, 0, $self->{keyLength});
- }
-
- return $hash;
-}
-
-sub verifyPassword {
- my $self = shift;
-
- my ($encryptedVerifier, $encryptedVerifierHash) = @_;
-
- my $verifier = $self->decrypt($encryptedVerifier);
- my $verifierHash = $self->decrypt($encryptedVerifierHash);
-
- my $verifierHash0 = $self->{hashProc}->($verifier);
-
- die "Wrong password: $self" unless ($verifierHash0 eq substr($verifierHash, 0, length($verifierHash0)));
-}
-
=head1 INCOMPATIBILITIES
This module returns data using classes from L<Spreadsheet::ParseExcel>, so for
diff --git a/lib/Spreadsheet/ParseXLSX/Decryptor.pm b/lib/Spreadsheet/ParseXLSX/Decryptor.pm
new file mode 100644
index 0000000..e98425f
--- /dev/null
+++ b/lib/Spreadsheet/ParseXLSX/Decryptor.pm
@@ -0,0 +1,222 @@
+package Spreadsheet::ParseXLSX::Decryptor;
+use strict;
+use warnings;
+
+use Crypt::Mode::CBC;
+use Crypt::Mode::ECB;
+use Digest::SHA ();
+use Encode ();
+use File::Temp 'tempfile';
+use MIME::Base64 ();
+use OLE::Storage_Lite;
+
+use Spreadsheet::ParseXLSX::Decryptor::Standard;
+use Spreadsheet::ParseXLSX::Decryptor::Agile;
+
+sub open {
+ my $class = shift;
+
+ my ($filename, $password) = @_;
+
+ $password = $password || 'VelvetSweatshop';
+
+ my ($infoFile, $packageFile) = _getCompoundData($filename, ['EncryptionInfo', 'EncryptedPackage']);
+
+ my $xlsx;
+
+ eval {
+ my $infoFH = IO::File->new();
+ $infoFH->open($infoFile);
+ $infoFH->binmode();
+
+ my $buffer;
+ $infoFH->read($buffer, 8);
+ my ($majorVers, $minorVers) = unpack('SS', $buffer);
+
+ if ($majorVers == 4 && $minorVers == 4) {
+ $xlsx = agileDecryption($infoFH, $packageFile, $password);
+ } else {
+ $xlsx = standardDecryption($infoFH, $packageFile, $password);
+ }
+ $infoFH->close();
+ };
+ unlink $infoFile, $packageFile;
+ die $@ if $@;
+
+ return $xlsx;
+}
+
+sub _getCompoundData {
+ my $filename = shift;
+ my $names = shift;
+
+ my @files;
+
+ my $storage = OLE::Storage_Lite->new($filename);
+
+ foreach my $name (@{$names}) {
+ my @data = $storage->getPpsSearch([OLE::Storage_Lite::Asc2Ucs($name)], 1, 1);
+ if ($#data < 0) {
+ push @files, undef;
+ } else {
+ my ($fh, $filename) = File::Temp::tempfile();
+ my $out = IO::Handle->new_from_fd($fh, 'w') || die "TempFile error!";
+ $out->write($data[0]->{Data});
+ $out->close();
+ push @files, $filename;
+ }
+ }
+
+ return @files;
+}
+
+sub standardDecryption {
+ my ($infoFH, $packageFile, $password) = @_;
+
+ my $buffer;
+ my $n = $infoFH->read($buffer, 24);
+
+ my ($encryptionHeaderSize, undef, undef, $algID, $algIDHash, $keyBits) = unpack('LLLLLL', $buffer);
+
+ $infoFH->seek($encryptionHeaderSize - 0x14, IO::File::SEEK_CUR);
+
+ $infoFH->read($buffer, 4);
+
+ my $saltSize = unpack('L', $buffer);
+
+ my ($salt, $encryptedVerifier, $verifierHashSize, $encryptedVerifierHash);
+
+ $infoFH->read($salt, 16);
+ $infoFH->read($encryptedVerifier, 16);
+
+ $infoFH->read($buffer, 4);
+ $verifierHashSize = unpack('L', $buffer);
+
+ $infoFH->read($encryptedVerifierHash, 32);
+ $infoFH->close();
+
+ my ($cipherAlgorithm, $hashAlgorithm);
+
+ if ($algID == 0x0000660E || $algID == 0x0000660F || $algID == 0x0000660E) {
+ $cipherAlgorithm = 'AES';
+ } else {
+ die sprintf('Unsupported encryption algorithm: 0x%.8x', $algID);
+ }
+
+ if ($algIDHash == 0x00008004) {
+ $hashAlgorithm = 'SHA-1';
+ } else {
+ die sprintf('Unsupported hash algorithm: 0x%.8x', $algIDHash);
+ }
+
+ my $decryptor = Spreadsheet::ParseXLSX::Decryptor::Standard->new({
+ cipherAlgorithm => $cipherAlgorithm,
+ cipherChaining => 'ECB',
+ hashAlgorithm => $hashAlgorithm,
+ salt => $salt,
+ password => $password,
+ keyBits => $keyBits,
+ spinCount => 50000
+ });
+
+ $decryptor->verifyPassword($encryptedVerifier, $encryptedVerifierHash);
+
+ my $in = new IO::File;
+ $in->open("<$packageFile") || die 'File/handle opening error';
+ $in->binmode();
+
+ my ($fh, $filename) = File::Temp::tempfile();
+ binmode($fh);
+ my $out = IO::Handle->new_from_fd($fh, 'w') || die "TempFile error!";
+
+ my $inbuf;
+ $in->read($inbuf, 8);
+ my $fileSize = unpack('L', $inbuf);
+
+ $decryptor->decryptFile($in, $out, 1024, $fileSize);
+
+ $in->close();
+ $out->close();
+
+ return $filename;
+}
+
+sub agileDecryption {
+ my ($infoFH, $packageFile, $password) = @_;
+
+ my $xml = XML::Twig->new;
+ $xml->parse($infoFH);
+
+ my ($info) = $xml->find_nodes('//encryption/keyEncryptors/keyEncryptor/p:encryptedKey');
+
+ my $encryptedVerifierHashInput = MIME::Base64::decode($info->att('encryptedVerifierHashInput'));
+ my $encryptedVerifierHashValue = MIME::Base64::decode($info->att('encryptedVerifierHashValue'));
+ my $encryptedKeyValue = MIME::Base64::decode($info->att('encryptedKeyValue'));
+
+ my $keyDecryptor = Spreadsheet::ParseXLSX::Decryptor::Agile->new({
+ cipherAlgorithm => $info->att('cipherAlgorithm'),
+ cipherChaining => $info->att('cipherChaining'),
+ hashAlgorithm => $info->att('hashAlgorithm'),
+ salt => MIME::Base64::decode($info->att('saltValue')),
+ password => $password,
+ keyBits => 0 + $info->att('keyBits'),
+ spinCount => 0 + $info->att('spinCount'),
+ blockSize => 0 + $info->att('blockSize')
+ });
+
+ $keyDecryptor->verifyPassword($encryptedVerifierHashInput, $encryptedVerifierHashValue);
+
+ my $key = $keyDecryptor->decrypt($encryptedKeyValue, "\x14\x6e\x0b\xe7\xab\xac\xd0\xd6");
+
+ ($info) = $xml->find_nodes('//encryption/keyData');
+
+ my $fileDecryptor = Spreadsheet::ParseXLSX::Decryptor::Agile->new({
+ cipherAlgorithm => $info->att('cipherAlgorithm'),
+ cipherChaining => $info->att('cipherChaining'),
+ hashAlgorithm => $info->att('hashAlgorithm'),
+ salt => MIME::Base64::decode($info->att('saltValue')),
+ password => $password,
+ keyBits => 0 + $info->att('keyBits'),
+ blockSize => 0 + $info->att('blockSize')
+ });
+
+ my $in = new IO::File;
+ $in->open("<$packageFile") || die 'File/handle opening error';
+ $in->binmode();
+
+ my ($fh, $filename) = File::Temp::tempfile();
+ binmode($fh);
+ my $out = IO::Handle->new_from_fd($fh, 'w') || die "TempFile error!";
+
+ my $inbuf;
+ $in->read($inbuf, 8);
+ my $fileSize = unpack('L', $inbuf);
+
+ $fileDecryptor->decryptFile($in, $out, 4096, $key, $fileSize);
+
+ $in->close();
+ $out->close();
+
+ return $filename;
+}
+
+sub new {
+ my $class = shift;
+ my $self = shift;
+
+ $self->{keyLength} = $self->{keyBits} / 8;
+
+ if ($self->{hashAlgorithm} eq 'SHA512') {
+ $self->{hashProc} = \&Digest::SHA::sha512;
+ } elsif ($self->{hashAlgorithm} eq 'SHA-1') {
+ $self->{hashProc} = \&Digest::SHA::sha1;
+ } elsif ($self->{hashAlgorithm} eq 'SHA256') {
+ $self->{hashProc} = \&Digest::SHA::sha256;
+ } else {
+ die "Unsupported hash algorithm: $self->{hashAlgorithm}";
+ }
+
+ return bless $self, $class;
+}
+
+1;
diff --git a/lib/Spreadsheet/ParseXLSX/Decryptor/Agile.pm b/lib/Spreadsheet/ParseXLSX/Decryptor/Agile.pm
new file mode 100644
index 0000000..3b836e3
--- /dev/null
+++ b/lib/Spreadsheet/ParseXLSX/Decryptor/Agile.pm
@@ -0,0 +1,107 @@
+package Spreadsheet::ParseXLSX::Decryptor::Agile;
+use strict;
+use warnings;
+
+use base 'Spreadsheet::ParseXLSX::Decryptor';
+
+sub new {
+ my $class = shift;
+ my $self = Spreadsheet::ParseXLSX::Decryptor->new(@_);
+ bless $self, $class;
+}
+
+sub decrypt {
+ my $self = shift;
+ my ($encryptedValue, $blockKey) = @_;
+
+ my $key = $self->_generateDecryptionKey($blockKey);
+ my $iv = $self->_generateInitializationVector('', $self->{blockSize});
+ my $cbc = Crypt::Mode::CBC->new($self->{cipherAlgorithm}, 0);
+ return $cbc->decrypt($encryptedValue, $key, $iv);
+}
+
+sub _generateDecryptionKey {
+ my $self = shift;
+ my ($blockKey) = @_;
+
+ my $hash;
+
+ unless ($self->{pregeneratedKey}) {
+ $hash = $self->{hashProc}->($self->{salt} . Encode::encode('UTF-16LE', $self->{password}));
+ for (my $i = 0; $i < $self->{spinCount}; $i++) {
+ $hash = $self->{hashProc}->(pack('L', $i) . $hash);
+ }
+ $self->{pregeneratedKey} = $hash;
+ }
+
+ $hash = $self->{hashProc}->($self->{pregeneratedKey} . $blockKey);
+
+ if (length($hash) > $self->{keyLength}) {
+ $hash = substr($hash, 0, $self->{keyLength});
+ } elsif (length($hash) < $self->{keyLength}) {
+ $hash .= "\x36" x ($self->{keyLength} - length($hash));
+ }
+ return $hash;
+}
+
+sub _generateInitializationVector {
+ my $self = shift;
+ my ($blockKey, $blockSize) = @_;
+
+ my $iv;
+ if ($blockKey) {
+ $iv = $self->{hashProc}->($self->{salt} . $blockKey);
+ } else {
+ $iv = $self->{salt};
+ }
+
+ if (length($iv) > $blockSize) {
+ $iv = substr($iv, 0, $blockSize);
+ } elsif (length($iv) < $blockSize) {
+ $iv = $iv . ("\x36" x ($blockSize - length($iv)));
+ }
+
+ return $iv;
+}
+
+sub decryptFile {
+ my $self = shift;
+ my ($inFile, $outFile, $bufferLength, $key, $fileSize) = @_;
+
+ my $cbc = Crypt::Mode::CBC->new($self->{cipherAlgorithm}, 0);
+
+ my $inbuf;
+ my $i = 0;
+
+ while (($fileSize > 0) && (my $inlen = $inFile->read($inbuf, $bufferLength))) {
+ my $blockId = pack('L', $i);
+
+ my $iv = $self->_generateInitializationVector($blockId, $self->{blockSize});
+
+ if ($inlen < $bufferLength) {
+ $inbuf .= "\x00" x ($bufferLength - $inlen);
+ }
+
+ my $outbuf = $cbc->decrypt($inbuf, $key, $iv);
+ if ($fileSize < $inlen) {
+ $inlen = $fileSize;
+ }
+
+ $outFile->write($outbuf, $inlen);
+ $i++;
+ $fileSize -= $inlen;
+ }
+}
+
+sub verifyPassword {
+ my $self = shift;
+
+ my ($encryptedVerifier, $encryptedVerifierHash) = @_;
+
+ my $encryptedVerifierHash0 = $self->{hashProc}->($self->decrypt($encryptedVerifier, "\xfe\xa7\xd2\x76\x3b\x4b\x9e\x79"));
+ $encryptedVerifierHash = $self->decrypt($encryptedVerifierHash, "\xd7\xaa\x0f\x6d\x30\x61\x34\x4e");
+
+ die "Wrong password: $self" unless ($encryptedVerifierHash0 eq $encryptedVerifierHash);
+}
+
+1;
diff --git a/lib/Spreadsheet/ParseXLSX/Decryptor/Standard.pm b/lib/Spreadsheet/ParseXLSX/Decryptor/Standard.pm
new file mode 100644
index 0000000..a940719
--- /dev/null
+++ b/lib/Spreadsheet/ParseXLSX/Decryptor/Standard.pm
@@ -0,0 +1,87 @@
+package Spreadsheet::ParseXLSX::Decryptor::Standard;
+use strict;
+use warnings;
+
+use base 'Spreadsheet::ParseXLSX::Decryptor';
+
+sub new {
+ my $class = shift;
+ my $self = Spreadsheet::ParseXLSX::Decryptor->new(@_);
+ bless $self, $class;
+}
+
+sub decrypt {
+ my $self = shift;
+ my ($encryptedValue) = @_;
+
+ my $key = $self->_generateDecryptionKey("\x00" x 4);
+ my $ecb = Crypt::Mode::ECB->new($self->{cipherAlgorithm}, 0);
+ return $ecb->decrypt($encryptedValue, $key);
+}
+
+sub decryptFile {
+ my $self = shift;
+ my ($inFile, $outFile, $bufferLength, $fileSize) = @_;
+
+ my $key = $self->_generateDecryptionKey("\x00" x 4);
+ my $ecb = Crypt::Mode::ECB->new($self->{cipherAlgorithm}, 0);
+
+ my $inbuf;
+ my $i = 0;
+
+ while (($fileSize > 0) && (my $inlen = $inFile->read($inbuf, $bufferLength))) {
+ if ($inlen < $bufferLength) {
+ $inbuf .= "\x00" x ($bufferLength - $inlen);
+ }
+
+ my $outbuf = $ecb->decrypt($inbuf, $key);
+ if ($fileSize < $inlen) {
+ $inlen = $fileSize;
+ }
+
+ $outFile->write($outbuf, $inlen);
+ $i++;
+ $fileSize -= $inlen;
+ }
+}
+
+sub _generateDecryptionKey {
+ my $self = shift;
+ my ($blockKey) = @_;
+
+ my $hash;
+ unless ($self->{pregeneratedKey}) {
+ $hash = $self->{hashProc}->($self->{salt} . Encode::encode('UTF-16LE', $self->{password}));
+ for (my $i = 0; $i < $self->{spinCount}; $i++) {
+ $hash = $self->{hashProc}->(pack('L', $i) . $hash);
+ }
+ $self->{pregeneratedKey} = $hash;
+ }
+
+ $hash = $self->{hashProc}->($self->{pregeneratedKey} . $blockKey);
+
+ my $x1 = $self->{hashProc}->(("\x36" x 64) ^ $hash);
+ if (length($x1) >= $self->{keyLength}) {
+ $hash = substr($x1, 0, $self->{keyLength});
+ } else {
+ my $x2 = $self->{hashProc}->(("\x5C" x 64) ^ $hash);
+ $hash = substr($x1 . $x2, 0, $self->{keyLength});
+ }
+
+ return $hash;
+}
+
+sub verifyPassword {
+ my $self = shift;
+
+ my ($encryptedVerifier, $encryptedVerifierHash) = @_;
+
+ my $verifier = $self->decrypt($encryptedVerifier);
+ my $verifierHash = $self->decrypt($encryptedVerifierHash);
+
+ my $verifierHash0 = $self->{hashProc}->($verifier);
+
+ die "Wrong password: $self" unless ($verifierHash0 eq substr($verifierHash, 0, length($verifierHash0)));
+}
+
+1;