| Filename | /home/micha/Projekt/spreadsheet-parsexlsx/lib/Spreadsheet/ParseXLSX/Decryptor.pm |
| Statements | Executed 23 statements in 974µs |
| Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 1.36ms | 1.66ms | Spreadsheet::ParseXLSX::Decryptor::BEGIN@12 |
| 1 | 1 | 1 | 410µs | 490µs | Spreadsheet::ParseXLSX::Decryptor::BEGIN@19 |
| 1 | 1 | 1 | 369µs | 453µs | Spreadsheet::ParseXLSX::Decryptor::BEGIN@18 |
| 1 | 1 | 1 | 215µs | 348µs | Spreadsheet::ParseXLSX::Decryptor::BEGIN@15 |
| 1 | 1 | 1 | 183µs | 228µs | Spreadsheet::ParseXLSX::Decryptor::BEGIN@11 |
| 1 | 1 | 1 | 178µs | 9.84ms | Spreadsheet::ParseXLSX::Decryptor::BEGIN@10 |
| 1 | 1 | 1 | 13µs | 15µs | Spreadsheet::ParseXLSX::Decryptor::BEGIN@3 |
| 1 | 1 | 1 | 8µs | 18µs | Spreadsheet::ParseXLSX::Decryptor::BEGIN@16 |
| 1 | 1 | 1 | 4µs | 25µs | Spreadsheet::ParseXLSX::Decryptor::BEGIN@4 |
| 1 | 1 | 1 | 3µs | 3µs | Spreadsheet::ParseXLSX::Decryptor::BEGIN@13 |
| 1 | 1 | 1 | 1µs | 1µs | Spreadsheet::ParseXLSX::Decryptor::BEGIN@14 |
| 0 | 0 | 0 | 0s | 0s | Spreadsheet::ParseXLSX::Decryptor::_agileDecryption |
| 0 | 0 | 0 | 0s | 0s | Spreadsheet::ParseXLSX::Decryptor::_getCompoundData |
| 0 | 0 | 0 | 0s | 0s | Spreadsheet::ParseXLSX::Decryptor::_standardDecryption |
| 0 | 0 | 0 | 0s | 0s | Spreadsheet::ParseXLSX::Decryptor::new |
| 0 | 0 | 0 | 0s | 0s | Spreadsheet::ParseXLSX::Decryptor::open |
| Line | State ments |
Time on line |
Calls | Time in subs |
Code |
|---|---|---|---|---|---|
| 1 | package Spreadsheet::ParseXLSX::Decryptor; | ||||
| 2 | |||||
| 3 | 2 | 19µs | 2 | 17µs | # spent 15µs (13+2) within Spreadsheet::ParseXLSX::Decryptor::BEGIN@3 which was called:
# once (13µs+2µs) by Spreadsheet::ParseXLSX::BEGIN@17 at line 3 # spent 15µs making 1 call to Spreadsheet::ParseXLSX::Decryptor::BEGIN@3
# spent 2µs making 1 call to strict::import |
| 4 | 2 | 17µs | 2 | 46µs | # spent 25µs (4+21) within Spreadsheet::ParseXLSX::Decryptor::BEGIN@4 which was called:
# once (4µs+21µs) by Spreadsheet::ParseXLSX::BEGIN@17 at line 4 # spent 25µs making 1 call to Spreadsheet::ParseXLSX::Decryptor::BEGIN@4
# spent 21µs making 1 call to warnings::import |
| 5 | |||||
| 6 | # VERSION | ||||
| 7 | |||||
| 8 | # ABSTRACT: helper class to open password protected files | ||||
| 9 | |||||
| 10 | 2 | 64µs | 2 | 9.84ms | # spent 9.84ms (178µs+9.66) within Spreadsheet::ParseXLSX::Decryptor::BEGIN@10 which was called:
# once (178µs+9.66ms) by Spreadsheet::ParseXLSX::BEGIN@17 at line 10 # spent 9.84ms making 1 call to Spreadsheet::ParseXLSX::Decryptor::BEGIN@10
# spent 800ns making 1 call to UNIVERSAL::import |
| 11 | 2 | 67µs | 2 | 228µs | # spent 228µs (183+44) within Spreadsheet::ParseXLSX::Decryptor::BEGIN@11 which was called:
# once (183µs+44µs) by Spreadsheet::ParseXLSX::BEGIN@17 at line 11 # spent 228µs making 1 call to Spreadsheet::ParseXLSX::Decryptor::BEGIN@11
# spent 900ns making 1 call to UNIVERSAL::import |
| 12 | 2 | 80µs | 1 | 1.66ms | # spent 1.66ms (1.36+297µs) within Spreadsheet::ParseXLSX::Decryptor::BEGIN@12 which was called:
# once (1.36ms+297µs) by Spreadsheet::ParseXLSX::BEGIN@17 at line 12 # spent 1.66ms making 1 call to Spreadsheet::ParseXLSX::Decryptor::BEGIN@12 |
| 13 | 2 | 11µs | 1 | 3µs | # spent 3µs within Spreadsheet::ParseXLSX::Decryptor::BEGIN@13 which was called:
# once (3µs+0s) by Spreadsheet::ParseXLSX::BEGIN@17 at line 13 # spent 3µs making 1 call to Spreadsheet::ParseXLSX::Decryptor::BEGIN@13 |
| 14 | 2 | 8µs | 1 | 1µs | # spent 1µs within Spreadsheet::ParseXLSX::Decryptor::BEGIN@14 which was called:
# once (1µs+0s) by Spreadsheet::ParseXLSX::BEGIN@17 at line 14 # spent 1µs making 1 call to Spreadsheet::ParseXLSX::Decryptor::BEGIN@14 |
| 15 | 2 | 74µs | 1 | 348µs | # spent 348µs (215+133) within Spreadsheet::ParseXLSX::Decryptor::BEGIN@15 which was called:
# once (215µs+133µs) by Spreadsheet::ParseXLSX::BEGIN@17 at line 15 # spent 348µs making 1 call to Spreadsheet::ParseXLSX::Decryptor::BEGIN@15 |
| 16 | 2 | 18µs | 2 | 29µs | # spent 18µs (8+11) within Spreadsheet::ParseXLSX::Decryptor::BEGIN@16 which was called:
# once (8µs+11µs) by Spreadsheet::ParseXLSX::BEGIN@17 at line 16 # spent 18µs making 1 call to Spreadsheet::ParseXLSX::Decryptor::BEGIN@16
# spent 11µs making 1 call to Exporter::import |
| 17 | |||||
| 18 | 2 | 64µs | 2 | 454µs | # spent 453µs (369+84) within Spreadsheet::ParseXLSX::Decryptor::BEGIN@18 which was called:
# once (369µs+84µs) by Spreadsheet::ParseXLSX::BEGIN@17 at line 18 # spent 453µs making 1 call to Spreadsheet::ParseXLSX::Decryptor::BEGIN@18
# spent 1µs making 1 call to UNIVERSAL::import |
| 19 | 2 | 549µs | 2 | 492µs | # spent 490µs (410+81) within Spreadsheet::ParseXLSX::Decryptor::BEGIN@19 which was called:
# once (410µs+81µs) by Spreadsheet::ParseXLSX::BEGIN@17 at line 19 # spent 490µs making 1 call to Spreadsheet::ParseXLSX::Decryptor::BEGIN@19
# spent 1µs making 1 call to UNIVERSAL::import |
| 20 | |||||
| 21 | sub open { | ||||
| 22 | my $class = shift; | ||||
| 23 | |||||
| 24 | my ($filename, $password) = @_; | ||||
| 25 | |||||
| 26 | $password = $password || 'VelvetSweatshop'; | ||||
| 27 | |||||
| 28 | my ($infoFH, $packageFH) = $class->_getCompoundData($filename, ['EncryptionInfo', 'EncryptedPackage']); | ||||
| 29 | |||||
| 30 | return unless $infoFH; | ||||
| 31 | |||||
| 32 | my $buffer; | ||||
| 33 | $infoFH->read($buffer, 8); | ||||
| 34 | my ($majorVers, $minorVers) = unpack('s<s<', $buffer); | ||||
| 35 | |||||
| 36 | my $xlsx; | ||||
| 37 | if ($majorVers == 4 && $minorVers == 4) { | ||||
| 38 | $xlsx = $class->_agileDecryption($infoFH, $packageFH, $password); | ||||
| 39 | } else { | ||||
| 40 | $xlsx = $class->_standardDecryption($infoFH, $packageFH, $password); | ||||
| 41 | } | ||||
| 42 | |||||
| 43 | return $xlsx; | ||||
| 44 | } | ||||
| 45 | |||||
| 46 | sub _getCompoundData { | ||||
| 47 | my $class = shift; | ||||
| 48 | my ($filename, $names) = @_; | ||||
| 49 | |||||
| 50 | my @files; | ||||
| 51 | |||||
| 52 | my $storage = OLE::Storage_Lite->new($filename); | ||||
| 53 | |||||
| 54 | foreach my $name (@{$names}) { | ||||
| 55 | my @data = $storage->getPpsSearch([OLE::Storage_Lite::Asc2Ucs($name)], 1, 1); | ||||
| 56 | if ($#data < 0) { | ||||
| 57 | push @files, undef; | ||||
| 58 | } else { | ||||
| 59 | my $fh = File::Temp->new; | ||||
| 60 | binmode($fh); | ||||
| 61 | $fh->write($data[0]->{Data}); | ||||
| 62 | $fh->seek(0, 0); | ||||
| 63 | push @files, $fh; | ||||
| 64 | } | ||||
| 65 | } | ||||
| 66 | |||||
| 67 | return @files; | ||||
| 68 | } | ||||
| 69 | |||||
| 70 | sub _standardDecryption { | ||||
| 71 | my $class = shift; | ||||
| 72 | my ($infoFH, $packageFH, $password) = @_; | ||||
| 73 | |||||
| 74 | my $buffer; | ||||
| 75 | my $n = $infoFH->read($buffer, 24); | ||||
| 76 | |||||
| 77 | my ($encryptionHeaderSize, undef, undef, $algID, $algIDHash, $keyBits) = unpack('L<*', $buffer); | ||||
| 78 | |||||
| 79 | $infoFH->seek($encryptionHeaderSize - 0x14, IO::File::SEEK_CUR); | ||||
| 80 | |||||
| 81 | $infoFH->read($buffer, 4); | ||||
| 82 | |||||
| 83 | my $saltSize = unpack('L<', $buffer); | ||||
| 84 | |||||
| 85 | my ($salt, $encryptedVerifier, $verifierHashSize, $encryptedVerifierHash); | ||||
| 86 | |||||
| 87 | $infoFH->read($salt, 16); | ||||
| 88 | $infoFH->read($encryptedVerifier, 16); | ||||
| 89 | |||||
| 90 | $infoFH->read($buffer, 4); | ||||
| 91 | $verifierHashSize = unpack('L<', $buffer); | ||||
| 92 | |||||
| 93 | $infoFH->read($encryptedVerifierHash, 32); | ||||
| 94 | $infoFH->close(); | ||||
| 95 | |||||
| 96 | my ($cipherAlgorithm, $hashAlgorithm); | ||||
| 97 | |||||
| 98 | if ($algID == 0x0000660E || $algID == 0x0000660F || $algID == 0x0000660E) { | ||||
| 99 | $cipherAlgorithm = 'AES'; | ||||
| 100 | } else { | ||||
| 101 | die sprintf('Unsupported encryption algorithm: 0x%.8x', $algID); | ||||
| 102 | } | ||||
| 103 | |||||
| 104 | if ($algIDHash == 0x00008004) { | ||||
| 105 | $hashAlgorithm = 'SHA-1'; | ||||
| 106 | } else { | ||||
| 107 | die sprintf('Unsupported hash algorithm: 0x%.8x', $algIDHash); | ||||
| 108 | } | ||||
| 109 | |||||
| 110 | my $decryptor = Spreadsheet::ParseXLSX::Decryptor::Standard->new({ | ||||
| 111 | cipherAlgorithm => $cipherAlgorithm, | ||||
| 112 | cipherChaining => 'ECB', | ||||
| 113 | hashAlgorithm => $hashAlgorithm, | ||||
| 114 | salt => $salt, | ||||
| 115 | password => $password, | ||||
| 116 | keyBits => $keyBits, | ||||
| 117 | spinCount => 50000 | ||||
| 118 | } | ||||
| 119 | ); | ||||
| 120 | |||||
| 121 | $decryptor->verifyPassword($encryptedVerifier, $encryptedVerifierHash); | ||||
| 122 | |||||
| 123 | my $fh = File::Temp->new; | ||||
| 124 | binmode($fh); | ||||
| 125 | |||||
| 126 | my $inbuf; | ||||
| 127 | $packageFH->read($inbuf, 8); | ||||
| 128 | my $fileSize = unpack('L<', $inbuf); | ||||
| 129 | |||||
| 130 | $decryptor->decryptFile($packageFH, $fh, 1024, $fileSize); | ||||
| 131 | |||||
| 132 | $fh->seek(0, 0); | ||||
| 133 | |||||
| 134 | return $fh; | ||||
| 135 | } | ||||
| 136 | |||||
| 137 | sub _agileDecryption { | ||||
| 138 | my $class = shift; | ||||
| 139 | my ($infoFH, $packageFH, $password) = @_; | ||||
| 140 | |||||
| 141 | my $xml = XML::Twig->new; | ||||
| 142 | $xml->parse($infoFH); | ||||
| 143 | |||||
| 144 | my ($info) = $xml->find_nodes('//encryption/keyEncryptors/keyEncryptor/p:encryptedKey'); | ||||
| 145 | |||||
| 146 | my $encryptedVerifierHashInput = MIME::Base64::decode($info->att('encryptedVerifierHashInput')); | ||||
| 147 | my $encryptedVerifierHashValue = MIME::Base64::decode($info->att('encryptedVerifierHashValue')); | ||||
| 148 | my $encryptedKeyValue = MIME::Base64::decode($info->att('encryptedKeyValue')); | ||||
| 149 | my $hashSize = 0 + $info->att('hashSize'); | ||||
| 150 | |||||
| 151 | my $keyDecryptor = Spreadsheet::ParseXLSX::Decryptor::Agile->new({ | ||||
| 152 | cipherAlgorithm => $info->att('cipherAlgorithm'), | ||||
| 153 | cipherChaining => $info->att('cipherChaining'), | ||||
| 154 | hashAlgorithm => $info->att('hashAlgorithm'), | ||||
| 155 | salt => MIME::Base64::decode($info->att('saltValue')), | ||||
| 156 | password => $password, | ||||
| 157 | keyBits => 0 + $info->att('keyBits'), | ||||
| 158 | spinCount => 0 + $info->att('spinCount'), | ||||
| 159 | blockSize => 0 + $info->att('blockSize') | ||||
| 160 | } | ||||
| 161 | ); | ||||
| 162 | |||||
| 163 | $keyDecryptor->verifyPassword($encryptedVerifierHashInput, $encryptedVerifierHashValue, $hashSize); | ||||
| 164 | |||||
| 165 | my $key = $keyDecryptor->decrypt($encryptedKeyValue, "\x14\x6e\x0b\xe7\xab\xac\xd0\xd6"); | ||||
| 166 | |||||
| 167 | ($info) = $xml->find_nodes('//encryption/keyData'); | ||||
| 168 | |||||
| 169 | my $fileDecryptor = Spreadsheet::ParseXLSX::Decryptor::Agile->new({ | ||||
| 170 | cipherAlgorithm => $info->att('cipherAlgorithm'), | ||||
| 171 | cipherChaining => $info->att('cipherChaining'), | ||||
| 172 | hashAlgorithm => $info->att('hashAlgorithm'), | ||||
| 173 | salt => MIME::Base64::decode($info->att('saltValue')), | ||||
| 174 | password => $password, | ||||
| 175 | keyBits => 0 + $info->att('keyBits'), | ||||
| 176 | blockSize => 0 + $info->att('blockSize') | ||||
| 177 | } | ||||
| 178 | ); | ||||
| 179 | |||||
| 180 | my $fh = File::Temp->new; | ||||
| 181 | binmode($fh); | ||||
| 182 | |||||
| 183 | my $inbuf; | ||||
| 184 | $packageFH->read($inbuf, 8); | ||||
| 185 | my $fileSize = unpack('L<', $inbuf); | ||||
| 186 | |||||
| 187 | $fileDecryptor->decryptFile($packageFH, $fh, 4096, $key, $fileSize); | ||||
| 188 | |||||
| 189 | $fh->seek(0, 0); | ||||
| 190 | |||||
| 191 | return $fh; | ||||
| 192 | } | ||||
| 193 | |||||
| 194 | sub new { | ||||
| 195 | my $class = shift; | ||||
| 196 | my ($args) = @_; | ||||
| 197 | |||||
| 198 | my $self = {%$args}; | ||||
| 199 | $self->{keyLength} = $self->{keyBits} / 8; | ||||
| 200 | |||||
| 201 | if ($self->{hashAlgorithm} eq 'SHA512') { | ||||
| 202 | $self->{hashProc} = \&Digest::SHA::sha512; | ||||
| 203 | } elsif (($self->{hashAlgorithm} eq 'SHA-1') || ($self->{hashAlgorithm} eq 'SHA1')) { | ||||
| 204 | $self->{hashProc} = \&Digest::SHA::sha1; | ||||
| 205 | } elsif ($self->{hashAlgorithm} eq 'SHA256') { | ||||
| 206 | $self->{hashProc} = \&Digest::SHA::sha256; | ||||
| 207 | } else { | ||||
| 208 | die "Unsupported hash algorithm: $self->{hashAlgorithm}"; | ||||
| 209 | } | ||||
| 210 | |||||
| 211 | return bless $self, $class; | ||||
| 212 | } | ||||
| 213 | |||||
| 214 | =begin Pod::Coverage | ||||
| 215 | |||||
| 216 | new | ||||
| 217 | open | ||||
| 218 | |||||
| 219 | =end Pod::Coverage | ||||
| 220 | |||||
| 221 | =cut | ||||
| 222 | |||||
| 223 | 1 | 2µs | 1; |