Profile of /home/micha/Projekt/spreadsheet-parsexlsx/lib/Spreadsheet/ParseXLSX/Decryptor.pm

← Index
NYTProf Performance Profile «line view»
For t/bug-md-11.t
Run on Fri Mar 8 13:27:24 2024
Reported on Fri Mar 8 13:30:23 2024

Filename | /home/micha/Projekt/spreadsheet-parsexlsx/lib/Spreadsheet/ParseXLSX/Decryptor.pm
Statements | Executed 23 statements in 974µs

Subroutines
Calls | P | F | Exclusive Time | Inclusive Time | Subroutine
--- | --- | --- | --- | --- | ---
1 | 1 | 1 | 1.36ms | 1.66ms | Spreadsheet::ParseXLSX::Decryptor::::BEGIN@12Spreadsheet::ParseXLSX::Decryptor::BEGIN@12
1 | 1 | 1 | 410µs | 490µs | Spreadsheet::ParseXLSX::Decryptor::::BEGIN@19Spreadsheet::ParseXLSX::Decryptor::BEGIN@19
1 | 1 | 1 | 369µs | 453µs | Spreadsheet::ParseXLSX::Decryptor::::BEGIN@18Spreadsheet::ParseXLSX::Decryptor::BEGIN@18
1 | 1 | 1 | 215µs | 348µs | Spreadsheet::ParseXLSX::Decryptor::::BEGIN@15Spreadsheet::ParseXLSX::Decryptor::BEGIN@15
1 | 1 | 1 | 183µs | 228µs | Spreadsheet::ParseXLSX::Decryptor::::BEGIN@11Spreadsheet::ParseXLSX::Decryptor::BEGIN@11
1 | 1 | 1 | 178µs | 9.84ms | Spreadsheet::ParseXLSX::Decryptor::::BEGIN@10Spreadsheet::ParseXLSX::Decryptor::BEGIN@10
1 | 1 | 1 | 13µs | 15µs | Spreadsheet::ParseXLSX::Decryptor::::BEGIN@3Spreadsheet::ParseXLSX::Decryptor::BEGIN@3
1 | 1 | 1 | 8µs | 18µs | Spreadsheet::ParseXLSX::Decryptor::::BEGIN@16Spreadsheet::ParseXLSX::Decryptor::BEGIN@16
1 | 1 | 1 | 4µs | 25µs | Spreadsheet::ParseXLSX::Decryptor::::BEGIN@4Spreadsheet::ParseXLSX::Decryptor::BEGIN@4
1 | 1 | 1 | 3µs | 3µs | Spreadsheet::ParseXLSX::Decryptor::::BEGIN@13Spreadsheet::ParseXLSX::Decryptor::BEGIN@13
1 | 1 | 1 | 1µs | 1µs | Spreadsheet::ParseXLSX::Decryptor::::BEGIN@14Spreadsheet::ParseXLSX::Decryptor::BEGIN@14
0 | 0 | 0 | 0s | 0s | Spreadsheet::ParseXLSX::Decryptor::::_agileDecryptionSpreadsheet::ParseXLSX::Decryptor::_agileDecryption
0 | 0 | 0 | 0s | 0s | Spreadsheet::ParseXLSX::Decryptor::::_getCompoundDataSpreadsheet::ParseXLSX::Decryptor::_getCompoundData
0 | 0 | 0 | 0s | 0s | Spreadsheet::ParseXLSX::Decryptor::::_standardDecryptionSpreadsheet::ParseXLSX::Decryptor::_standardDecryption
0 | 0 | 0 | 0s | 0s | Spreadsheet::ParseXLSX::Decryptor::::newSpreadsheet::ParseXLSX::Decryptor::new
0 | 0 | 0 | 0s | 0s | Spreadsheet::ParseXLSX::Decryptor::::openSpreadsheet::ParseXLSX::Decryptor::open

Call graph for these subroutines as a Graphviz dot language file.

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 use strict; # 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 use warnings; # 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 use Crypt::Mode::CBC; # 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 use Crypt::Mode::ECB; # 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 use Digest::SHA (); # 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 use Encode (); # 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 use File::Temp (); # 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 use MIME::Base64 (); # 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 use OLE::Storage_Lite; # 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 use Spreadsheet::ParseXLSX::Decryptor::Standard; # 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 use Spreadsheet::ParseXLSX::Decryptor::Agile; # 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;