You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TestEncryptor.java 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.poifs.crypt.tests;
  16. import static org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM;
  17. import static org.apache.poi.poifs.crypt.CryptoFunctions.getMessageDigest;
  18. import static org.junit.jupiter.api.Assertions.assertArrayEquals;
  19. import static org.junit.jupiter.api.Assertions.assertEquals;
  20. import static org.junit.jupiter.api.Assertions.assertFalse;
  21. import static org.junit.jupiter.api.Assertions.assertNotNull;
  22. import static org.junit.jupiter.api.Assertions.assertTrue;
  23. import static org.junit.jupiter.api.Assumptions.assumeTrue;
  24. import java.io.File;
  25. import java.io.FileOutputStream;
  26. import java.io.IOException;
  27. import java.io.InputStream;
  28. import java.io.OutputStream;
  29. import java.security.DigestInputStream;
  30. import java.security.GeneralSecurityException;
  31. import java.security.MessageDigest;
  32. import java.util.Iterator;
  33. import java.util.Random;
  34. import javax.crypto.Cipher;
  35. import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
  36. import org.apache.poi.POIDataSamples;
  37. import org.apache.poi.openxml4j.opc.ContentTypes;
  38. import org.apache.poi.openxml4j.opc.OPCPackage;
  39. import org.apache.poi.poifs.crypt.CipherAlgorithm;
  40. import org.apache.poi.poifs.crypt.Decryptor;
  41. import org.apache.poi.poifs.crypt.EncryptionInfo;
  42. import org.apache.poi.poifs.crypt.EncryptionMode;
  43. import org.apache.poi.poifs.crypt.EncryptionVerifier;
  44. import org.apache.poi.poifs.crypt.Encryptor;
  45. import org.apache.poi.poifs.crypt.HashAlgorithm;
  46. import org.apache.poi.poifs.crypt.agile.AgileDecryptor;
  47. import org.apache.poi.poifs.crypt.agile.AgileEncryptionHeader;
  48. import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier;
  49. import org.apache.poi.poifs.filesystem.DirectoryNode;
  50. import org.apache.poi.poifs.filesystem.DocumentEntry;
  51. import org.apache.poi.poifs.filesystem.DocumentNode;
  52. import org.apache.poi.poifs.filesystem.Entry;
  53. import org.apache.poi.poifs.filesystem.POIFSFileSystem;
  54. import org.apache.poi.poifs.filesystem.TempFilePOIFSFileSystem;
  55. import org.apache.poi.util.IOUtils;
  56. import org.apache.poi.util.TempFile;
  57. import org.apache.poi.xwpf.usermodel.XWPFDocument;
  58. import org.apache.poi.xwpf.usermodel.XWPFParagraph;
  59. import org.junit.jupiter.api.Disabled;
  60. import org.junit.jupiter.api.Test;
  61. class TestEncryptor {
  62. @Test
  63. void binaryRC4Encryption() throws Exception {
  64. // please contribute a real sample file, which is binary rc4 encrypted
  65. // ... at least the output can be opened in Excel Viewer
  66. String password = "pass";
  67. final byte[] payloadExpected;
  68. try (InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleMultiCell.xlsx")) {
  69. payloadExpected = IOUtils.toByteArray(is);
  70. }
  71. UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream();
  72. try (POIFSFileSystem fs = new POIFSFileSystem()) {
  73. EncryptionInfo ei = new EncryptionInfo(EncryptionMode.binaryRC4);
  74. Encryptor enc = ei.getEncryptor();
  75. enc.confirmPassword(password);
  76. try (OutputStream os = enc.getDataStream(fs.getRoot())) {
  77. os.write(payloadExpected);
  78. }
  79. fs.writeFilesystem(bos);
  80. }
  81. final byte[] payloadActual;
  82. try (POIFSFileSystem fs = new POIFSFileSystem(bos.toInputStream())) {
  83. EncryptionInfo ei = new EncryptionInfo(fs);
  84. Decryptor dec = ei.getDecryptor();
  85. boolean b = dec.verifyPassword(password);
  86. assertTrue(b);
  87. try (InputStream is = dec.getDataStream(fs.getRoot())) {
  88. payloadActual = IOUtils.toByteArray(is);
  89. }
  90. }
  91. assertArrayEquals(payloadExpected, payloadActual);
  92. }
  93. @Test
  94. void tempFileAgileEncryption() throws Exception {
  95. String password = "pass";
  96. final byte[] payloadExpected;
  97. try (InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleMultiCell.xlsx")) {
  98. payloadExpected = IOUtils.toByteArray(is);
  99. }
  100. UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream();
  101. try (POIFSFileSystem fs = new TempFilePOIFSFileSystem()) {
  102. EncryptionInfo ei = new EncryptionInfo(EncryptionMode.agile);
  103. Encryptor enc = ei.getEncryptor();
  104. enc.confirmPassword(password);
  105. try (OutputStream os = enc.getDataStream(fs.getRoot())) {
  106. os.write(payloadExpected);
  107. }
  108. fs.writeFilesystem(bos);
  109. }
  110. final byte[] payloadActual;
  111. try (POIFSFileSystem fs = new POIFSFileSystem(bos.toInputStream())) {
  112. EncryptionInfo ei = new EncryptionInfo(fs);
  113. Decryptor dec = ei.getDecryptor();
  114. boolean b = dec.verifyPassword(password);
  115. assertTrue(b);
  116. try (InputStream is = dec.getDataStream(fs.getRoot())) {
  117. payloadActual = IOUtils.toByteArray(is);
  118. }
  119. }
  120. assertArrayEquals(payloadExpected, payloadActual);
  121. }
  122. @Test
  123. void agileEncryption() throws Exception {
  124. int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
  125. assumeTrue(maxKeyLen == 0x7FFFFFFF, "Please install JCE Unlimited Strength Jurisdiction Policy files for AES 256");
  126. File file = POIDataSamples.getDocumentInstance().getFile("bug53475-password-is-pass.docx");
  127. String pass = "pass";
  128. final byte[] payloadExpected, encPackExpected;
  129. final long decPackLenExpected;
  130. final EncryptionInfo infoExpected;
  131. final Decryptor decExpected;
  132. try (POIFSFileSystem nfs = new POIFSFileSystem(file, true)) {
  133. // Check the encryption details
  134. infoExpected = new EncryptionInfo(nfs);
  135. decExpected = Decryptor.getInstance(infoExpected);
  136. boolean passed = decExpected.verifyPassword(pass);
  137. assertTrue(passed, "Unable to process: document is encrypted");
  138. // extract the payload
  139. try (InputStream is = decExpected.getDataStream(nfs)) {
  140. payloadExpected = IOUtils.toByteArray(is);
  141. }
  142. decPackLenExpected = decExpected.getLength();
  143. assertEquals(decPackLenExpected, payloadExpected.length);
  144. final DirectoryNode root = nfs.getRoot();
  145. final DocumentEntry entry = (DocumentEntry)root.getEntry(Decryptor.DEFAULT_POIFS_ENTRY);
  146. try (InputStream is = root.createDocumentInputStream(entry)) {
  147. // ignore padding block
  148. encPackExpected = IOUtils.toByteArray(is, entry.getSize()-16);
  149. }
  150. }
  151. // check that same verifier/salt lead to same hashes
  152. final byte[] verifierSaltExpected = infoExpected.getVerifier().getSalt();
  153. final byte[] verifierExpected = decExpected.getVerifier();
  154. final byte[] keySalt = infoExpected.getHeader().getKeySalt();
  155. final byte[] keySpec = decExpected.getSecretKey().getEncoded();
  156. final byte[] integritySalt = decExpected.getIntegrityHmacKey();
  157. // the hmacs of the file always differ, as we use PKCS5-padding to pad the bytes
  158. // whereas office just uses random bytes
  159. // byte integrityHash[] = d.getIntegrityHmacValue();
  160. final EncryptionInfo infoActual = new EncryptionInfo(
  161. EncryptionMode.agile
  162. , infoExpected.getVerifier().getCipherAlgorithm()
  163. , infoExpected.getVerifier().getHashAlgorithm()
  164. , infoExpected.getHeader().getKeySize()
  165. , infoExpected.getHeader().getBlockSize()
  166. , infoExpected.getVerifier().getChainingMode()
  167. );
  168. Encryptor e = Encryptor.getInstance(infoActual);
  169. e.confirmPassword(pass, keySpec, keySalt, verifierExpected, verifierSaltExpected, integritySalt);
  170. UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream();
  171. try (POIFSFileSystem fs = new POIFSFileSystem()) {
  172. try (OutputStream os = e.getDataStream(fs)) {
  173. os.write(payloadExpected);
  174. }
  175. fs.writeFilesystem(bos);
  176. }
  177. final EncryptionInfo infoActual2;
  178. final byte[] payloadActual, encPackActual;
  179. final long decPackLenActual;
  180. try (POIFSFileSystem nfs = new POIFSFileSystem(bos.toInputStream())) {
  181. infoActual2 = new EncryptionInfo(nfs.getRoot());
  182. Decryptor decActual = Decryptor.getInstance(infoActual2);
  183. boolean passed = decActual.verifyPassword(pass);
  184. assertTrue(passed, "Unable to process: document is encrypted");
  185. // extract the payload
  186. try (InputStream is = decActual.getDataStream(nfs)) {
  187. payloadActual = IOUtils.toByteArray(is);
  188. }
  189. decPackLenActual = decActual.getLength();
  190. final DirectoryNode root = nfs.getRoot();
  191. final DocumentEntry entry = (DocumentEntry)root.getEntry(Decryptor.DEFAULT_POIFS_ENTRY);
  192. try (InputStream is = root.createDocumentInputStream(entry)) {
  193. // ignore padding block
  194. encPackActual = IOUtils.toByteArray(is, entry.getSize()-16);
  195. }
  196. }
  197. AgileEncryptionHeader aehExpected = (AgileEncryptionHeader)infoExpected.getHeader();
  198. AgileEncryptionHeader aehActual = (AgileEncryptionHeader)infoActual.getHeader();
  199. assertArrayEquals(aehExpected.getEncryptedHmacKey(), aehActual.getEncryptedHmacKey());
  200. assertEquals(decPackLenExpected, decPackLenActual);
  201. assertArrayEquals(payloadExpected, payloadActual);
  202. assertArrayEquals(encPackExpected, encPackActual);
  203. }
  204. @Test
  205. void standardEncryption() throws Exception {
  206. File file = POIDataSamples.getDocumentInstance().getFile("bug53475-password-is-solrcell.docx");
  207. final String pass = "solrcell";
  208. final byte[] payloadExpected;
  209. final EncryptionInfo infoExpected;
  210. final Decryptor d;
  211. try (POIFSFileSystem nfs = new POIFSFileSystem(file, true)) {
  212. // Check the encryption details
  213. infoExpected = new EncryptionInfo(nfs);
  214. d = Decryptor.getInstance(infoExpected);
  215. boolean passed = d.verifyPassword(pass);
  216. assertTrue(passed, "Unable to process: document is encrypted");
  217. // extract the payload
  218. try (InputStream is = d.getDataStream(nfs)) {
  219. payloadExpected = IOUtils.toByteArray(is);
  220. }
  221. }
  222. // check that same verifier/salt lead to same hashes
  223. final byte[] verifierSaltExpected = infoExpected.getVerifier().getSalt();
  224. final byte[] verifierExpected = d.getVerifier();
  225. final byte[] keySpec = d.getSecretKey().getEncoded();
  226. final byte[] keySalt = infoExpected.getHeader().getKeySalt();
  227. final EncryptionInfo infoActual = new EncryptionInfo(
  228. EncryptionMode.standard
  229. , infoExpected.getVerifier().getCipherAlgorithm()
  230. , infoExpected.getVerifier().getHashAlgorithm()
  231. , infoExpected.getHeader().getKeySize()
  232. , infoExpected.getHeader().getBlockSize()
  233. , infoExpected.getVerifier().getChainingMode()
  234. );
  235. final Encryptor e = Encryptor.getInstance(infoActual);
  236. e.confirmPassword(pass, keySpec, keySalt, verifierExpected, verifierSaltExpected, null);
  237. assertArrayEquals(infoExpected.getVerifier().getEncryptedVerifier(), infoActual.getVerifier().getEncryptedVerifier());
  238. assertArrayEquals(infoExpected.getVerifier().getEncryptedVerifierHash(), infoActual.getVerifier().getEncryptedVerifierHash());
  239. // now we use a newly generated salt/verifier and check
  240. // if the file content is still the same
  241. final UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream(50000);
  242. try (POIFSFileSystem fs = new POIFSFileSystem()) {
  243. final EncryptionInfo infoActual2 = new EncryptionInfo(
  244. EncryptionMode.standard
  245. , infoExpected.getVerifier().getCipherAlgorithm()
  246. , infoExpected.getVerifier().getHashAlgorithm()
  247. , infoExpected.getHeader().getKeySize()
  248. , infoExpected.getHeader().getBlockSize()
  249. , infoExpected.getVerifier().getChainingMode()
  250. );
  251. final Encryptor e2 = Encryptor.getInstance(infoActual2);
  252. e2.confirmPassword(pass);
  253. try (OutputStream os = e2.getDataStream(fs)) {
  254. os.write(payloadExpected);
  255. }
  256. fs.writeFilesystem(bos);
  257. }
  258. final byte[] payloadActual;
  259. try (POIFSFileSystem nfs = new POIFSFileSystem(bos.toInputStream())) {
  260. final EncryptionInfo ei = new EncryptionInfo(nfs);
  261. Decryptor d2 = Decryptor.getInstance(ei);
  262. assertTrue(d2.verifyPassword(pass), "Unable to process: document is encrypted");
  263. try (InputStream is = d2.getDataStream(nfs)) {
  264. payloadActual = IOUtils.toByteArray(is);
  265. }
  266. }
  267. assertArrayEquals(payloadExpected, payloadActual);
  268. }
  269. /**
  270. * Ensure we can encrypt a package that is missing the Core
  271. * Properties, eg one from dodgy versions of Jasper Reports
  272. * See https://github.com/nestoru/xlsxenc/
  273. */
  274. @Test
  275. void encryptPackageWithoutCoreProperties() throws Exception {
  276. // Open our file without core properties
  277. UnsynchronizedByteArrayOutputStream baos = new UnsynchronizedByteArrayOutputStream();
  278. try (InputStream is = POIDataSamples.getOpenXML4JInstance().openResourceAsStream("OPCCompliance_NoCoreProperties.xlsx");
  279. OPCPackage pkg = OPCPackage.open(is)) {
  280. // It doesn't have any core properties yet
  281. assertEquals(0, pkg.getPartsByContentType(ContentTypes.CORE_PROPERTIES_PART).size());
  282. assertNotNull(pkg.getPackageProperties());
  283. assertNotNull(pkg.getPackageProperties().getLanguageProperty());
  284. assertFalse(pkg.getPackageProperties().getLanguageProperty().isPresent());
  285. // Encrypt it
  286. EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile);
  287. Encryptor enc = info.getEncryptor();
  288. enc.confirmPassword("password");
  289. try (POIFSFileSystem fs = new POIFSFileSystem()) {
  290. try (OutputStream os = enc.getDataStream(fs)) {
  291. pkg.save(os);
  292. }
  293. // Save the resulting OLE2 document, and re-open it
  294. fs.writeFilesystem(baos);
  295. }
  296. }
  297. try (POIFSFileSystem inpFS = new POIFSFileSystem(baos.toInputStream())) {
  298. // Check we can decrypt it
  299. EncryptionInfo info = new EncryptionInfo(inpFS);
  300. Decryptor d = Decryptor.getInstance(info);
  301. assertTrue(d.verifyPassword("password"));
  302. try (OPCPackage inpPkg = OPCPackage.open(d.getDataStream(inpFS))) {
  303. // Check it now has empty core properties
  304. assertEquals(1, inpPkg.getPartsByContentType(ContentTypes.CORE_PROPERTIES_PART).size());
  305. assertNotNull(inpPkg.getPackageProperties());
  306. assertNotNull(inpPkg.getPackageProperties().getLanguageProperty());
  307. assertFalse(inpPkg.getPackageProperties().getLanguageProperty().isPresent());
  308. }
  309. }
  310. }
  311. @Test
  312. @Disabled
  313. void inPlaceRewrite() throws Exception {
  314. File f = TempFile.createTempFile("protected_agile", ".docx");
  315. try (FileOutputStream fos = new FileOutputStream(f);
  316. InputStream fis = POIDataSamples.getPOIFSInstance().openResourceAsStream("protected_agile.docx")) {
  317. IOUtils.copy(fis, fos);
  318. }
  319. try (POIFSFileSystem fs = new POIFSFileSystem(f, false)) {
  320. // decrypt the protected file - in this case it was encrypted with the default password
  321. EncryptionInfo encInfo = new EncryptionInfo(fs);
  322. Decryptor d = encInfo.getDecryptor();
  323. boolean b = d.verifyPassword(Decryptor.DEFAULT_PASSWORD);
  324. assertTrue(b);
  325. try (InputStream docIS = d.getDataStream(fs);
  326. XWPFDocument docx = new XWPFDocument(docIS)) {
  327. // do some strange things with it ;)
  328. XWPFParagraph p = docx.getParagraphArray(0);
  329. p.insertNewRun(0).setText("POI was here! All your base are belong to us!");
  330. p.insertNewRun(1).addBreak();
  331. // and encrypt it again
  332. Encryptor e = encInfo.getEncryptor();
  333. e.confirmPassword("AYBABTU");
  334. try (OutputStream os = e.getDataStream(fs)) {
  335. docx.write(os);
  336. }
  337. }
  338. }
  339. }
  340. private void listEntry(DocumentNode de, String ext, String path) throws IOException {
  341. path += "\\" + de.getName().replaceAll("[\\p{Cntrl}]", "_");
  342. System.out.println(ext+": "+path+" ("+de.getSize()+" bytes)");
  343. String name = de.getName().replaceAll("[\\p{Cntrl}]", "_");
  344. InputStream is = ((DirectoryNode)de.getParent()).createDocumentInputStream(de);
  345. FileOutputStream fos = new FileOutputStream("solr."+name+"."+ext);
  346. IOUtils.copy(is, fos);
  347. fos.close();
  348. is.close();
  349. }
  350. @SuppressWarnings("unused")
  351. private void listDir(DirectoryNode dn, String ext, String path) throws IOException {
  352. path += "\\" + dn.getName().replace('\u0006', '_');
  353. System.out.println(ext+": "+path+" ("+dn.getStorageClsid()+")");
  354. Iterator<Entry> iter = dn.getEntries();
  355. while (iter.hasNext()) {
  356. Entry ent = iter.next();
  357. if (ent instanceof DirectoryNode) {
  358. listDir((DirectoryNode)ent, ext, path);
  359. } else {
  360. listEntry((DocumentNode)ent, ext, path);
  361. }
  362. }
  363. }
  364. /*
  365. * this test simulates the generation of bugs 60320 sample file
  366. * as the padding bytes of the EncryptedPackage stream are random or in POIs case PKCS5-padded
  367. * one would need to mock those bytes to get the same hmacValues - see diff below
  368. *
  369. * this use-case is experimental - for the time being the setters of the encryption classes
  370. * are spreaded between two packages and are protected - so you would need to violate
  371. * the packages rules and provide a helper class in the *poifs.crypt package-namespace.
  372. * the default way of defining the encryption settings is via the EncryptionInfo class
  373. */
  374. @Test
  375. void bug60320CustomEncrypt() throws Exception {
  376. int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
  377. assumeTrue(maxKeyLen == 0x7FFFFFFF, "Please install JCE Unlimited Strength Jurisdiction Policy files for AES 256");
  378. // --- src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java (revision 1766745)
  379. // +++ src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java (working copy)
  380. // @@ -208,6 +208,13 @@
  381. // protected int invokeCipher(int posInChunk, boolean doFinal) throws GeneralSecurityException {
  382. // byte plain[] = (_plainByteFlags.isEmpty()) ? null : _chunk.clone();
  383. //
  384. // + if (posInChunk < 4096) {
  385. // + _cipher.update(_chunk, 0, posInChunk, _chunk);
  386. // + byte bla[] = { (byte)0x7A,(byte)0x0F,(byte)0x27,(byte)0xF0,(byte)0x17,(byte)0x6E,(byte)0x77,(byte)0x05,(byte)0xB9,(byte)0xDA,(byte)0x49,(byte)0xF9,(byte)0xD7,(byte)0x8E,(byte)0x03,(byte)0x1D };
  387. // + System.arraycopy(bla, 0, _chunk, posInChunk-2, bla.length);
  388. // + return posInChunk-2+bla.length;
  389. // + }
  390. // +
  391. // int ciLen = (doFinal)
  392. // ? _cipher.doFinal(_chunk, 0, posInChunk, _chunk)
  393. // : _cipher.update(_chunk, 0, posInChunk, _chunk);
  394. //
  395. // --- src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java (revision 1766745)
  396. // +++ src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java (working copy)
  397. //
  398. // @@ -300,7 +297,7 @@
  399. // protected static Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk, EncryptionInfo encryptionInfo, SecretKey skey, int encryptionMode)
  400. // throws GeneralSecurityException {
  401. // EncryptionHeader header = encryptionInfo.getHeader();
  402. // - String padding = (lastChunk ? "PKCS5Padding" : "NoPadding");
  403. // + String padding = "NoPadding"; // (lastChunk ? "PKCS5Padding" : "NoPadding");
  404. // if (existing == null || !existing.getAlgorithm().endsWith(padding)) {
  405. // existing = getCipher(skey, header.getCipherAlgorithm(), header.getChainingMode(), header.getKeySalt(), encryptionMode, padding);
  406. // }
  407. final EncryptionInfo infoOrig;
  408. final byte[] zipInput, epOrigBytes;
  409. try (InputStream is = POIDataSamples.getPOIFSInstance().openResourceAsStream("60320-protected.xlsx");
  410. POIFSFileSystem fsOrig = new POIFSFileSystem(is)) {
  411. infoOrig = new EncryptionInfo(fsOrig);
  412. Decryptor decOrig = infoOrig.getDecryptor();
  413. boolean b = decOrig.verifyPassword("Test001!!");
  414. assertTrue(b);
  415. try (InputStream decIn = decOrig.getDataStream(fsOrig)) {
  416. zipInput = IOUtils.toByteArray(decIn);
  417. }
  418. try (InputStream epOrig = fsOrig.getRoot().createDocumentInputStream("EncryptedPackage")) {
  419. // ignore the 16 padding bytes
  420. epOrigBytes = IOUtils.toByteArray(epOrig, 9400);
  421. }
  422. }
  423. EncryptionInfo eiNew = new EncryptionInfo(EncryptionMode.agile);
  424. AgileEncryptionHeader aehHeader = (AgileEncryptionHeader)eiNew.getHeader();
  425. aehHeader.setCipherAlgorithm(CipherAlgorithm.aes128);
  426. aehHeader.setHashAlgorithm(HashAlgorithm.sha1);
  427. AgileEncryptionVerifier aehVerifier = (AgileEncryptionVerifier)eiNew.getVerifier();
  428. aehVerifier.setCipherAlgorithm(CipherAlgorithm.aes256);
  429. aehVerifier.setHashAlgorithm(HashAlgorithm.sha512);
  430. Encryptor enc = eiNew.getEncryptor();
  431. enc.confirmPassword("Test001!!",
  432. infoOrig.getDecryptor().getSecretKey().getEncoded(),
  433. infoOrig.getHeader().getKeySalt(),
  434. infoOrig.getDecryptor().getVerifier(),
  435. infoOrig.getVerifier().getSalt(),
  436. infoOrig.getDecryptor().getIntegrityHmacKey()
  437. );
  438. final byte[] epNewBytes;
  439. final EncryptionInfo infoReload;
  440. try (POIFSFileSystem fsNew = new POIFSFileSystem()) {
  441. try (OutputStream os = enc.getDataStream(fsNew)) {
  442. os.write(zipInput);
  443. }
  444. UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream();
  445. fsNew.writeFilesystem(bos);
  446. try (POIFSFileSystem fsReload = new POIFSFileSystem(bos.toInputStream())) {
  447. infoReload = new EncryptionInfo(fsReload);
  448. try (InputStream epReload = fsReload.getRoot().createDocumentInputStream("EncryptedPackage")) {
  449. epNewBytes = IOUtils.toByteArray(epReload, 9400);
  450. }
  451. }
  452. }
  453. assertArrayEquals(epOrigBytes, epNewBytes);
  454. Decryptor decReload = infoReload.getDecryptor();
  455. assertTrue(decReload.verifyPassword("Test001!!"));
  456. AgileEncryptionHeader aehOrig = (AgileEncryptionHeader)infoOrig.getHeader();
  457. AgileEncryptionHeader aehReload = (AgileEncryptionHeader)infoReload.getHeader();
  458. assertEquals(aehOrig.getBlockSize(), aehReload.getBlockSize());
  459. assertEquals(aehOrig.getChainingMode(), aehReload.getChainingMode());
  460. assertEquals(aehOrig.getCipherAlgorithm(), aehReload.getCipherAlgorithm());
  461. assertEquals(aehOrig.getCipherProvider(), aehReload.getCipherProvider());
  462. assertEquals(aehOrig.getCspName(), aehReload.getCspName());
  463. assertArrayEquals(aehOrig.getEncryptedHmacKey(), aehReload.getEncryptedHmacKey());
  464. // this only works, when the paddings are mocked to be the same ...
  465. // assertArrayEquals(aehOrig.getEncryptedHmacValue(), aehReload.getEncryptedHmacValue());
  466. assertEquals(aehOrig.getFlags(), aehReload.getFlags());
  467. assertEquals(aehOrig.getHashAlgorithm(), aehReload.getHashAlgorithm());
  468. assertArrayEquals(aehOrig.getKeySalt(), aehReload.getKeySalt());
  469. assertEquals(aehOrig.getKeySize(), aehReload.getKeySize());
  470. AgileEncryptionVerifier aevOrig = (AgileEncryptionVerifier)infoOrig.getVerifier();
  471. AgileEncryptionVerifier aevReload = (AgileEncryptionVerifier)infoReload.getVerifier();
  472. assertEquals(aevOrig.getBlockSize(), aevReload.getBlockSize());
  473. assertEquals(aevOrig.getChainingMode(), aevReload.getChainingMode());
  474. assertEquals(aevOrig.getCipherAlgorithm(), aevReload.getCipherAlgorithm());
  475. assertArrayEquals(aevOrig.getEncryptedKey(), aevReload.getEncryptedKey());
  476. assertArrayEquals(aevOrig.getEncryptedVerifier(), aevReload.getEncryptedVerifier());
  477. assertArrayEquals(aevOrig.getEncryptedVerifierHash(), aevReload.getEncryptedVerifierHash());
  478. assertEquals(aevOrig.getHashAlgorithm(), aevReload.getHashAlgorithm());
  479. assertEquals(aevOrig.getKeySize(), aevReload.getKeySize());
  480. assertArrayEquals(aevOrig.getSalt(), aevReload.getSalt());
  481. assertEquals(aevOrig.getSpinCount(), aevReload.getSpinCount());
  482. AgileDecryptor adOrig = (AgileDecryptor)infoOrig.getDecryptor();
  483. AgileDecryptor adReload = (AgileDecryptor)infoReload.getDecryptor();
  484. assertArrayEquals(adOrig.getIntegrityHmacKey(), adReload.getIntegrityHmacKey());
  485. // doesn't work without mocking ... see above
  486. // assertArrayEquals(adOrig.getIntegrityHmacValue(), adReload.getIntegrityHmacValue());
  487. assertArrayEquals(adOrig.getSecretKey().getEncoded(), adReload.getSecretKey().getEncoded());
  488. assertArrayEquals(adOrig.getVerifier(), adReload.getVerifier());
  489. }
  490. @Test
  491. void smallFile() throws IOException, GeneralSecurityException {
  492. final int tinyFileSize = 80_000_000;
  493. final String pass = "s3cr3t";
  494. File tmpFile = TempFile.createTempFile("tiny", ".bin");
  495. // create/populate empty file
  496. try (POIFSFileSystem poifs = new POIFSFileSystem();
  497. FileOutputStream fos = new FileOutputStream(tmpFile)) {
  498. poifs.writeFilesystem(fos);
  499. }
  500. EncryptionInfo info1 = new EncryptionInfo(EncryptionMode.agile);
  501. Encryptor enc = info1.getEncryptor();
  502. enc.confirmPassword(pass);
  503. final MessageDigest md = getMessageDigest(HashAlgorithm.sha256);
  504. // reopen as mmap-ed file
  505. try (POIFSFileSystem poifs = new POIFSFileSystem(tmpFile, false)) {
  506. try (OutputStream os = enc.getDataStream(poifs);
  507. RandomStream rs = new RandomStream(md)) {
  508. IOUtils.copy(rs, os, tinyFileSize);
  509. }
  510. poifs.writeFilesystem();
  511. }
  512. final byte[] digest1 = md.digest();
  513. md.reset();
  514. // reopen and check the digest
  515. try (POIFSFileSystem poifs = new POIFSFileSystem(tmpFile)) {
  516. EncryptionInfo info2 = new EncryptionInfo(poifs);
  517. Decryptor dec = info2.getDecryptor();
  518. boolean passOk = dec.verifyPassword(pass);
  519. assertTrue(passOk);
  520. try (InputStream is = dec.getDataStream(poifs);
  521. DigestInputStream dis = new DigestInputStream(is, md)) {
  522. IOUtils.copy(dis, NULL_OUTPUT_STREAM);
  523. }
  524. }
  525. final byte[] digest2 = md.digest();
  526. assertArrayEquals(digest1, digest2);
  527. boolean isDeleted = tmpFile.delete();
  528. assertTrue(isDeleted);
  529. }
  530. private static final class RandomStream extends InputStream {
  531. private final Random rand = new Random();
  532. private final byte[] buf = new byte[1024];
  533. private final MessageDigest md;
  534. private RandomStream(MessageDigest md) {
  535. this.md = md;
  536. }
  537. @Override
  538. public int read() {
  539. int ret = rand.nextInt(256);
  540. md.update((byte)ret);
  541. return ret;
  542. }
  543. @Override
  544. public int read(byte[] b, final int off, int len) {
  545. for (int start = off; start-off < len; start += buf.length) {
  546. rand.nextBytes(buf);
  547. int copyLen = Math.min(buf.length, len-(start-off));
  548. System.arraycopy(buf, 0, b, start, copyLen);
  549. md.update(buf, 0, copyLen);
  550. }
  551. return len;
  552. }
  553. }
  554. }