bug#51165: Add support for OOXML Agile Encryption
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1101397 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
5f671bd806
commit
6857223c5a
@ -122,6 +122,9 @@ under the License.
|
||||
<property name="main.commons-logging.jar" location="${main.lib}/commons-logging-1.1.jar"/>
|
||||
<property name="main.commons-logging.url"
|
||||
value="${repository.m2}/maven2/commons-logging/commons-logging/1.1/commons-logging-1.1.jar"/>
|
||||
<property name="main.commons-codec.jar" location="${main.lib}/commons-codec-1.5.jar"/>
|
||||
<property name="main.commons-codec.url"
|
||||
value="${repository.m2}/maven2/commons-codec/commons-codec/1.5/commons-codec-1.5.jar"/>
|
||||
<property name="main.log4j.jar" location="${main.lib}/log4j-1.2.13.jar"/>
|
||||
<property name="main.log4j.url" value="${repository.m2}/maven2/log4j/log4j/1.2.13/log4j-1.2.13.jar"/>
|
||||
<property name="main.junit.jar" location="${main.lib}/junit-3.8.1.jar"/>
|
||||
@ -166,6 +169,7 @@ under the License.
|
||||
|
||||
<path id="main.classpath">
|
||||
<pathelement location="${main.commons-logging.jar}"/>
|
||||
<pathelement location="${main.commons-codec.jar}"/>
|
||||
<pathelement location="${main.log4j.jar}"/>
|
||||
<pathelement location="${main.junit.jar}"/>
|
||||
</path>
|
||||
@ -295,6 +299,7 @@ under the License.
|
||||
<or>
|
||||
<and>
|
||||
<available file="${main.commons-logging.jar}"/>
|
||||
<available file="${main.commons-codec.jar}"/>
|
||||
<available file="${main.log4j.jar}"/>
|
||||
<available file="${main.junit.jar}"/>
|
||||
<available file="${main.ant.jar}"/>
|
||||
@ -311,6 +316,10 @@ under the License.
|
||||
<param name="sourcefile" value="${main.commons-logging.url}"/>
|
||||
<param name="destfile" value="${main.commons-logging.jar}"/>
|
||||
</antcall>
|
||||
<antcall target="downloadfile">
|
||||
<param name="sourcefile" value="${main.commons-codec.url}"/>
|
||||
<param name="destfile" value="${main.commons-codec.jar}"/>
|
||||
</antcall>
|
||||
<antcall target="downloadfile">
|
||||
<param name="sourcefile" value="${main.log4j.url}"/>
|
||||
<param name="destfile" value="${main.log4j.jar}"/>
|
||||
|
84
src/documentation/content/xdocs/encryption.xml
Normal file
84
src/documentation/content/xdocs/encryption.xml
Normal file
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
====================================================================
|
||||
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.
|
||||
====================================================================
|
||||
-->
|
||||
<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V1.3//EN" "./dtd/document-v13.dtd">
|
||||
|
||||
|
||||
<document>
|
||||
<header>
|
||||
<title>Apache POI - Encryption support</title>
|
||||
<authors>
|
||||
<person id="maxcom" name="Maxim Valyanskiy" email="maxcom@apache.org"/>
|
||||
</authors>
|
||||
</header>
|
||||
|
||||
<body>
|
||||
<section><title>Overview</title>
|
||||
<p>Apache POI contains support for reading few variants of encrypted office files: </p>
|
||||
<ul>
|
||||
<li>XLS - RC4 Encryption</li>
|
||||
<li>XML-based formats (XLSX, DOCX and etc) - AES Encryption</li>
|
||||
</ul>
|
||||
|
||||
<p>Some "write-protected" files are encrypted with build-in password, POI can read that files too.</p>
|
||||
</section>
|
||||
|
||||
<section><title>XLS</title>
|
||||
<p>When HSSF receive encrypted file, it tries to decode it with MSOffice build-in password.
|
||||
Use static method setCurrentUserPassword(String password) of org.apache.poi.hssf.record.crypto.Biff8EncryptionKey to
|
||||
set password. It sets thread local variable. Do not forget to reset it to null after text extraction.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section><title>XML-based formats</title>
|
||||
<p>XML-based formats are stored in OLE-package stream "EncryptedPackage". Use org.apache.poi.poifs.crypt.Decryptor
|
||||
to decode file:</p>
|
||||
|
||||
<source>
|
||||
EncryptionInfo info = new EncryptionInfo(filesystem);
|
||||
Decryptor d = new Decryptor(info);
|
||||
|
||||
try {
|
||||
if (!d.verifyPassword(password)) {
|
||||
throw new RuntimeException("Unable to process: document is encrypted");
|
||||
}
|
||||
|
||||
InputStream dataStream = d.getDataStream(filesystem);
|
||||
|
||||
// parse dataStream
|
||||
|
||||
} catch (GeneralSecurityException ex) {
|
||||
throw new RuntimeException("Unable to process encrypted document", ex);
|
||||
}
|
||||
</source>
|
||||
|
||||
<p>If you want to read file encrypted with build-in password, use Decryptor.DEFAULT_PASSWORD.</p>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
<footer>
|
||||
<legal>
|
||||
Copyright (c) @year@ The Apache Software Foundation. All rights reserved.
|
||||
</legal>
|
||||
</footer>
|
||||
</document>
|
||||
|
||||
|
||||
|
||||
|
244
src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java
Normal file
244
src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java
Normal file
@ -0,0 +1,244 @@
|
||||
/* ====================================================================
|
||||
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 java.util.Arrays;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
|
||||
/**
|
||||
* @author Gary King
|
||||
*/
|
||||
public class AgileDecryptor extends Decryptor {
|
||||
|
||||
private final EncryptionInfo _info;
|
||||
private SecretKey _secretKey;
|
||||
|
||||
private static final byte[] kVerifierInputBlock;
|
||||
private static final byte[] kHashedVerifierBlock;
|
||||
private static final byte[] kCryptoKeyBlock;
|
||||
|
||||
static {
|
||||
kVerifierInputBlock =
|
||||
new byte[] { (byte)0xfe, (byte)0xa7, (byte)0xd2, (byte)0x76,
|
||||
(byte)0x3b, (byte)0x4b, (byte)0x9e, (byte)0x79 };
|
||||
kHashedVerifierBlock =
|
||||
new byte[] { (byte)0xd7, (byte)0xaa, (byte)0x0f, (byte)0x6d,
|
||||
(byte)0x30, (byte)0x61, (byte)0x34, (byte)0x4e };
|
||||
kCryptoKeyBlock =
|
||||
new byte[] { (byte)0x14, (byte)0x6e, (byte)0x0b, (byte)0xe7,
|
||||
(byte)0xab, (byte)0xac, (byte)0xd0, (byte)0xd6 };
|
||||
}
|
||||
|
||||
public boolean verifyPassword(String password) throws GeneralSecurityException {
|
||||
EncryptionVerifier verifier = _info.getVerifier();
|
||||
int algorithm = verifier.getAlgorithm();
|
||||
int mode = verifier.getCipherMode();
|
||||
|
||||
byte[] pwHash = hashPassword(_info, password);
|
||||
byte[] iv = generateIv(algorithm, verifier.getSalt(), null);
|
||||
|
||||
SecretKey skey;
|
||||
skey = new SecretKeySpec(generateKey(pwHash, kVerifierInputBlock), "AES");
|
||||
Cipher cipher = getCipher(algorithm, mode, skey, iv);
|
||||
byte[] verifierHashInput = cipher.doFinal(verifier.getVerifier());
|
||||
|
||||
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
||||
byte[] trimmed = new byte[verifier.getSalt().length];
|
||||
System.arraycopy(verifierHashInput, 0, trimmed, 0, trimmed.length);
|
||||
byte[] hashedVerifier = sha1.digest(trimmed);
|
||||
|
||||
skey = new SecretKeySpec(generateKey(pwHash, kHashedVerifierBlock), "AES");
|
||||
iv = generateIv(algorithm, verifier.getSalt(), null);
|
||||
cipher = getCipher(algorithm, mode, skey, iv);
|
||||
byte[] verifierHash = cipher.doFinal(verifier.getVerifierHash());
|
||||
trimmed = new byte[hashedVerifier.length];
|
||||
System.arraycopy(verifierHash, 0, trimmed, 0, trimmed.length);
|
||||
|
||||
if (Arrays.equals(trimmed, hashedVerifier)) {
|
||||
skey = new SecretKeySpec(generateKey(pwHash, kCryptoKeyBlock), "AES");
|
||||
iv = generateIv(algorithm, verifier.getSalt(), null);
|
||||
cipher = getCipher(algorithm, mode, skey, iv);
|
||||
byte[] inter = cipher.doFinal(verifier.getEncryptedKey());
|
||||
byte[] keyspec = new byte[_info.getHeader().getKeySize() / 8];
|
||||
System.arraycopy(inter, 0, keyspec, 0, keyspec.length);
|
||||
_secretKey = new SecretKeySpec(keyspec, "AES");
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
|
||||
DocumentInputStream dis = dir.createDocumentInputStream("EncryptedPackage");
|
||||
long size = dis.readLong();
|
||||
return new ChunkedCipherInputStream(dis, size);
|
||||
}
|
||||
|
||||
protected AgileDecryptor(EncryptionInfo info) {
|
||||
_info = info;
|
||||
}
|
||||
|
||||
private class ChunkedCipherInputStream extends InputStream {
|
||||
private int _lastIndex = 0;
|
||||
private long _pos = 0;
|
||||
private final long _size;
|
||||
private final DocumentInputStream _stream;
|
||||
private byte[] _chunk;
|
||||
private Cipher _cipher;
|
||||
|
||||
public ChunkedCipherInputStream(DocumentInputStream stream, long size)
|
||||
throws GeneralSecurityException {
|
||||
_size = size;
|
||||
_stream = stream;
|
||||
_cipher = getCipher(_info.getHeader().getAlgorithm(),
|
||||
_info.getHeader().getCipherMode(),
|
||||
_secretKey, _info.getHeader().getKeySalt());
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
byte[] b = new byte[1];
|
||||
if (read(b) == 1)
|
||||
return b[0];
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int read(byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int total = 0;
|
||||
|
||||
while (len > 0) {
|
||||
if (_chunk == null) {
|
||||
try {
|
||||
_chunk = nextChunk();
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new EncryptedDocumentException(e.getMessage());
|
||||
}
|
||||
}
|
||||
int count = (int)(4096L - (_pos & 0xfff));
|
||||
count = Math.min(available(), Math.min(count, len));
|
||||
System.arraycopy(_chunk, (int)(_pos & 0xfff), b, off, count);
|
||||
off += count;
|
||||
len -= count;
|
||||
_pos += count;
|
||||
if ((_pos & 0xfff) == 0)
|
||||
_chunk = null;
|
||||
total += count;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public long skip(long n) throws IOException {
|
||||
long start = _pos;
|
||||
long skip = Math.min(available(), n);
|
||||
|
||||
if ((((_pos + skip) ^ start) & ~0xfff) != 0)
|
||||
_chunk = null;
|
||||
_pos += skip;
|
||||
return skip;
|
||||
}
|
||||
|
||||
public int available() throws IOException { return (int)(_size - _pos); }
|
||||
public void close() throws IOException { _stream.close(); }
|
||||
public boolean markSupported() { return false; }
|
||||
|
||||
private byte[] nextChunk() throws GeneralSecurityException, IOException {
|
||||
int index = (int)(_pos >> 12);
|
||||
byte[] blockKey = new byte[4];
|
||||
LittleEndian.putInt(blockKey, index);
|
||||
byte[] iv = generateIv(_info.getHeader().getAlgorithm(),
|
||||
_info.getHeader().getKeySalt(), blockKey);
|
||||
_cipher.init(Cipher.DECRYPT_MODE, _secretKey, new IvParameterSpec(iv));
|
||||
if (_lastIndex != index)
|
||||
_stream.skip((index - _lastIndex) << 12);
|
||||
|
||||
byte[] block = new byte[Math.min(_stream.available(), 4096)];
|
||||
_stream.readFully(block);
|
||||
_lastIndex = index + 1;
|
||||
return _cipher.doFinal(block);
|
||||
}
|
||||
}
|
||||
|
||||
private Cipher getCipher(int algorithm, int mode, SecretKey key, byte[] vec)
|
||||
throws GeneralSecurityException {
|
||||
String name = null;
|
||||
String chain = null;
|
||||
|
||||
if (algorithm == EncryptionHeader.ALGORITHM_AES_128 ||
|
||||
algorithm == EncryptionHeader.ALGORITHM_AES_192 ||
|
||||
algorithm == EncryptionHeader.ALGORITHM_AES_256)
|
||||
name = "AES";
|
||||
|
||||
if (mode == EncryptionHeader.MODE_CBC)
|
||||
chain = "CBC";
|
||||
else if (mode == EncryptionHeader.MODE_CFB)
|
||||
chain = "CFB";
|
||||
|
||||
Cipher cipher = Cipher.getInstance(name + "/" + chain + "/NoPadding");
|
||||
IvParameterSpec iv = new IvParameterSpec(vec);
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, iv);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
private byte[] getBlock(int algorithm, byte[] hash) {
|
||||
byte[] result = new byte[getBlockSize(algorithm)];
|
||||
Arrays.fill(result, (byte)0x36);
|
||||
System.arraycopy(hash, 0, result, 0, Math.min(result.length, hash.length));
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] generateKey(byte[] hash, byte[] blockKey) throws NoSuchAlgorithmException {
|
||||
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
||||
sha1.update(hash);
|
||||
return getBlock(_info.getVerifier().getAlgorithm(), sha1.digest(blockKey));
|
||||
}
|
||||
|
||||
protected byte[] generateIv(int algorithm, byte[] salt, byte[] blockKey)
|
||||
throws NoSuchAlgorithmException {
|
||||
|
||||
|
||||
if (blockKey == null)
|
||||
return getBlock(algorithm, salt);
|
||||
|
||||
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
||||
sha1.update(salt);
|
||||
return getBlock(algorithm, sha1.digest(blockKey));
|
||||
}
|
||||
}
|
@ -19,150 +19,74 @@ package org.apache.poi.poifs.crypt;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
|
||||
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
|
||||
/**
|
||||
* @author Maxim Valyanskiy
|
||||
*/
|
||||
public class Decryptor {
|
||||
public abstract class Decryptor {
|
||||
public static final String DEFAULT_PASSWORD="VelvetSweatshop";
|
||||
|
||||
private final EncryptionInfo info;
|
||||
private byte[] passwordHash;
|
||||
public abstract InputStream getDataStream(DirectoryNode dir)
|
||||
throws IOException, GeneralSecurityException;
|
||||
|
||||
public Decryptor(EncryptionInfo info) {
|
||||
this.info = info;
|
||||
public abstract boolean verifyPassword(String password)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
public static Decryptor getInstance(EncryptionInfo info) {
|
||||
int major = info.getVersionMajor();
|
||||
int minor = info.getVersionMinor();
|
||||
|
||||
if (major == 4 && minor == 4)
|
||||
return new AgileDecryptor(info);
|
||||
else if (minor == 2 && (major == 3 || major == 4))
|
||||
return new EcmaDecryptor(info);
|
||||
else
|
||||
throw new EncryptedDocumentException("Unsupported version");
|
||||
}
|
||||
|
||||
private void generatePasswordHash(String password) throws NoSuchAlgorithmException {
|
||||
public InputStream getDataStream(NPOIFSFileSystem fs) throws IOException, GeneralSecurityException {
|
||||
return getDataStream(fs.getRoot());
|
||||
}
|
||||
|
||||
public InputStream getDataStream(POIFSFileSystem fs) throws IOException, GeneralSecurityException {
|
||||
return getDataStream(fs.getRoot());
|
||||
}
|
||||
|
||||
protected static int getBlockSize(int algorithm) {
|
||||
switch (algorithm) {
|
||||
case EncryptionHeader.ALGORITHM_AES_128: return 16;
|
||||
case EncryptionHeader.ALGORITHM_AES_192: return 24;
|
||||
case EncryptionHeader.ALGORITHM_AES_256: return 32;
|
||||
}
|
||||
throw new EncryptedDocumentException("Unknown block size");
|
||||
}
|
||||
|
||||
protected byte[] hashPassword(EncryptionInfo info,
|
||||
String password) throws NoSuchAlgorithmException {
|
||||
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
||||
|
||||
byte[] passwordBytes;
|
||||
byte[] bytes;
|
||||
try {
|
||||
passwordBytes = password.getBytes("UTF-16LE");
|
||||
} catch(UnsupportedEncodingException e) {
|
||||
throw new RuntimeException("Your JVM is broken - UTF16 not found!");
|
||||
bytes = password.getBytes("UTF-16LE");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new EncryptedDocumentException("UTF16 not supported");
|
||||
}
|
||||
|
||||
sha1.update(info.getVerifier().getSalt());
|
||||
byte[] hash = sha1.digest(passwordBytes);
|
||||
|
||||
byte[] hash = sha1.digest(bytes);
|
||||
byte[] iterator = new byte[4];
|
||||
for (int i = 0; i<50000; i++) {
|
||||
sha1.reset();
|
||||
|
||||
for (int i = 0; i < info.getVerifier().getSpinCount(); i++) {
|
||||
sha1.reset();
|
||||
LittleEndian.putInt(iterator, i);
|
||||
sha1.update(iterator);
|
||||
hash = sha1.digest(hash);
|
||||
}
|
||||
|
||||
passwordHash = hash;
|
||||
return 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; i<finalHash.length; i++) {
|
||||
buff[i] = (byte) (buff[i] ^ finalHash[i]);
|
||||
}
|
||||
|
||||
sha1.reset();
|
||||
byte[] x1 = sha1.digest(buff);
|
||||
|
||||
Arrays.fill(buff, (byte) 0x5c);
|
||||
for (int i=0; i<finalHash.length; i++) {
|
||||
buff[i] = (byte) (buff[i] ^ finalHash[i]);
|
||||
}
|
||||
|
||||
sha1.reset();
|
||||
byte[] x2 = sha1.digest(buff);
|
||||
|
||||
byte[] x3 = new byte[x1.length + x2.length];
|
||||
System.arraycopy(x1, 0, x3, 0, x1.length);
|
||||
System.arraycopy(x2, 0, x3, x1.length, x2.length);
|
||||
|
||||
return truncateOrPad(x3, requiredKeyLength);
|
||||
}
|
||||
|
||||
public boolean verifyPassword(String password) throws GeneralSecurityException {
|
||||
generatePasswordHash(password);
|
||||
|
||||
Cipher cipher = getCipher();
|
||||
|
||||
byte[] verifier = cipher.doFinal(info.getVerifier().getVerifier());
|
||||
|
||||
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
||||
byte[] calcVerifierHash = sha1.digest(verifier);
|
||||
|
||||
byte[] verifierHash = truncateOrPad(cipher.doFinal(info.getVerifier().getVerifierHash()), calcVerifierHash.length);
|
||||
|
||||
return Arrays.equals(calcVerifierHash, verifierHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array of the requested length,
|
||||
* truncated or zero padded as needed.
|
||||
* Behaves like Arrays.copyOf in Java 1.6
|
||||
*/
|
||||
private byte[] truncateOrPad(byte[] source, int length) {
|
||||
byte[] result = new byte[length];
|
||||
System.arraycopy(source, 0, result, 0, Math.min(length, source.length));
|
||||
if(length > source.length) {
|
||||
for(int i=source.length; i<length; i++) {
|
||||
result[i] = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Cipher getCipher() throws GeneralSecurityException {
|
||||
byte[] key = generateKey(0);
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
SecretKey skey = new SecretKeySpec(key, "AES");
|
||||
cipher.init(Cipher.DECRYPT_MODE, skey);
|
||||
|
||||
return cipher;
|
||||
}
|
||||
|
||||
public InputStream getDataStream(POIFSFileSystem fs) throws IOException, GeneralSecurityException {
|
||||
return getDataStream(fs.getRoot());
|
||||
}
|
||||
|
||||
public InputStream getDataStream(NPOIFSFileSystem fs) throws IOException, GeneralSecurityException {
|
||||
return getDataStream(fs.getRoot());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
|
||||
DocumentInputStream dis = dir.createDocumentInputStream("EncryptedPackage");
|
||||
|
||||
long size = dis.readLong();
|
||||
|
||||
return new CipherInputStream(dis, getCipher());
|
||||
}
|
||||
}
|
||||
}
|
132
src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java
Normal file
132
src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java
Normal file
@ -0,0 +1,132 @@
|
||||
/* ====================================================================
|
||||
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 java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
|
||||
/**
|
||||
* @author Maxim Valyanskiy
|
||||
* @author Gary King
|
||||
*/
|
||||
public class EcmaDecryptor extends Decryptor {
|
||||
private final EncryptionInfo info;
|
||||
private byte[] passwordHash;
|
||||
|
||||
public EcmaDecryptor(EncryptionInfo info) {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
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; i<finalHash.length; i++) {
|
||||
buff[i] = (byte) (buff[i] ^ finalHash[i]);
|
||||
}
|
||||
|
||||
sha1.reset();
|
||||
byte[] x1 = sha1.digest(buff);
|
||||
|
||||
Arrays.fill(buff, (byte) 0x5c);
|
||||
for (int i=0; i<finalHash.length; i++) {
|
||||
buff[i] = (byte) (buff[i] ^ finalHash[i]);
|
||||
}
|
||||
|
||||
sha1.reset();
|
||||
byte[] x2 = sha1.digest(buff);
|
||||
|
||||
byte[] x3 = new byte[x1.length + x2.length];
|
||||
System.arraycopy(x1, 0, x3, 0, x1.length);
|
||||
System.arraycopy(x2, 0, x3, x1.length, x2.length);
|
||||
|
||||
return truncateOrPad(x3, requiredKeyLength);
|
||||
}
|
||||
|
||||
public boolean verifyPassword(String password) throws GeneralSecurityException {
|
||||
passwordHash = hashPassword(info, password);
|
||||
|
||||
Cipher cipher = getCipher();
|
||||
|
||||
byte[] verifier = cipher.doFinal(info.getVerifier().getVerifier());
|
||||
|
||||
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
||||
byte[] calcVerifierHash = sha1.digest(verifier);
|
||||
|
||||
byte[] verifierHash = truncateOrPad(cipher.doFinal(info.getVerifier().getVerifierHash()), calcVerifierHash.length);
|
||||
|
||||
return Arrays.equals(calcVerifierHash, verifierHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array of the requested length,
|
||||
* truncated or zero padded as needed.
|
||||
* Behaves like Arrays.copyOf in Java 1.6
|
||||
*/
|
||||
private byte[] truncateOrPad(byte[] source, int length) {
|
||||
byte[] result = new byte[length];
|
||||
System.arraycopy(source, 0, result, 0, Math.min(length, source.length));
|
||||
if(length > source.length) {
|
||||
for(int i=source.length; i<length; i++) {
|
||||
result[i] = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Cipher getCipher() throws GeneralSecurityException {
|
||||
byte[] key = generateKey(0);
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
SecretKey skey = new SecretKeySpec(key, "AES");
|
||||
cipher.init(Cipher.DECRYPT_MODE, skey);
|
||||
|
||||
return cipher;
|
||||
}
|
||||
|
||||
public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
|
||||
DocumentInputStream dis = dir.createDocumentInputStream("EncryptedPackage");
|
||||
|
||||
long size = dis.readLong();
|
||||
|
||||
return new CipherInputStream(dis, getCipher());
|
||||
}
|
||||
}
|
@ -16,12 +16,19 @@
|
||||
==================================================================== */
|
||||
package org.apache.poi.poifs.crypt;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
|
||||
/**
|
||||
* @author Maxim Valyanskiy
|
||||
* @author Gary King
|
||||
*/
|
||||
public class EncryptionHeader {
|
||||
public static final int ALGORITHM_RC4 = 0x6801;
|
||||
@ -32,7 +39,11 @@ public class EncryptionHeader {
|
||||
public static final int HASH_SHA1 = 0x8004;
|
||||
|
||||
public static final int PROVIDER_RC4 = 1;
|
||||
public static final int PROVIDER_AES = 0x18;
|
||||
public static final int PROVIDER_AES = 0x18;
|
||||
|
||||
public static final int MODE_ECB = 1;
|
||||
public static final int MODE_CBC = 2;
|
||||
public static final int MODE_CFB = 3;
|
||||
|
||||
private final int flags;
|
||||
private final int sizeExtra;
|
||||
@ -40,6 +51,8 @@ public class EncryptionHeader {
|
||||
private final int hashAlgorithm;
|
||||
private final int keySize;
|
||||
private final int providerType;
|
||||
private final int cipherMode;
|
||||
private final byte[] keySalt;
|
||||
private final String cspName;
|
||||
|
||||
public EncryptionHeader(DocumentInputStream is) throws IOException {
|
||||
@ -63,8 +76,75 @@ public class EncryptionHeader {
|
||||
|
||||
builder.append(c);
|
||||
}
|
||||
|
||||
cspName = builder.toString();
|
||||
cipherMode = MODE_ECB;
|
||||
keySalt = null;
|
||||
}
|
||||
|
||||
public EncryptionHeader(String descriptor) throws IOException {
|
||||
NamedNodeMap keyData;
|
||||
try {
|
||||
ByteArrayInputStream is;
|
||||
is = new ByteArrayInputStream(descriptor.getBytes());
|
||||
keyData = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder().parse(is)
|
||||
.getElementsByTagName("keyData").item(0).getAttributes();
|
||||
} catch (Exception e) {
|
||||
throw new EncryptedDocumentException("Unable to parse keyData");
|
||||
}
|
||||
|
||||
keySize = Integer.parseInt(keyData.getNamedItem("keyBits")
|
||||
.getNodeValue());
|
||||
flags = 0;
|
||||
sizeExtra = 0;
|
||||
cspName = null;
|
||||
|
||||
int blockSize = Integer.parseInt(keyData.getNamedItem("blockSize").
|
||||
getNodeValue());
|
||||
String cipher = keyData.getNamedItem("cipherAlgorithm").getNodeValue();
|
||||
|
||||
if ("AES".equals(cipher)) {
|
||||
providerType = PROVIDER_AES;
|
||||
if (blockSize == 16)
|
||||
algorithm = ALGORITHM_AES_128;
|
||||
else if (blockSize == 24)
|
||||
algorithm = ALGORITHM_AES_192;
|
||||
else if (blockSize == 32)
|
||||
algorithm = ALGORITHM_AES_256;
|
||||
else
|
||||
throw new EncryptedDocumentException("Unsupported key length");
|
||||
} else {
|
||||
throw new EncryptedDocumentException("Unsupported cipher");
|
||||
}
|
||||
|
||||
String chaining = keyData.getNamedItem("cipherChaining").getNodeValue();
|
||||
|
||||
if ("ChainingModeCBC".equals(chaining))
|
||||
cipherMode = MODE_CBC;
|
||||
else if ("ChainingModeCFB".equals(chaining))
|
||||
cipherMode = MODE_CFB;
|
||||
else
|
||||
throw new EncryptedDocumentException("Unsupported chaining mode");
|
||||
|
||||
String hashAlg = keyData.getNamedItem("hashAlgorithm").getNodeValue();
|
||||
int hashSize = Integer.parseInt(keyData.getNamedItem("hashSize")
|
||||
.getNodeValue());
|
||||
|
||||
if ("SHA1".equals(hashAlg) && hashSize == 20)
|
||||
hashAlgorithm = HASH_SHA1;
|
||||
else
|
||||
throw new EncryptedDocumentException("Unsupported hash algorithm");
|
||||
|
||||
String salt = keyData.getNamedItem("saltValue").getNodeValue();
|
||||
int saltLength = Integer.parseInt(keyData.getNamedItem("saltSize")
|
||||
.getNodeValue());
|
||||
keySalt = Base64.decodeBase64(salt.getBytes());
|
||||
if (keySalt.length != saltLength)
|
||||
throw new EncryptedDocumentException("Invalid salt length");
|
||||
}
|
||||
|
||||
public int getCipherMode() {
|
||||
return cipherMode;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
@ -87,6 +167,10 @@ public class EncryptionHeader {
|
||||
return keySize;
|
||||
}
|
||||
|
||||
public byte[] getKeySalt() {
|
||||
return keySalt;
|
||||
}
|
||||
|
||||
public int getProviderType() {
|
||||
return providerType;
|
||||
}
|
||||
|
@ -16,15 +16,16 @@
|
||||
==================================================================== */
|
||||
package org.apache.poi.poifs.crypt;
|
||||
|
||||
import org.apache.poi.poifs.filesystem.DocumentEntry;
|
||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
|
||||
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author Maxim Valyanskiy
|
||||
* @author Gary King
|
||||
*/
|
||||
public class EncryptionInfo {
|
||||
private final int versionMajor;
|
||||
@ -37,24 +38,30 @@ public class EncryptionInfo {
|
||||
public EncryptionInfo(POIFSFileSystem fs) throws IOException {
|
||||
this(fs.getRoot());
|
||||
}
|
||||
public EncryptionInfo(NPOIFSFileSystem fs) throws IOException {
|
||||
this(fs.getRoot());
|
||||
}
|
||||
public EncryptionInfo(DirectoryNode dir) throws IOException {
|
||||
DocumentInputStream dis = dir.createDocumentInputStream("EncryptionInfo");
|
||||
|
||||
versionMajor = dis.readShort();
|
||||
versionMinor = dis.readShort();
|
||||
|
||||
encryptionFlags = dis.readInt();
|
||||
|
||||
int hSize = dis.readInt();
|
||||
|
||||
header = new EncryptionHeader(dis);
|
||||
|
||||
if (header.getAlgorithm()==EncryptionHeader.ALGORITHM_RC4) {
|
||||
verifier = new EncryptionVerifier(dis, 20);
|
||||
if (versionMajor == 4 && versionMinor == 4 && encryptionFlags == 0x40) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
byte[] xmlDescriptor = new byte[dis.available()];
|
||||
dis.read(xmlDescriptor);
|
||||
for (byte b : xmlDescriptor)
|
||||
builder.append((char)b);
|
||||
String descriptor = builder.toString();
|
||||
header = new EncryptionHeader(descriptor);
|
||||
verifier = new EncryptionVerifier(descriptor);
|
||||
} else {
|
||||
verifier = new EncryptionVerifier(dis, 32);
|
||||
int hSize = dis.readInt();
|
||||
header = new EncryptionHeader(dis);
|
||||
if (header.getAlgorithm()==EncryptionHeader.ALGORITHM_RC4) {
|
||||
verifier = new EncryptionVerifier(dis, 20);
|
||||
} else {
|
||||
verifier = new EncryptionVerifier(dis, 32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,16 +16,103 @@
|
||||
==================================================================== */
|
||||
package org.apache.poi.poifs.crypt;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
|
||||
/**
|
||||
* @author Maxim Valyanskiy
|
||||
* @author Gary King
|
||||
*/
|
||||
public class EncryptionVerifier {
|
||||
private final byte[] salt = new byte[16];
|
||||
private final byte[] verifier = new byte[16];
|
||||
private final byte[] salt;
|
||||
private final byte[] verifier;
|
||||
private final byte[] verifierHash;
|
||||
private final byte[] encryptedKey;
|
||||
private final int verifierHashSize;
|
||||
private final int spinCount;
|
||||
private final int algorithm;
|
||||
private final int cipherMode;
|
||||
|
||||
public EncryptionVerifier(String descriptor) {
|
||||
NamedNodeMap keyData = null;
|
||||
try {
|
||||
ByteArrayInputStream is;
|
||||
is = new ByteArrayInputStream(descriptor.getBytes());
|
||||
NodeList keyEncryptor = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder().parse(is)
|
||||
.getElementsByTagName("keyEncryptor").item(0).getChildNodes();
|
||||
for (int i = 0; i < keyEncryptor.getLength(); i++) {
|
||||
Node node = keyEncryptor.item(i);
|
||||
if (node.getNodeName().equals("p:encryptedKey")) {
|
||||
keyData = node.getAttributes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (keyData == null)
|
||||
throw new EncryptedDocumentException("");
|
||||
} catch (Exception e) {
|
||||
throw new EncryptedDocumentException("Unable to parse keyEncryptor");
|
||||
}
|
||||
|
||||
spinCount = Integer.parseInt(keyData.getNamedItem("spinCount")
|
||||
.getNodeValue());
|
||||
verifier = Base64.decodeBase64(keyData
|
||||
.getNamedItem("encryptedVerifierHashInput")
|
||||
.getNodeValue().getBytes());
|
||||
salt = Base64.decodeBase64(keyData.getNamedItem("saltValue")
|
||||
.getNodeValue().getBytes());
|
||||
|
||||
encryptedKey = Base64.decodeBase64(keyData
|
||||
.getNamedItem("encryptedKeyValue")
|
||||
.getNodeValue().getBytes());
|
||||
|
||||
int saltSize = Integer.parseInt(keyData.getNamedItem("saltSize")
|
||||
.getNodeValue());
|
||||
if (saltSize != salt.length)
|
||||
throw new EncryptedDocumentException("Invalid salt size");
|
||||
|
||||
verifierHash = Base64.decodeBase64(keyData
|
||||
.getNamedItem("encryptedVerifierHashValue")
|
||||
.getNodeValue().getBytes());
|
||||
|
||||
int blockSize = Integer.parseInt(keyData.getNamedItem("blockSize")
|
||||
.getNodeValue());
|
||||
|
||||
String alg = keyData.getNamedItem("cipherAlgorithm").getNodeValue();
|
||||
|
||||
if ("AES".equals(alg)) {
|
||||
if (blockSize == 16)
|
||||
algorithm = EncryptionHeader.ALGORITHM_AES_128;
|
||||
else if (blockSize == 24)
|
||||
algorithm = EncryptionHeader.ALGORITHM_AES_192;
|
||||
else if (blockSize == 32)
|
||||
algorithm = EncryptionHeader.ALGORITHM_AES_256;
|
||||
else
|
||||
throw new EncryptedDocumentException("Unsupported block size");
|
||||
} else {
|
||||
throw new EncryptedDocumentException("Unsupported cipher");
|
||||
}
|
||||
|
||||
String chain = keyData.getNamedItem("cipherChaining").getNodeValue();
|
||||
if ("ChainingModeCBC".equals(chain))
|
||||
cipherMode = EncryptionHeader.MODE_CBC;
|
||||
else if ("ChainingModeCFB".equals(chain))
|
||||
cipherMode = EncryptionHeader.MODE_CFB;
|
||||
else
|
||||
throw new EncryptedDocumentException("Unsupported chaining mode");
|
||||
|
||||
verifierHashSize = Integer.parseInt(keyData.getNamedItem("hashSize")
|
||||
.getNodeValue());
|
||||
}
|
||||
|
||||
public EncryptionVerifier(DocumentInputStream is, int encryptedLength) {
|
||||
int saltSize = is.readInt();
|
||||
@ -34,13 +121,20 @@ public class EncryptionVerifier {
|
||||
throw new RuntimeException("Salt size != 16 !?");
|
||||
}
|
||||
|
||||
salt = new byte[16];
|
||||
is.readFully(salt);
|
||||
verifier = new byte[16];
|
||||
is.readFully(verifier);
|
||||
|
||||
verifierHashSize = is.readInt();
|
||||
|
||||
verifierHash = new byte[encryptedLength];
|
||||
is.readFully(verifierHash);
|
||||
|
||||
spinCount = 50000;
|
||||
algorithm = EncryptionHeader.ALGORITHM_AES_128;
|
||||
cipherMode = EncryptionHeader.MODE_ECB;
|
||||
encryptedKey = null;
|
||||
}
|
||||
|
||||
public byte[] getSalt() {
|
||||
@ -54,4 +148,20 @@ public class EncryptionVerifier {
|
||||
public byte[] getVerifierHash() {
|
||||
return verifierHash;
|
||||
}
|
||||
|
||||
public int getSpinCount() {
|
||||
return spinCount;
|
||||
}
|
||||
|
||||
public int getCipherMode() {
|
||||
return cipherMode;
|
||||
}
|
||||
|
||||
public int getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public byte[] getEncryptedKey() {
|
||||
return encryptedKey;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
/* ====================================================================
|
||||
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.Test;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
|
||||
/**
|
||||
* Tests for org.apache.poi.poifs.crypt
|
||||
*
|
||||
* @author Gary King
|
||||
*/
|
||||
public final class AllPOIFSCryptoTests {
|
||||
|
||||
public static Test suite() {
|
||||
TestSuite result = new TestSuite(AllPOIFSCryptoTests.class.getName());
|
||||
result.addTestSuite(TestDecryptor.class);
|
||||
result.addTestSuite(TestEncryptionInfo.class);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -27,14 +27,15 @@ import java.util.zip.ZipInputStream;
|
||||
|
||||
/**
|
||||
* @author Maxim Valyanskiy
|
||||
* @author Gary King
|
||||
*/
|
||||
public class DecryptorTest extends TestCase {
|
||||
public class TestDecryptor extends TestCase {
|
||||
public void testPasswordVerification() throws IOException, GeneralSecurityException {
|
||||
POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protect.xlsx"));
|
||||
|
||||
EncryptionInfo info = new EncryptionInfo(fs);
|
||||
|
||||
Decryptor d = new Decryptor(info);
|
||||
Decryptor d = Decryptor.getInstance(info);
|
||||
|
||||
assertTrue(d.verifyPassword(Decryptor.DEFAULT_PASSWORD));
|
||||
}
|
||||
@ -44,13 +45,27 @@ public class DecryptorTest extends TestCase {
|
||||
|
||||
EncryptionInfo info = new EncryptionInfo(fs);
|
||||
|
||||
Decryptor d = new Decryptor(info);
|
||||
Decryptor d = Decryptor.getInstance(info);
|
||||
|
||||
d.verifyPassword(Decryptor.DEFAULT_PASSWORD);
|
||||
|
||||
zipOk(fs, d);
|
||||
}
|
||||
|
||||
public void testAgile() throws IOException, GeneralSecurityException {
|
||||
POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protected_agile.docx"));
|
||||
|
||||
EncryptionInfo info = new EncryptionInfo(fs);
|
||||
|
||||
assertTrue(info.getVersionMajor() == 4 && info.getVersionMinor() == 4);
|
||||
|
||||
Decryptor d = Decryptor.getInstance(info);
|
||||
|
||||
assertTrue(d.verifyPassword(Decryptor.DEFAULT_PASSWORD));
|
||||
|
||||
zipOk(fs, d);
|
||||
}
|
||||
|
||||
private void zipOk(POIFSFileSystem fs, Decryptor d) throws IOException, GeneralSecurityException {
|
||||
ZipInputStream zin = new ZipInputStream(d.getDataStream(fs));
|
||||
|
@ -25,7 +25,7 @@ import java.io.IOException;
|
||||
/**
|
||||
* @author Maxim Valyanskiy
|
||||
*/
|
||||
public class EncryptionInfoTest extends TestCase {
|
||||
public class TestEncryptionInfo extends TestCase {
|
||||
public void testEncryptionInfo() throws IOException {
|
||||
POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protect.xlsx"));
|
||||
|
BIN
test-data/poifs/protected_agile.docx
Normal file
BIN
test-data/poifs/protected_agile.docx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user