From e100cb756f13824185d40eccc7beaac2b44c59ab Mon Sep 17 00:00:00 2001 From: Maxim Valyanskiy Date: Thu, 27 May 2010 13:23:27 +0000 Subject: [PATCH] Initial support for reading AES-encrypted/write-protected OOXML files git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@948825 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/poi/poifs/crypt/Decryptor.java | 133 ++++++++++++++++++ .../poi/poifs/crypt/EncryptionHeader.java | 97 +++++++++++++ .../poi/poifs/crypt/EncryptionInfo.java | 72 ++++++++++ .../poi/poifs/crypt/EncryptionVerifier.java | 57 ++++++++ .../apache/poi/poifs/crypt/DecryptorTest.java | 68 +++++++++ .../poi/poifs/crypt/EncryptionInfoTest.java | 45 ++++++ test-data/poifs/protect.xlsx | Bin 0 -> 12968 bytes 7 files changed, 472 insertions(+) create mode 100644 src/java/org/apache/poi/poifs/crypt/Decryptor.java create mode 100644 src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java create mode 100644 src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java create mode 100644 src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java create mode 100644 src/testcases/org/apache/poi/poifs/crypt/DecryptorTest.java create mode 100644 src/testcases/org/apache/poi/poifs/crypt/EncryptionInfoTest.java create mode 100644 test-data/poifs/protect.xlsx diff --git a/src/java/org/apache/poi/poifs/crypt/Decryptor.java b/src/java/org/apache/poi/poifs/crypt/Decryptor.java new file mode 100644 index 0000000000..a47100d696 --- /dev/null +++ b/src/java/org/apache/poi/poifs/crypt/Decryptor.java @@ -0,0 +1,133 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.poifs.crypt; + +import org.apache.poi.poifs.filesystem.DocumentInputStream; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.util.LittleEndian; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +/** + * @author Maxim Valyanskiy + */ +public class Decryptor { + public static final String DEFAULT_PASSWORD="VelvetSweatshop"; + + private final EncryptionInfo info; + private byte[] passwordHash; + + public Decryptor(EncryptionInfo info) { + this.info = info; + } + + private void generatePasswordHash(String password) throws NoSuchAlgorithmException { + MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); + + sha1.update(info.getVerifier().getSalt()); + byte[] hash = sha1.digest(password.getBytes(Charset.forName("UTF-16LE"))); + + byte[] iterator = new byte[4]; + for (int i = 0; i<50000; i++) { + sha1.reset(); + + LittleEndian.putInt(iterator, i); + sha1.update(iterator); + hash = sha1.digest(hash); + } + + passwordHash = hash; + } + + private byte[] generateKey(int block) throws NoSuchAlgorithmException { + MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); + + sha1.update(passwordHash); + byte[] blockValue = new byte[4]; + LittleEndian.putInt(blockValue, block); + byte[] finalHash = sha1.digest(blockValue); + + int requiredKeyLength = info.getHeader().getKeySize()/8; + + byte[] buff = new byte[64]; + + Arrays.fill(buff, (byte) 0x36); + + for (int i=0; i0) { + zin.skip(zin.available()); + } + } + } +} diff --git a/src/testcases/org/apache/poi/poifs/crypt/EncryptionInfoTest.java b/src/testcases/org/apache/poi/poifs/crypt/EncryptionInfoTest.java new file mode 100644 index 0000000000..eb84727e33 --- /dev/null +++ b/src/testcases/org/apache/poi/poifs/crypt/EncryptionInfoTest.java @@ -0,0 +1,45 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.poifs.crypt; + +import junit.framework.TestCase; +import org.apache.poi.POIDataSamples; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; + +import java.io.IOException; + +/** + * @author Maxim Valyanskiy + */ +public class EncryptionInfoTest extends TestCase { + public void testEncryptionInfo() throws IOException { + POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protect.xlsx")); + + EncryptionInfo info = new EncryptionInfo(fs); + + assertEquals(3, info.getVersionMajor()); + assertEquals(2, info.getVersionMinor()); + + assertEquals(EncryptionHeader.ALGORITHM_AES_128, info.getHeader().getAlgorithm()); + assertEquals(EncryptionHeader.HASH_SHA1, info.getHeader().getHashAlgorithm()); + assertEquals(128, info.getHeader().getKeySize()); + assertEquals(EncryptionHeader.PROVIDER_AES, info.getHeader().getProviderType()); + assertEquals("Microsoft Enhanced RSA and AES Cryptographic Provider", info.getHeader().getCspName()); + + assertEquals(32, info.getVerifier().getVerifierHash().length); + } +} diff --git a/test-data/poifs/protect.xlsx b/test-data/poifs/protect.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..1767b14377539d30c5111fd616a612c87867fa9c GIT binary patch literal 12968 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 literal 0 HcmV?d00001