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 | BEGIN@12 | Spreadsheet::ParseXLSX::Decryptor::
1 | 1 | 1 | 410µs | 490µs | BEGIN@19 | Spreadsheet::ParseXLSX::Decryptor::
1 | 1 | 1 | 369µs | 453µs | BEGIN@18 | Spreadsheet::ParseXLSX::Decryptor::
1 | 1 | 1 | 215µs | 348µs | BEGIN@15 | Spreadsheet::ParseXLSX::Decryptor::
1 | 1 | 1 | 183µs | 228µs | BEGIN@11 | Spreadsheet::ParseXLSX::Decryptor::
1 | 1 | 1 | 178µs | 9.84ms | BEGIN@10 | Spreadsheet::ParseXLSX::Decryptor::
1 | 1 | 1 | 13µs | 15µs | BEGIN@3 | Spreadsheet::ParseXLSX::Decryptor::
1 | 1 | 1 | 8µs | 18µs | BEGIN@16 | Spreadsheet::ParseXLSX::Decryptor::
1 | 1 | 1 | 4µs | 25µs | BEGIN@4 | Spreadsheet::ParseXLSX::Decryptor::
1 | 1 | 1 | 3µs | 3µs | BEGIN@13 | Spreadsheet::ParseXLSX::Decryptor::
1 | 1 | 1 | 1µs | 1µs | BEGIN@14 | Spreadsheet::ParseXLSX::Decryptor::
0 | 0 | 0 | 0s | 0s | _agileDecryption | Spreadsheet::ParseXLSX::Decryptor::
0 | 0 | 0 | 0s | 0s | _getCompoundData | Spreadsheet::ParseXLSX::Decryptor::
0 | 0 | 0 | 0s | 0s | _standardDecryption | Spreadsheet::ParseXLSX::Decryptor::
0 | 0 | 0 | 0s | 0s | new | Spreadsheet::ParseXLSX::Decryptor::
0 | 0 | 0 | 0s | 0s | open | Spreadsheet::ParseXLSX::Decryptor::
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; |