Preparations for hssf_cryptoapi:

- Add cloneable
- Change existing hslf cryptoapi to streaming


git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hssf_cryptoapi@1755127 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2016-08-03 23:54:01 +00:00
parent ab36704d3e
commit c4ac2e7758
38 changed files with 1417 additions and 856 deletions

View File

@ -32,6 +32,7 @@ import org.apache.poi.hpsf.PropertySet;
import org.apache.poi.hpsf.PropertySetFactory;
import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
@ -59,6 +60,8 @@ public abstract class POIDocument implements Closeable {
/* Have the property streams been read yet? (Only done on-demand) */
private boolean initialized = false;
private static final String[] encryptedStreamNames = { "EncryptedSummary" };
/**
* Constructs a POIDocument with the given directory node.
@ -195,13 +198,18 @@ public abstract class POIDocument implements Closeable {
try {
if (encryptionInfo != null) {
step = "getting encrypted";
InputStream is = encryptionInfo.getDecryptor().getDataStream(directory);
try {
encPoifs = new NPOIFSFileSystem(is);
dirNode = encPoifs.getRoot();
} finally {
is.close();
String encryptedStream = null;
for (String s : encryptedStreamNames) {
if (dirNode.hasEntry(s)) {
encryptedStream = s;
}
}
if (encryptedStream == null) {
throw new EncryptedDocumentException("can't find matching encrypted property stream");
}
CryptoAPIDecryptor dec = (CryptoAPIDecryptor)encryptionInfo.getDecryptor();
encPoifs = dec.getSummaryEntries(dirNode, encryptedStream);
dirNode = encPoifs.getRoot();
}
//directory can be null when creating new documents

View File

@ -18,13 +18,14 @@
package org.apache.poi.hssf.record;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import org.apache.poi.hssf.dev.BiffViewer;
import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream;
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianInputStream;
@ -91,6 +92,10 @@ public final class RecordInputStream implements LittleEndianInput {
* index within the data section of the current BIFF record
*/
private int _currentDataOffset;
/**
* index within the data section when mark() was called
*/
private int _markedDataOffset;
private static final class SimpleHeaderInput implements BiffHeaderInput {
@ -123,8 +128,8 @@ public final class RecordInputStream implements LittleEndianInput {
_bhi = new SimpleHeaderInput(in);
} else {
Biff8DecryptingStream bds = new Biff8DecryptingStream(in, initialOffset, key);
_dataInput = bds;
_bhi = bds;
_dataInput = bds;
}
_nextSid = readNextSid();
}
@ -491,4 +496,31 @@ public final class RecordInputStream implements LittleEndianInput {
public int getNextSid() {
return _nextSid;
}
/**
* Mark the stream position - experimental function
*
* @param readlimit the read ahead limit
*
* @see InputStream#mark(int)
*/
@Internal
public void mark(int readlimit) {
((InputStream)_dataInput).mark(readlimit);
_markedDataOffset = _currentDataOffset;
}
/**
* Resets the stream position to the previously marked position.
* Experimental function - this only works, when nextRecord() wasn't called in the meantime.
*
* @throws IOException if marking is not supported
*
* @see InputStream#reset()
*/
@Internal
public void reset() throws IOException {
((InputStream)_dataInput).reset();
_currentDataOffset = _markedDataOffset;
}
}

View File

@ -16,6 +16,7 @@
==================================================================== */
package org.apache.poi.poifs.crypt;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
@ -29,54 +30,81 @@ import org.apache.poi.util.LittleEndianInputStream;
@Internal
public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
private final int chunkSize;
private final int chunkMask;
private final int chunkBits;
private final int _chunkSize;
private final int _chunkBits;
private int _lastIndex = 0;
private long _pos = 0;
private long _size;
private byte[] _chunk;
private Cipher _cipher;
private final long _size;
private final byte[] _chunk;
private final Cipher _cipher;
private int _lastIndex;
private long _pos;
private boolean _chunkIsValid = false;
public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize)
throws GeneralSecurityException {
throws GeneralSecurityException {
this(stream, size, chunkSize, 0);
}
public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize, int initialPos)
throws GeneralSecurityException {
super((InputStream)stream);
_size = size;
this.chunkSize = chunkSize;
chunkMask = chunkSize-1;
chunkBits = Integer.bitCount(chunkMask);
_pos = initialPos;
this._chunkSize = chunkSize;
if (chunkSize == -1) {
_chunk = new byte[4096];
} else {
_chunk = new byte[chunkSize];
}
_chunkBits = Integer.bitCount(_chunk.length-1);
_lastIndex = (int)(_pos >> _chunkBits);
_cipher = initCipherForBlock(null, _lastIndex);
}
public final Cipher initCipherForBlock(int block) throws IOException, GeneralSecurityException {
if (_chunkSize != -1) {
throw new GeneralSecurityException("the cipher block can only be set for streaming encryption, e.g. CryptoAPI...");
}
_cipher = initCipherForBlock(null, 0);
_chunkIsValid = false;
return initCipherForBlock(_cipher, block);
}
protected abstract Cipher initCipherForBlock(Cipher existing, int block)
throws GeneralSecurityException;
@Override
public int read() throws IOException {
byte[] b = new byte[1];
if (read(b) == 1)
if (read(b) == 1) {
return b[0];
}
return -1;
}
// do not implement! -> recursion
// public int read(byte[] b) throws IOException;
@Override
public int read(byte[] b, int off, int len) throws IOException {
int total = 0;
if (available() <= 0) return -1;
if (available() <= 0) {
return -1;
}
final int chunkMask = getChunkMask();
while (len > 0) {
if (_chunk == null) {
if (!_chunkIsValid) {
try {
_chunk = nextChunk();
nextChunk();
_chunkIsValid = true;
} catch (GeneralSecurityException e) {
throw new EncryptedDocumentException(e.getMessage(), e);
}
}
int count = (int)(chunkSize - (_pos & chunkMask));
int count = (int)(_chunk.length - (_pos & chunkMask));
int avail = available();
if (avail == 0) {
return total;
@ -86,8 +114,9 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
off += count;
len -= count;
_pos += count;
if ((_pos & chunkMask) == 0)
_chunk = null;
if ((_pos & chunkMask) == 0) {
_chunkIsValid = false;
}
total += count;
}
@ -95,18 +124,28 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
}
@Override
public long skip(long n) throws IOException {
public long skip(final long n) throws IOException {
long start = _pos;
long skip = Math.min(available(), n);
long skip = Math.min(remainingBytes(), n);
if ((((_pos + skip) ^ start) & ~chunkMask) != 0)
_chunk = null;
if ((((_pos + skip) ^ start) & ~getChunkMask()) != 0) {
_chunkIsValid = false;
}
_pos += skip;
return skip;
}
@Override
public int available() {
return remainingBytes();
}
/**
* Helper method for forbidden available call - we know the size beforehand, so it's ok ...
*
* @return the remaining byte until EOF
*/
private int remainingBytes() {
return (int)(_size - _pos);
}
@ -125,17 +164,37 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
throw new UnsupportedOperationException();
}
private byte[] nextChunk() throws GeneralSecurityException, IOException {
int index = (int)(_pos >> chunkBits);
initCipherForBlock(_cipher, index);
private int getChunkMask() {
return _chunk.length-1;
}
private void nextChunk() throws GeneralSecurityException, IOException {
if (_chunkSize != -1) {
int index = (int)(_pos >> _chunkBits);
initCipherForBlock(_cipher, index);
if (_lastIndex != index) {
super.skip((index - _lastIndex) << chunkBits);
if (_lastIndex != index) {
super.skip((index - _lastIndex) << _chunkBits);
}
_lastIndex = index + 1;
}
byte[] block = new byte[Math.min(super.available(), chunkSize)];
super.read(block, 0, block.length);
_lastIndex = index + 1;
return _cipher.doFinal(block);
final int todo = (int)Math.min(_size, _chunk.length);
int readBytes = 0, totalBytes = 0;
do {
readBytes = super.read(_chunk, totalBytes, todo-totalBytes);
totalBytes += Math.max(0, readBytes);
} while (readBytes != -1 && totalBytes < todo);
if (readBytes == -1 && _pos+totalBytes < _size) {
throw new EOFException("buffer underrun");
}
if (_chunkSize == -1) {
_cipher.update(_chunk, 0, totalBytes, _chunk);
} else {
_cipher.doFinal(_chunk, 0, totalBytes, _chunk);
}
}
}

View File

@ -32,6 +32,7 @@ import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.POIFSWriterEvent;
import org.apache.poi.poifs.filesystem.POIFSWriterListener;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts;
@ -41,137 +42,177 @@ import org.apache.poi.util.TempFile;
@Internal
public abstract class ChunkedCipherOutputStream extends FilterOutputStream {
private static final POILogger logger = POILogFactory.getLogger(ChunkedCipherOutputStream.class);
protected final int chunkSize;
protected final int chunkMask;
protected final int chunkBits;
private static final POILogger LOG = POILogFactory.getLogger(ChunkedCipherOutputStream.class);
private static final int STREAMING = -1;
protected final int _chunkSize;
protected final int _chunkBits;
private final byte[] _chunk;
private final File fileOut;
private final DirectoryNode dir;
private final File _fileOut;
private final DirectoryNode _dir;
private long _pos = 0;
private Cipher _cipher;
public ChunkedCipherOutputStream(DirectoryNode dir, int chunkSize) throws IOException, GeneralSecurityException {
super(null);
this.chunkSize = chunkSize;
chunkMask = chunkSize-1;
chunkBits = Integer.bitCount(chunkMask);
_chunk = new byte[chunkSize];
fileOut = TempFile.createTempFile("encrypted_package", "crypt");
fileOut.deleteOnExit();
this.out = new FileOutputStream(fileOut);
this.dir = dir;
this._chunkSize = chunkSize;
int cs = chunkSize == STREAMING ? 4096 : chunkSize;
_chunk = new byte[cs];
_chunkBits = Integer.bitCount(cs-1);
_fileOut = TempFile.createTempFile("encrypted_package", "crypt");
_fileOut.deleteOnExit();
this.out = new FileOutputStream(_fileOut);
this._dir = dir;
_cipher = initCipherForBlock(null, 0, false);
}
public ChunkedCipherOutputStream(OutputStream stream, int chunkSize) throws IOException, GeneralSecurityException {
super(stream);
this._chunkSize = chunkSize;
int cs = chunkSize == STREAMING ? 4096 : chunkSize;
_chunk = new byte[cs];
_chunkBits = Integer.bitCount(cs-1);
_fileOut = null;
_dir = null;
_cipher = initCipherForBlock(null, 0, false);
}
public final Cipher initCipherForBlock(int block, boolean lastChunk) throws IOException, GeneralSecurityException {
return initCipherForBlock(_cipher, block, lastChunk);
}
protected abstract Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk)
throws GeneralSecurityException;
@SuppressWarnings("hiding")
throws IOException, GeneralSecurityException;
protected abstract void calculateChecksum(File fileOut, int oleStreamSize)
throws GeneralSecurityException, IOException;
@SuppressWarnings("hiding")
protected abstract void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
throws IOException, GeneralSecurityException;
@Override
public void write(int b) throws IOException {
write(new byte[]{(byte)b});
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len)
throws IOException {
if (len == 0) return;
if (len == 0) {
return;
}
if (len < 0 || b.length < off+len) {
throw new IOException("not enough bytes in your input buffer");
}
final int chunkMask = getChunkMask();
while (len > 0) {
int posInChunk = (int)(_pos & chunkMask);
int nextLen = Math.min(chunkSize-posInChunk, len);
int nextLen = Math.min(_chunk.length-posInChunk, len);
System.arraycopy(b, off, _chunk, posInChunk, nextLen);
_pos += nextLen;
off += nextLen;
len -= nextLen;
if ((_pos & chunkMask) == 0) {
try {
writeChunk();
} catch (GeneralSecurityException e) {
throw new IOException(e);
}
writeChunk(len > 0);
}
}
}
protected void writeChunk() throws IOException, GeneralSecurityException {
int posInChunk = (int)(_pos & chunkMask);
private int getChunkMask() {
return _chunk.length-1;
}
protected void writeChunk(boolean continued) throws IOException {
if (_pos == 0) {
return;
}
int posInChunk = (int)(_pos & getChunkMask());
// normally posInChunk is 0, i.e. on the next chunk (-> index-1)
// but if called on close(), posInChunk is somewhere within the chunk data
int index = (int)(_pos >> chunkBits);
int index = (int)(_pos >> _chunkBits);
boolean lastChunk;
if (posInChunk==0) {
index--;
posInChunk = chunkSize;
posInChunk = _chunk.length;
lastChunk = false;
} else {
// pad the last chunk
lastChunk = true;
}
_cipher = initCipherForBlock(_cipher, index, lastChunk);
int ciLen;
try {
if (_chunkSize == STREAMING) {
if (continued) {
ciLen = _cipher.update(_chunk, 0, posInChunk, _chunk);
} else {
ciLen = _cipher.doFinal(_chunk, 0, posInChunk, _chunk);
}
// reset stream (not only) in case we were interrupted by plain stream parts
_pos = 0;
} else {
_cipher = initCipherForBlock(_cipher, index, lastChunk);
ciLen = _cipher.doFinal(_chunk, 0, posInChunk, _chunk);
}
} catch (GeneralSecurityException e) {
throw new IOException("can't re-/initialize cipher", e);
}
int ciLen = _cipher.doFinal(_chunk, 0, posInChunk, _chunk);
out.write(_chunk, 0, ciLen);
}
@Override
public void close() throws IOException {
try {
writeChunk();
writeChunk(false);
super.close();
int oleStreamSize = (int)(fileOut.length()+LittleEndianConsts.LONG_SIZE);
calculateChecksum(fileOut, (int)_pos);
dir.createDocument(DEFAULT_POIFS_ENTRY, oleStreamSize, new EncryptedPackageWriter());
createEncryptionInfoEntry(dir, fileOut);
if (_fileOut != null) {
int oleStreamSize = (int)(_fileOut.length()+LittleEndianConsts.LONG_SIZE);
calculateChecksum(_fileOut, (int)_pos);
_dir.createDocument(DEFAULT_POIFS_ENTRY, oleStreamSize, new EncryptedPackageWriter());
createEncryptionInfoEntry(_dir, _fileOut);
}
} catch (GeneralSecurityException e) {
throw new IOException(e);
}
}
private class EncryptedPackageWriter implements POIFSWriterListener {
@Override
public void processPOIFSWriterEvent(POIFSWriterEvent event) {
try {
OutputStream os = event.getStream();
byte buf[] = new byte[chunkSize];
// StreamSize (8 bytes): An unsigned integer that specifies the number of bytes used by data
// encrypted within the EncryptedData field, not including the size of the StreamSize field.
// Note that the actual size of the \EncryptedPackage stream (1) can be larger than this
// value, depending on the block size of the chosen encryption algorithm
LittleEndian.putLong(buf, 0, _pos);
os.write(buf, 0, LittleEndian.LONG_SIZE);
FileInputStream fis = new FileInputStream(fileOut);
int readBytes;
while ((readBytes = fis.read(buf)) != -1) {
os.write(buf, 0, readBytes);
}
// StreamSize (8 bytes): An unsigned integer that specifies the number of bytes used by data
// encrypted within the EncryptedData field, not including the size of the StreamSize field.
// Note that the actual size of the \EncryptedPackage stream (1) can be larger than this
// value, depending on the block size of the chosen encryption algorithm
byte buf[] = new byte[LittleEndianConsts.LONG_SIZE];
LittleEndian.putLong(buf, 0, _pos);
os.write(buf);
FileInputStream fis = new FileInputStream(_fileOut);
IOUtils.copy(fis, os);
fis.close();
os.close();
if (!fileOut.delete()) {
logger.log(POILogger.ERROR, "Can't delete temporary encryption file: "+fileOut);
if (!_fileOut.delete()) {
LOG.log(POILogger.ERROR, "Can't delete temporary encryption file: "+_fileOut);
}
} catch (IOException e) {
throw new EncryptedDocumentException(e);

View File

@ -20,24 +20,26 @@ import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.util.LittleEndianInput;
public abstract class Decryptor {
public abstract class Decryptor implements Cloneable {
public static final String DEFAULT_PASSWORD="VelvetSweatshop";
public static final String DEFAULT_POIFS_ENTRY="EncryptedPackage";
protected final EncryptionInfoBuilder builder;
protected EncryptionInfo encryptionInfo;
private SecretKey secretKey;
private byte[] verifier, integrityHmacKey, integrityHmacValue;
protected Decryptor(EncryptionInfoBuilder builder) {
this.builder = builder;
protected Decryptor() {
}
/**
@ -54,6 +56,45 @@ public abstract class Decryptor {
public abstract InputStream getDataStream(DirectoryNode dir)
throws IOException, GeneralSecurityException;
/**
* Wraps a stream for decryption<p>
*
* As we are handling streams and don't know the total length beforehand,
* it's the callers duty to care for the length of the entries.
*
* @param stream the stream to be wrapped
* @param initialPos initial/current byte position within the stream
* @return decrypted stream
*/
public InputStream getDataStream(LittleEndianInput stream, int size, int initialPos)
throws IOException, GeneralSecurityException {
throw new RuntimeException("this decryptor doesn't support reading from a stream");
}
/**
* Sets the chunk size of the data stream.
* Needs to be set before the data stream is requested.
* When not set, the implementation uses method specific default values
*
* @param chunkSize the chunk size, i.e. the block size with the same encryption key
*/
public void setChunkSize(int chunkSize) {
throw new RuntimeException("this decryptor doesn't support changing the chunk size");
}
/**
* Initializes a cipher object for a given block index for encryption
*
* @param cipher may be null, otherwise the given instance is reset to the new block index
* @param block the block index, e.g. the persist/slide id (hslf)
* @return a new cipher object, if cipher was null, otherwise the reinitialized cipher
* @throws GeneralSecurityException
*/
public Cipher initCipherForBlock(Cipher cipher, int block)
throws GeneralSecurityException {
throw new RuntimeException("this decryptor doesn't support initCipherForBlock");
}
public abstract boolean verifyPassword(String password)
throws GeneralSecurityException;
@ -85,9 +126,11 @@ public abstract class Decryptor {
public InputStream getDataStream(NPOIFSFileSystem fs) throws IOException, GeneralSecurityException {
return getDataStream(fs.getRoot());
}
public InputStream getDataStream(OPOIFSFileSystem fs) throws IOException, GeneralSecurityException {
return getDataStream(fs.getRoot());
}
public InputStream getDataStream(POIFSFileSystem fs) throws IOException, GeneralSecurityException {
return getDataStream(fs.getRoot());
}
@ -126,10 +169,29 @@ public abstract class Decryptor {
}
protected int getBlockSizeInBytes() {
return builder.getHeader().getBlockSize();
return encryptionInfo.getHeader().getBlockSize();
}
protected int getKeySizeInBytes() {
return builder.getHeader().getKeySize()/8;
return encryptionInfo.getHeader().getKeySize()/8;
}
public EncryptionInfo getEncryptionInfo() {
return encryptionInfo;
}
public void setEncryptionInfo(EncryptionInfo encryptionInfo) {
this.encryptionInfo = encryptionInfo;
}
@Override
public Decryptor clone() throws CloneNotSupportedException {
Decryptor other = (Decryptor)super.clone();
other.integrityHmacKey = integrityHmacKey.clone();
other.integrityHmacValue = integrityHmacValue.clone();
other.verifier = verifier.clone();
other.secretKey = new SecretKeySpec(secretKey.getEncoded(), secretKey.getAlgorithm());
// encryptionInfo is set from outside
return other;
}
}

View File

@ -16,12 +16,11 @@
==================================================================== */
package org.apache.poi.poifs.crypt;
/**
* Reads and processes OOXML Encryption Headers
* The constants are largely based on ZIP constants.
*/
public abstract class EncryptionHeader {
public abstract class EncryptionHeader implements Cloneable {
public static final int ALGORITHM_RC4 = CipherAlgorithm.rc4.ecmaId;
public static final int ALGORITHM_AES_128 = CipherAlgorithm.aes128.ecmaId;
public static final int ALGORITHM_AES_192 = CipherAlgorithm.aes192.ecmaId;
@ -132,4 +131,11 @@ public abstract class EncryptionHeader {
protected void setCspName(String cspName) {
this.cspName = cspName;
}
@Override
public EncryptionHeader clone() throws CloneNotSupportedException {
EncryptionHeader other = (EncryptionHeader)super.clone();
other.keySalt = (keySalt == null) ? null : keySalt.clone();
return other;
}
}

View File

@ -34,15 +34,15 @@ import org.apache.poi.util.LittleEndianInput;
/**
*/
public class EncryptionInfo {
public class EncryptionInfo implements Cloneable {
private final int versionMajor;
private final int versionMinor;
private final int encryptionFlags;
private final EncryptionHeader header;
private final EncryptionVerifier verifier;
private final Decryptor decryptor;
private final Encryptor encryptor;
private EncryptionHeader header;
private EncryptionVerifier verifier;
private Decryptor decryptor;
private Encryptor encryptor;
/**
* A flag that specifies whether CryptoAPI RC4 or ECMA-376 encryption
@ -96,11 +96,10 @@ public class EncryptionInfo {
public EncryptionInfo(LittleEndianInput dis, boolean isCryptoAPI) throws IOException {
final EncryptionMode encryptionMode;
versionMajor = dis.readShort();
versionMinor = dis.readShort();
versionMajor = dis.readUShort();
versionMinor = dis.readUShort();
if (!isCryptoAPI
&& versionMajor == binaryRC4.versionMajor
if ( versionMajor == binaryRC4.versionMajor
&& versionMinor == binaryRC4.versionMinor) {
encryptionMode = binaryRC4;
encryptionFlags = -1;
@ -138,10 +137,6 @@ public class EncryptionInfo {
}
eib.initialize(this, dis);
header = eib.getHeader();
verifier = eib.getVerifier();
decryptor = eib.getDecryptor();
encryptor = eib.getEncryptor();
}
/**
@ -187,11 +182,6 @@ public class EncryptionInfo {
}
eib.initialize(this, cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
header = eib.getHeader();
verifier = eib.getVerifier();
decryptor = eib.getDecryptor();
encryptor = eib.getEncryptor();
}
protected static EncryptionInfoBuilder getBuilder(EncryptionMode encryptionMode)
@ -229,4 +219,32 @@ public class EncryptionInfo {
public Encryptor getEncryptor() {
return encryptor;
}
}
public void setHeader(EncryptionHeader header) {
this.header = header;
}
public void setVerifier(EncryptionVerifier verifier) {
this.verifier = verifier;
}
public void setDecryptor(Decryptor decryptor) {
this.decryptor = decryptor;
}
public void setEncryptor(Encryptor encryptor) {
this.encryptor = encryptor;
}
@Override
public EncryptionInfo clone() throws CloneNotSupportedException {
EncryptionInfo other = (EncryptionInfo)super.clone();
other.header = header.clone();
other.verifier = verifier.clone();
other.decryptor = decryptor.clone();
other.decryptor.setEncryptionInfo(other);
other.encryptor = encryptor.clone();
other.encryptor.setEncryptionInfo(other);
return other;
}
}

View File

@ -30,24 +30,4 @@ public interface EncryptionInfoBuilder {
* initialize the builder from scratch
*/
void initialize(EncryptionInfo ei, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode);
/**
* @return the header data
*/
EncryptionHeader getHeader();
/**
* @return the verifier data
*/
EncryptionVerifier getVerifier();
/**
* @return the decryptor
*/
Decryptor getDecryptor();
/**
* @return the encryptor
*/
Encryptor getEncryptor();
}

View File

@ -16,11 +16,10 @@
==================================================================== */
package org.apache.poi.poifs.crypt;
/**
* Used when checking if a key is valid for a document
*/
public abstract class EncryptionVerifier {
public abstract class EncryptionVerifier implements Cloneable {
private byte[] salt;
private byte[] encryptedVerifier;
private byte[] encryptedVerifierHash;
@ -105,5 +104,13 @@ public abstract class EncryptionVerifier {
this.hashAlgorithm = hashAlgorithm;
}
@Override
public EncryptionVerifier clone() throws CloneNotSupportedException {
EncryptionVerifier other = (EncryptionVerifier)super.clone();
other.salt = (salt == null) ? null : salt.clone();
other.encryptedVerifier = (encryptedVerifier == null) ? null : encryptedVerifier.clone();
other.encryptedVerifierHash = (encryptedVerifierHash == null) ? null : encryptedVerifierHash.clone();
other.encryptedKey = (encryptedKey == null) ? null : encryptedKey.clone();
return other;
}
}

View File

@ -21,14 +21,16 @@ import java.io.OutputStream;
import java.security.GeneralSecurityException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
public abstract class Encryptor {
public abstract class Encryptor implements Cloneable {
protected static final String DEFAULT_POIFS_ENTRY = Decryptor.DEFAULT_POIFS_ENTRY;
private EncryptionInfo encryptionInfo;
private SecretKey secretKey;
/**
@ -66,4 +68,20 @@ public abstract class Encryptor {
protected void setSecretKey(SecretKey secretKey) {
this.secretKey = secretKey;
}
public EncryptionInfo getEncryptionInfo() {
return encryptionInfo;
}
public void setEncryptionInfo(EncryptionInfo encryptionInfo) {
this.encryptionInfo = encryptionInfo;
}
@Override
public Encryptor clone() throws CloneNotSupportedException {
Encryptor other = (Encryptor)super.clone();
other.secretKey = new SecretKeySpec(secretKey.getEncoded(), secretKey.getAlgorithm());
// encryptionInfo is set from outside
return other;
}
}

View File

@ -29,36 +29,45 @@ import javax.crypto.spec.SecretKeySpec;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.poifs.crypt.*;
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.StringUtil;
public class BinaryRC4Decryptor extends Decryptor {
public class BinaryRC4Decryptor extends Decryptor implements Cloneable {
private long _length = -1L;
private int _chunkSize = 512;
private class BinaryRC4CipherInputStream extends ChunkedCipherInputStream {
@Override
protected Cipher initCipherForBlock(Cipher existing, int block)
throws GeneralSecurityException {
return BinaryRC4Decryptor.initCipherForBlock(existing, block, builder, getSecretKey(), Cipher.DECRYPT_MODE);
return BinaryRC4Decryptor.this.initCipherForBlock(existing, block);
}
public BinaryRC4CipherInputStream(DocumentInputStream stream, long size)
throws GeneralSecurityException {
super(stream, size, 512);
super(stream, size, _chunkSize);
}
public BinaryRC4CipherInputStream(LittleEndianInput stream)
throws GeneralSecurityException {
super(stream, Integer.MAX_VALUE, _chunkSize);
}
}
protected BinaryRC4Decryptor(BinaryRC4EncryptionInfoBuilder builder) {
super(builder);
protected BinaryRC4Decryptor() {
}
@Override
public boolean verifyPassword(String password) {
EncryptionVerifier ver = builder.getVerifier();
EncryptionVerifier ver = getEncryptionInfo().getVerifier();
SecretKey skey = generateSecretKey(password, ver);
try {
Cipher cipher = initCipherForBlock(null, 0, builder, skey, Cipher.DECRYPT_MODE);
Cipher cipher = initCipherForBlock(null, 0, getEncryptionInfo(), skey, Cipher.DECRYPT_MODE);
byte encryptedVerifier[] = ver.getEncryptedVerifier();
byte verifier[] = new byte[encryptedVerifier.length];
cipher.update(encryptedVerifier, 0, encryptedVerifier.length, verifier);
@ -78,17 +87,23 @@ public class BinaryRC4Decryptor extends Decryptor {
return false;
}
protected static Cipher initCipherForBlock(Cipher cipher, int block,
EncryptionInfoBuilder builder, SecretKey skey, int encryptMode)
@Override
public Cipher initCipherForBlock(Cipher cipher, int block)
throws GeneralSecurityException {
EncryptionVerifier ver = builder.getVerifier();
return initCipherForBlock(cipher, block, getEncryptionInfo(), getSecretKey(), Cipher.DECRYPT_MODE);
}
protected static Cipher initCipherForBlock(Cipher cipher, int block,
EncryptionInfo encryptionInfo, SecretKey skey, int encryptMode)
throws GeneralSecurityException {
EncryptionVerifier ver = encryptionInfo.getVerifier();
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
byte blockKey[] = new byte[4];
LittleEndian.putUInt(blockKey, 0, block);
byte encKey[] = CryptoFunctions.generateKey(skey.getEncoded(), hashAlgo, blockKey, 16);
SecretKey key = new SecretKeySpec(encKey, skey.getAlgorithm());
if (cipher == null) {
EncryptionHeader em = builder.getHeader();
EncryptionHeader em = encryptionInfo.getHeader();
cipher = CryptoFunctions.getCipher(key, em.getCipherAlgorithm(), null, null, encryptMode);
} else {
cipher.init(encryptMode, key);
@ -96,10 +111,10 @@ public class BinaryRC4Decryptor extends Decryptor {
return cipher;
}
protected static SecretKey generateSecretKey(String password,
EncryptionVerifier ver) {
if (password.length() > 255)
protected static SecretKey generateSecretKey(String password, EncryptionVerifier ver) {
if (password.length() > 255) {
password = password.substring(0, 255);
}
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
byte hash[] = hashAlg.digest(StringUtil.getToUnicodeLE(password));
@ -116,15 +131,22 @@ public class BinaryRC4Decryptor extends Decryptor {
return skey;
}
@Override
@SuppressWarnings("resource")
public InputStream getDataStream(DirectoryNode dir) throws IOException,
public ChunkedCipherInputStream getDataStream(DirectoryNode dir) throws IOException,
GeneralSecurityException {
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
_length = dis.readLong();
BinaryRC4CipherInputStream cipherStream = new BinaryRC4CipherInputStream(dis, _length);
return cipherStream;
return new BinaryRC4CipherInputStream(dis, _length);
}
public InputStream getDataStream(LittleEndianInput stream)
throws IOException, GeneralSecurityException {
return new BinaryRC4CipherInputStream(stream);
}
@Override
public long getLength() {
if (_length == -1L) {
throw new IllegalStateException("Decryptor.getDataStream() was not called");
@ -132,4 +154,14 @@ public class BinaryRC4Decryptor extends Decryptor {
return _length;
}
@Override
public void setChunkSize(int chunkSize) {
_chunkSize = chunkSize;
}
@Override
public BinaryRC4Decryptor clone() throws CloneNotSupportedException {
return (BinaryRC4Decryptor)super.clone();
}
}

View File

@ -24,8 +24,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
public class BinaryRC4EncryptionHeader extends EncryptionHeader implements
EncryptionRecord {
public class BinaryRC4EncryptionHeader extends EncryptionHeader implements EncryptionRecord, Cloneable {
protected BinaryRC4EncryptionHeader() {
setCipherAlgorithm(CipherAlgorithm.rc4);
@ -39,6 +38,14 @@ public class BinaryRC4EncryptionHeader extends EncryptionHeader implements
setChainingMode(null);
}
@Override
public void write(LittleEndianByteArrayOutputStream littleendianbytearrayoutputstream) {
}
@Override
public BinaryRC4EncryptionHeader clone() throws CloneNotSupportedException {
return (BinaryRC4EncryptionHeader)super.clone();
}
}

View File

@ -23,55 +23,37 @@ import org.apache.poi.util.LittleEndianInput;
public class BinaryRC4EncryptionInfoBuilder implements EncryptionInfoBuilder {
EncryptionInfo info;
BinaryRC4EncryptionHeader header;
BinaryRC4EncryptionVerifier verifier;
BinaryRC4Decryptor decryptor;
BinaryRC4Encryptor encryptor;
public BinaryRC4EncryptionInfoBuilder() {
}
@Override
public void initialize(EncryptionInfo info, LittleEndianInput dis)
throws IOException {
this.info = info;
int vMajor = info.getVersionMajor();
int vMinor = info.getVersionMinor();
assert (vMajor == 1 && vMinor == 1);
header = new BinaryRC4EncryptionHeader();
verifier = new BinaryRC4EncryptionVerifier(dis);
decryptor = new BinaryRC4Decryptor(this);
encryptor = new BinaryRC4Encryptor(this);
info.setHeader(new BinaryRC4EncryptionHeader());
info.setVerifier(new BinaryRC4EncryptionVerifier(dis));
Decryptor dec = new BinaryRC4Decryptor();
dec.setEncryptionInfo(info);
info.setDecryptor(dec);
Encryptor enc = new BinaryRC4Encryptor();
enc.setEncryptionInfo(info);
info.setEncryptor(enc);
}
@Override
public void initialize(EncryptionInfo info,
CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm,
int keyBits, int blockSize, ChainingMode chainingMode) {
this.info = info;
header = new BinaryRC4EncryptionHeader();
verifier = new BinaryRC4EncryptionVerifier();
decryptor = new BinaryRC4Decryptor(this);
encryptor = new BinaryRC4Encryptor(this);
}
public BinaryRC4EncryptionHeader getHeader() {
return header;
}
public BinaryRC4EncryptionVerifier getVerifier() {
return verifier;
}
public BinaryRC4Decryptor getDecryptor() {
return decryptor;
}
public BinaryRC4Encryptor getEncryptor() {
return encryptor;
}
public EncryptionInfo getEncryptionInfo() {
return info;
info.setHeader(new BinaryRC4EncryptionHeader());
info.setVerifier(new BinaryRC4EncryptionVerifier());
Decryptor dec = new BinaryRC4Decryptor();
dec.setEncryptionInfo(info);
info.setDecryptor(dec);
Encryptor enc = new BinaryRC4Encryptor();
enc.setEncryptionInfo(info);
info.setEncryptor(enc);
}
}

View File

@ -23,7 +23,7 @@ import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
import org.apache.poi.util.LittleEndianInput;
public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements EncryptionRecord {
public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements EncryptionRecord, Cloneable {
protected BinaryRC4EncryptionVerifier() {
setSpinCount(-1);
@ -50,6 +50,7 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E
setHashAlgorithm(HashAlgorithm.md5);
}
@Override
protected void setSalt(byte salt[]) {
if (salt == null || salt.length != 16) {
throw new EncryptedDocumentException("invalid verifier salt");
@ -58,14 +59,17 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E
super.setSalt(salt);
}
@Override
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
super.setEncryptedVerifier(encryptedVerifier);
}
@Override
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
super.setEncryptedVerifierHash(encryptedVerifierHash);
}
@Override
public void write(LittleEndianByteArrayOutputStream bos) {
byte salt[] = getSalt();
assert (salt.length == 16);
@ -78,4 +82,8 @@ public class BinaryRC4EncryptionVerifier extends EncryptionVerifier implements E
bos.write(encryptedVerifierHash);
}
@Override
public BinaryRC4EncryptionVerifier clone() throws CloneNotSupportedException {
return (BinaryRC4EncryptionVerifier)super.clone();
}
}

View File

@ -34,24 +34,95 @@ import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.Encryptor;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
public class BinaryRC4Encryptor extends Encryptor {
public class BinaryRC4Encryptor extends Encryptor implements Cloneable {
protected BinaryRC4Encryptor() {
}
@Override
public void confirmPassword(String password) {
Random r = new SecureRandom();
byte salt[] = new byte[16];
byte verifier[] = new byte[16];
r.nextBytes(salt);
r.nextBytes(verifier);
confirmPassword(password, null, null, verifier, salt, null);
}
@Override
public void confirmPassword(String password, byte keySpec[],
byte keySalt[], byte verifier[], byte verifierSalt[],
byte integritySalt[]) {
BinaryRC4EncryptionVerifier ver = (BinaryRC4EncryptionVerifier)getEncryptionInfo().getVerifier();
ver.setSalt(verifierSalt);
SecretKey skey = BinaryRC4Decryptor.generateSecretKey(password, ver);
setSecretKey(skey);
try {
Cipher cipher = BinaryRC4Decryptor.initCipherForBlock(null, 0, getEncryptionInfo(), skey, Cipher.ENCRYPT_MODE);
byte encryptedVerifier[] = new byte[16];
cipher.update(verifier, 0, 16, encryptedVerifier);
ver.setEncryptedVerifier(encryptedVerifier);
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
byte calcVerifierHash[] = hashAlg.digest(verifier);
byte encryptedVerifierHash[] = cipher.doFinal(calcVerifierHash);
ver.setEncryptedVerifierHash(encryptedVerifierHash);
} catch (GeneralSecurityException e) {
throw new EncryptedDocumentException("Password confirmation failed", e);
}
}
@Override
public OutputStream getDataStream(DirectoryNode dir)
throws IOException, GeneralSecurityException {
OutputStream countStream = new BinaryRC4CipherOutputStream(dir);
return countStream;
}
protected int getKeySizeInBytes() {
return getEncryptionInfo().getHeader().getKeySize() / 8;
}
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
DataSpaceMapUtils.addDefaultDataSpace(dir);
final EncryptionInfo info = getEncryptionInfo();
final BinaryRC4EncryptionHeader header = (BinaryRC4EncryptionHeader)info.getHeader();
final BinaryRC4EncryptionVerifier verifier = (BinaryRC4EncryptionVerifier)info.getVerifier();
EncryptionRecord er = new EncryptionRecord() {
@Override
public void write(LittleEndianByteArrayOutputStream bos) {
bos.writeShort(info.getVersionMajor());
bos.writeShort(info.getVersionMinor());
header.write(bos);
verifier.write(bos);
}
};
DataSpaceMapUtils.createEncryptionEntry(dir, "EncryptionInfo", er);
}
@Override
public BinaryRC4Encryptor clone() throws CloneNotSupportedException {
return (BinaryRC4Encryptor)super.clone();
}
private final BinaryRC4EncryptionInfoBuilder builder;
protected class BinaryRC4CipherOutputStream extends ChunkedCipherOutputStream {
@Override
protected Cipher initCipherForBlock(Cipher cipher, int block, boolean lastChunk)
throws GeneralSecurityException {
return BinaryRC4Decryptor.initCipherForBlock(cipher, block, builder, getSecretKey(), Cipher.ENCRYPT_MODE);
return BinaryRC4Decryptor.initCipherForBlock(cipher, block, getEncryptionInfo(), getSecretKey(), Cipher.ENCRYPT_MODE);
}
@Override
protected void calculateChecksum(File file, int i) {
}
@Override
protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
throws IOException, GeneralSecurityException {
BinaryRC4Encryptor.this.createEncryptionInfoEntry(dir);
@ -62,66 +133,4 @@ public class BinaryRC4Encryptor extends Encryptor {
super(dir, 512);
}
}
protected BinaryRC4Encryptor(BinaryRC4EncryptionInfoBuilder builder) {
this.builder = builder;
}
public void confirmPassword(String password) {
Random r = new SecureRandom();
byte salt[] = new byte[16];
byte verifier[] = new byte[16];
r.nextBytes(salt);
r.nextBytes(verifier);
confirmPassword(password, null, null, verifier, salt, null);
}
public void confirmPassword(String password, byte keySpec[],
byte keySalt[], byte verifier[], byte verifierSalt[],
byte integritySalt[]) {
BinaryRC4EncryptionVerifier ver = builder.getVerifier();
ver.setSalt(verifierSalt);
SecretKey skey = BinaryRC4Decryptor.generateSecretKey(password, ver);
setSecretKey(skey);
try {
Cipher cipher = BinaryRC4Decryptor.initCipherForBlock(null, 0, builder, skey, Cipher.ENCRYPT_MODE);
byte encryptedVerifier[] = new byte[16];
cipher.update(verifier, 0, 16, encryptedVerifier);
ver.setEncryptedVerifier(encryptedVerifier);
org.apache.poi.poifs.crypt.HashAlgorithm hashAlgo = ver
.getHashAlgorithm();
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
byte calcVerifierHash[] = hashAlg.digest(verifier);
byte encryptedVerifierHash[] = cipher.doFinal(calcVerifierHash);
ver.setEncryptedVerifierHash(encryptedVerifierHash);
} catch (GeneralSecurityException e) {
throw new EncryptedDocumentException("Password confirmation failed", e);
}
}
public OutputStream getDataStream(DirectoryNode dir)
throws IOException, GeneralSecurityException {
OutputStream countStream = new BinaryRC4CipherOutputStream(dir);
return countStream;
}
protected int getKeySizeInBytes() {
return builder.getHeader().getKeySize() / 8;
}
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
DataSpaceMapUtils.addDefaultDataSpace(dir);
final EncryptionInfo info = builder.getEncryptionInfo();
final BinaryRC4EncryptionHeader header = builder.getHeader();
final BinaryRC4EncryptionVerifier verifier = builder.getVerifier();
EncryptionRecord er = new EncryptionRecord() {
public void write(LittleEndianByteArrayOutputStream bos) {
bos.writeShort(info.getVersionMajor());
bos.writeShort(info.getVersionMinor());
header.write(bos);
verifier.write(bos);
}
};
DataSpaceMapUtils.createEncryptionEntry(dir, "EncryptionInfo", er);
}
}

View File

@ -17,7 +17,6 @@
package org.apache.poi.poifs.crypt.cryptoapi;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@ -27,79 +26,34 @@ import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.poifs.crypt.ChunkedCipherInputStream;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionHeader;
import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.EncryptionVerifier;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.poifs.filesystem.DocumentNode;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.BoundedInputStream;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.StringUtil;
public class CryptoAPIDecryptor extends Decryptor {
public class CryptoAPIDecryptor extends Decryptor implements Cloneable {
private long _length;
private int _chunkSize = -1;
private class SeekableByteArrayInputStream extends ByteArrayInputStream {
Cipher cipher;
byte oneByte[] = { 0 };
public void seek(int newpos) {
if (newpos > count) {
throw new ArrayIndexOutOfBoundsException(newpos);
}
this.pos = newpos;
mark = newpos;
}
public void setBlock(int block) throws GeneralSecurityException {
cipher = initCipherForBlock(cipher, block);
}
public synchronized int read() {
int ch = super.read();
if (ch == -1) return -1;
oneByte[0] = (byte) ch;
try {
cipher.update(oneByte, 0, 1, oneByte);
} catch (ShortBufferException e) {
throw new EncryptedDocumentException(e);
}
return oneByte[0];
}
public synchronized int read(byte b[], int off, int len) {
int readLen = super.read(b, off, len);
if (readLen ==-1) return -1;
try {
cipher.update(b, off, readLen, b, off);
} catch (ShortBufferException e) {
throw new EncryptedDocumentException(e);
}
return readLen;
}
public SeekableByteArrayInputStream(byte buf[])
throws GeneralSecurityException {
super(buf);
cipher = initCipherForBlock(null, 0);
}
}
static class StreamDescriptorEntry {
static BitField flagStream = BitFieldFactory.getInstance(1);
@ -111,16 +65,16 @@ public class CryptoAPIDecryptor extends Decryptor {
String streamName;
}
protected CryptoAPIDecryptor(CryptoAPIEncryptionInfoBuilder builder) {
super(builder);
protected CryptoAPIDecryptor() {
_length = -1L;
}
@Override
public boolean verifyPassword(String password) {
EncryptionVerifier ver = builder.getVerifier();
EncryptionVerifier ver = getEncryptionInfo().getVerifier();
SecretKey skey = generateSecretKey(password, ver);
try {
Cipher cipher = initCipherForBlock(null, 0, builder, skey, Cipher.DECRYPT_MODE);
Cipher cipher = initCipherForBlock(null, 0, getEncryptionInfo(), skey, Cipher.DECRYPT_MODE);
byte encryptedVerifier[] = ver.getEncryptedVerifier();
byte verifier[] = new byte[encryptedVerifier.length];
cipher.update(encryptedVerifier, 0, encryptedVerifier.length, verifier);
@ -140,30 +94,25 @@ public class CryptoAPIDecryptor extends Decryptor {
return false;
}
/**
* Initializes a cipher object for a given block index for decryption
*
* @param cipher may be null, otherwise the given instance is reset to the new block index
* @param block the block index, e.g. the persist/slide id (hslf)
* @return a new cipher object, if cipher was null, otherwise the reinitialized cipher
* @throws GeneralSecurityException
*/
@Override
public Cipher initCipherForBlock(Cipher cipher, int block)
throws GeneralSecurityException {
return initCipherForBlock(cipher, block, builder, getSecretKey(), Cipher.DECRYPT_MODE);
EncryptionInfo ei = getEncryptionInfo();
SecretKey sk = getSecretKey();
return initCipherForBlock(cipher, block, ei, sk, Cipher.DECRYPT_MODE);
}
protected static Cipher initCipherForBlock(Cipher cipher, int block,
EncryptionInfoBuilder builder, SecretKey skey, int encryptMode)
EncryptionInfo encryptionInfo, SecretKey skey, int encryptMode)
throws GeneralSecurityException {
EncryptionVerifier ver = builder.getVerifier();
EncryptionVerifier ver = encryptionInfo.getVerifier();
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
byte blockKey[] = new byte[4];
LittleEndian.putUInt(blockKey, 0, block);
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
hashAlg.update(skey.getEncoded());
byte encKey[] = hashAlg.digest(blockKey);
EncryptionHeader header = builder.getHeader();
EncryptionHeader header = encryptionInfo.getHeader();
int keyBits = header.getKeySize();
encKey = CryptoFunctions.getBlock0(encKey, keyBits / 8);
if (keyBits == 40) {
@ -190,6 +139,18 @@ public class CryptoAPIDecryptor extends Decryptor {
return skey;
}
@Override
public ChunkedCipherInputStream getDataStream(DirectoryNode dir)
throws IOException, GeneralSecurityException {
throw new IOException("not supported");
}
@Override
public ChunkedCipherInputStream getDataStream(LittleEndianInput stream, int size, int initialPos)
throws IOException, GeneralSecurityException {
return new CryptoAPICipherInputStream(stream, size, initialPos);
}
/**
* Decrypt the Document-/SummaryInformation and other optionally streams.
* Opposed to other crypto modes, cryptoapi is record based and can't be used
@ -197,15 +158,17 @@ public class CryptoAPIDecryptor extends Decryptor {
*
* @see <a href="http://msdn.microsoft.com/en-us/library/dd943321(v=office.12).aspx">2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream</a>
*/
public InputStream getDataStream(DirectoryNode dir)
public POIFSFileSystem getSummaryEntries(DirectoryNode root, String encryptedStream)
throws IOException, GeneralSecurityException {
NPOIFSFileSystem fsOut = new NPOIFSFileSystem();
DocumentNode es = (DocumentNode) dir.getEntry("EncryptedSummary");
DocumentInputStream dis = dir.createDocumentInputStream(es);
POIFSFileSystem fsOut = new POIFSFileSystem();
// HSLF: encryptedStream
// HSSF: encryption
DocumentNode es = (DocumentNode) root.getEntry(encryptedStream);
DocumentInputStream dis = root.createDocumentInputStream(es);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOUtils.copy(dis, bos);
dis.close();
SeekableByteArrayInputStream sbis = new SeekableByteArrayInputStream(bos.toByteArray());
CryptoAPIDocumentInputStream sbis = new CryptoAPIDocumentInputStream(this, bos.toByteArray());
LittleEndianInputStream leis = new LittleEndianInputStream(sbis);
int streamDescriptorArrayOffset = (int) leis.readUInt();
/* int streamDescriptorArraySize = (int) */ leis.readUInt();
@ -239,21 +202,40 @@ public class CryptoAPIDecryptor extends Decryptor {
leis.close();
sbis.close();
sbis = null;
bos.reset();
fsOut.writeFilesystem(bos);
fsOut.close();
_length = bos.size();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
return bis;
return fsOut;
}
/**
* @return the length of the stream returned by {@link #getDataStream(DirectoryNode)}
*/
@Override
public long getLength() {
if (_length == -1L) {
throw new IllegalStateException("Decryptor.getDataStream() was not called");
}
return _length;
}
public void setChunkSize(int chunkSize) {
_chunkSize = chunkSize;
}
@Override
public CryptoAPIDecryptor clone() throws CloneNotSupportedException {
return (CryptoAPIDecryptor)super.clone();
}
private class CryptoAPICipherInputStream extends ChunkedCipherInputStream {
@Override
protected Cipher initCipherForBlock(Cipher existing, int block)
throws GeneralSecurityException {
return CryptoAPIDecryptor.this.initCipherForBlock(existing, block);
}
public CryptoAPICipherInputStream(LittleEndianInput stream, long size, int initialPos)
throws GeneralSecurityException {
super(stream, size, _chunkSize, initialPos);
}
}
}

View File

@ -0,0 +1,86 @@
/* ====================================================================
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.cryptoapi;
import java.io.ByteArrayInputStream;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.ShortBufferException;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.util.Internal;
/**
* A seekable InputStream, which is used to decrypt/extract the document entries
* within the encrypted stream
*/
@Internal
/* package */ class CryptoAPIDocumentInputStream extends ByteArrayInputStream {
private Cipher cipher;
private final CryptoAPIDecryptor decryptor;
private byte oneByte[] = { 0 };
public void seek(int newpos) {
if (newpos > count) {
throw new ArrayIndexOutOfBoundsException(newpos);
}
this.pos = newpos;
mark = newpos;
}
public void setBlock(int block) throws GeneralSecurityException {
cipher = decryptor.initCipherForBlock(cipher, block);
}
@Override
public synchronized int read() {
int ch = super.read();
if (ch == -1) {
return -1;
}
oneByte[0] = (byte) ch;
try {
cipher.update(oneByte, 0, 1, oneByte);
} catch (ShortBufferException e) {
throw new EncryptedDocumentException(e);
}
return oneByte[0];
}
@Override
public synchronized int read(byte b[], int off, int len) {
int readLen = super.read(b, off, len);
if (readLen ==-1) {
return -1;
}
try {
cipher.update(b, off, readLen, b, off);
} catch (ShortBufferException e) {
throw new EncryptedDocumentException(e);
}
return readLen;
}
public CryptoAPIDocumentInputStream(CryptoAPIDecryptor decryptor, byte buf[])
throws GeneralSecurityException {
super(buf);
this.decryptor = decryptor;
cipher = decryptor.initCipherForBlock(null, 0);
}
}

View File

@ -0,0 +1,74 @@
/* ====================================================================
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.cryptoapi;
import java.io.ByteArrayOutputStream;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.util.Internal;
/**
* An OutputStream for the document entries within the encrypted stream
*/
@Internal
/* package */ class CryptoAPIDocumentOutputStream extends ByteArrayOutputStream {
private Cipher cipher;
private CryptoAPIEncryptor encryptor;
private byte oneByte[] = { 0 };
public CryptoAPIDocumentOutputStream(CryptoAPIEncryptor encryptor) throws GeneralSecurityException {
this.encryptor = encryptor;
setBlock(0);
}
public byte[] getBuf() {
return buf;
}
public void setSize(int count) {
this.count = count;
}
public void setBlock(int block) throws GeneralSecurityException {
cipher = encryptor.initCipherForBlock(cipher, block);
}
@Override
public void write(int b) {
try {
oneByte[0] = (byte)b;
cipher.update(oneByte, 0, 1, oneByte, 0);
super.write(oneByte);
} catch (Exception e) {
throw new EncryptedDocumentException(e);
}
}
@Override
public void write(byte[] b, int off, int len) {
try {
cipher.update(b, off, len, b, off);
super.write(b, off, len);
} catch (Exception e) {
throw new EncryptedDocumentException(e);
}
}
}

View File

@ -27,7 +27,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.standard.StandardEncryptionHeader;
import org.apache.poi.util.LittleEndianInput;
public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader {
public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader implements Cloneable {
public CryptoAPIEncryptionHeader(LittleEndianInput is) throws IOException {
super(is);
@ -39,6 +39,7 @@ public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader {
super(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
}
@Override
public void setKeySize(int keyBits) {
// Microsoft Base Cryptographic Provider is limited up to 40 bits
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa375599(v=vs.85).aspx
@ -59,4 +60,9 @@ public class CryptoAPIEncryptionHeader extends StandardEncryptionHeader {
setCspName(CipherProvider.rc4.cipherProviderName);
}
}
@Override
public CryptoAPIEncryptionHeader clone() throws CloneNotSupportedException {
return (CryptoAPIEncryptionHeader)super.clone();
}
}

View File

@ -23,63 +23,52 @@ import org.apache.poi.poifs.crypt.*;
import org.apache.poi.util.LittleEndianInput;
public class CryptoAPIEncryptionInfoBuilder implements EncryptionInfoBuilder {
EncryptionInfo info;
CryptoAPIEncryptionHeader header;
CryptoAPIEncryptionVerifier verifier;
CryptoAPIDecryptor decryptor;
CryptoAPIEncryptor encryptor;
public CryptoAPIEncryptionInfoBuilder() {
}
/**
* initialize the builder from a stream
*/
@Override
public void initialize(EncryptionInfo info, LittleEndianInput dis)
throws IOException {
this.info = info;
/* int hSize = */ dis.readInt();
header = new CryptoAPIEncryptionHeader(dis);
verifier = new CryptoAPIEncryptionVerifier(dis, header);
decryptor = new CryptoAPIDecryptor(this);
encryptor = new CryptoAPIEncryptor(this);
CryptoAPIEncryptionHeader header = new CryptoAPIEncryptionHeader(dis);
info.setHeader(header);
info.setVerifier(new CryptoAPIEncryptionVerifier(dis, header));
CryptoAPIDecryptor dec = new CryptoAPIDecryptor();
dec.setEncryptionInfo(info);
info.setDecryptor(dec);
CryptoAPIEncryptor enc = new CryptoAPIEncryptor();
enc.setEncryptionInfo(info);
info.setEncryptor(enc);
}
/**
* initialize the builder from scratch
*/
@Override
public void initialize(EncryptionInfo info,
CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm,
int keyBits, int blockSize, ChainingMode chainingMode) {
this.info = info;
if (cipherAlgorithm == null) cipherAlgorithm = CipherAlgorithm.rc4;
if (hashAlgorithm == null) hashAlgorithm = HashAlgorithm.sha1;
if (keyBits == -1) keyBits = 0x28;
if (cipherAlgorithm == null) {
cipherAlgorithm = CipherAlgorithm.rc4;
}
if (hashAlgorithm == null) {
hashAlgorithm = HashAlgorithm.sha1;
}
if (keyBits == -1) {
keyBits = 0x28;
}
assert(cipherAlgorithm == CipherAlgorithm.rc4 && hashAlgorithm == HashAlgorithm.sha1);
header = new CryptoAPIEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
verifier = new CryptoAPIEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
decryptor = new CryptoAPIDecryptor(this);
encryptor = new CryptoAPIEncryptor(this);
}
public CryptoAPIEncryptionHeader getHeader() {
return header;
}
public CryptoAPIEncryptionVerifier getVerifier() {
return verifier;
}
public CryptoAPIDecryptor getDecryptor() {
return decryptor;
}
public CryptoAPIEncryptor getEncryptor() {
return encryptor;
}
public EncryptionInfo getEncryptionInfo() {
return info;
info.setHeader(new CryptoAPIEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
info.setVerifier(new CryptoAPIEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
CryptoAPIDecryptor dec = new CryptoAPIDecryptor();
dec.setEncryptionInfo(info);
info.setDecryptor(dec);
CryptoAPIEncryptor enc = new CryptoAPIEncryptor();
enc.setEncryptionInfo(info);
info.setEncryptor(enc);
}
}

View File

@ -23,7 +23,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.standard.StandardEncryptionVerifier;
import org.apache.poi.util.LittleEndianInput;
public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier {
public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier implements Cloneable {
protected CryptoAPIEncryptionVerifier(LittleEndianInput is,
CryptoAPIEncryptionHeader header) {
@ -36,15 +36,23 @@ public class CryptoAPIEncryptionVerifier extends StandardEncryptionVerifier {
super(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
}
@Override
protected void setSalt(byte salt[]) {
super.setSalt(salt);
}
@Override
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
super.setEncryptedVerifier(encryptedVerifier);
}
@Override
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
super.setEncryptedVerifierHash(encryptedVerifierHash);
}
@Override
public CryptoAPIEncryptionVerifier clone() throws CloneNotSupportedException {
return (CryptoAPIEncryptionVerifier)super.clone();
}
}

View File

@ -18,7 +18,7 @@
package org.apache.poi.poifs.crypt.cryptoapi;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
@ -36,6 +36,7 @@ import org.apache.poi.hpsf.DocumentSummaryInformation;
import org.apache.poi.hpsf.PropertySetFactory;
import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.hpsf.WritingNotSupportedException;
import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
import org.apache.poi.poifs.crypt.EncryptionInfo;
@ -50,13 +51,14 @@ import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
import org.apache.poi.util.StringUtil;
public class CryptoAPIEncryptor extends Encryptor {
private final CryptoAPIEncryptionInfoBuilder builder;
protected CryptoAPIEncryptor(CryptoAPIEncryptionInfoBuilder builder) {
this.builder = builder;
public class CryptoAPIEncryptor extends Encryptor implements Cloneable {
private int _chunkSize = 512;
protected CryptoAPIEncryptor() {
}
@Override
public void confirmPassword(String password) {
Random r = new SecureRandom();
byte salt[] = new byte[16];
@ -66,11 +68,12 @@ public class CryptoAPIEncryptor extends Encryptor {
confirmPassword(password, null, null, verifier, salt, null);
}
@Override
public void confirmPassword(String password, byte keySpec[],
byte keySalt[], byte verifier[], byte verifierSalt[],
byte integritySalt[]) {
assert(verifier != null && verifierSalt != null);
CryptoAPIEncryptionVerifier ver = builder.getVerifier();
CryptoAPIEncryptionVerifier ver = (CryptoAPIEncryptionVerifier)getEncryptionInfo().getVerifier();
ver.setSalt(verifierSalt);
SecretKey skey = CryptoAPIDecryptor.generateSecretKey(password, ver);
setSecretKey(skey);
@ -99,8 +102,19 @@ public class CryptoAPIEncryptor extends Encryptor {
*/
public Cipher initCipherForBlock(Cipher cipher, int block)
throws GeneralSecurityException {
return CryptoAPIDecryptor.initCipherForBlock(cipher, block, builder, getSecretKey(), Cipher.ENCRYPT_MODE);
return CryptoAPIDecryptor.initCipherForBlock(cipher, block, getEncryptionInfo(), getSecretKey(), Cipher.ENCRYPT_MODE);
}
@Override
public ChunkedCipherOutputStream getDataStream(DirectoryNode dir)
throws IOException, GeneralSecurityException {
throw new IOException("not supported");
}
public CryptoAPICipherOutputStream getDataStream(OutputStream stream)
throws IOException, GeneralSecurityException {
return new CryptoAPICipherOutputStream(stream);
}
/**
* Encrypt the Document-/SummaryInformation and other optionally streams.
@ -109,9 +123,9 @@ public class CryptoAPIEncryptor extends Encryptor {
*
* @see <a href="http://msdn.microsoft.com/en-us/library/dd943321(v=office.12).aspx">2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream</a>
*/
public OutputStream getDataStream(DirectoryNode dir)
public OutputStream getSummaryEntries(DirectoryNode dir)
throws IOException, GeneralSecurityException {
CipherByteArrayOutputStream bos = new CipherByteArrayOutputStream();
CryptoAPIDocumentOutputStream bos = new CryptoAPIDocumentOutputStream(this);
byte buf[] = new byte[8];
bos.write(buf, 0, 8); // skip header
@ -124,7 +138,9 @@ public class CryptoAPIEncryptor extends Encryptor {
int block = 0;
for (String entryName : entryNames) {
if (!dir.hasEntry(entryName)) continue;
if (!dir.hasEntry(entryName)) {
continue;
}
StreamDescriptorEntry descEntry = new StreamDescriptorEntry();
descEntry.block = block;
descEntry.streamOffset = bos.size();
@ -193,15 +209,20 @@ public class CryptoAPIEncryptor extends Encryptor {
}
protected int getKeySizeInBytes() {
return builder.getHeader().getKeySize() / 8;
return getEncryptionInfo().getHeader().getKeySize() / 8;
}
public void setChunkSize(int chunkSize) {
_chunkSize = chunkSize;
}
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
DataSpaceMapUtils.addDefaultDataSpace(dir);
final EncryptionInfo info = builder.getEncryptionInfo();
final CryptoAPIEncryptionHeader header = builder.getHeader();
final CryptoAPIEncryptionVerifier verifier = builder.getVerifier();
final EncryptionInfo info = getEncryptionInfo();
final CryptoAPIEncryptionHeader header = (CryptoAPIEncryptionHeader)getEncryptionInfo().getHeader();
final CryptoAPIEncryptionVerifier verifier = (CryptoAPIEncryptionVerifier)getEncryptionInfo().getVerifier();
EncryptionRecord er = new EncryptionRecord() {
@Override
public void write(LittleEndianByteArrayOutputStream bos) {
bos.writeShort(info.getVersionMajor());
bos.writeShort(info.getVersionMinor());
@ -212,44 +233,42 @@ public class CryptoAPIEncryptor extends Encryptor {
DataSpaceMapUtils.createEncryptionEntry(dir, "EncryptionInfo", er);
}
private class CipherByteArrayOutputStream extends ByteArrayOutputStream {
Cipher cipher;
byte oneByte[] = { 0 };
public CipherByteArrayOutputStream() throws GeneralSecurityException {
setBlock(0);
}
public byte[] getBuf() {
return buf;
}
public void setSize(int count) {
this.count = count;
}
public void setBlock(int block) throws GeneralSecurityException {
cipher = initCipherForBlock(cipher, block);
}
public void write(int b) {
try {
oneByte[0] = (byte)b;
cipher.update(oneByte, 0, 1, oneByte, 0);
super.write(oneByte);
} catch (Exception e) {
throw new EncryptedDocumentException(e);
}
}
public void write(byte[] b, int off, int len) {
try {
cipher.update(b, off, len, b, off);
super.write(b, off, len);
} catch (Exception e) {
throw new EncryptedDocumentException(e);
}
}
@Override
public CryptoAPIEncryptor clone() throws CloneNotSupportedException {
return (CryptoAPIEncryptor)super.clone();
}
protected class CryptoAPICipherOutputStream extends ChunkedCipherOutputStream {
@Override
protected Cipher initCipherForBlock(Cipher cipher, int block, boolean lastChunk)
throws IOException, GeneralSecurityException {
flush();
EncryptionInfo ei = getEncryptionInfo();
SecretKey sk = getSecretKey();
return CryptoAPIDecryptor.initCipherForBlock(cipher, block, ei, sk, Cipher.ENCRYPT_MODE);
}
@Override
protected void calculateChecksum(File file, int i) {
}
@Override
protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
throws IOException, GeneralSecurityException {
throw new RuntimeException("createEncryptionInfoEntry not supported");
}
public CryptoAPICipherOutputStream(OutputStream stream)
throws IOException, GeneralSecurityException {
super(stream, CryptoAPIEncryptor.this._chunkSize);
}
@Override
public void flush() throws IOException {
writeChunk(false);
}
}
}

View File

@ -34,7 +34,6 @@ import org.apache.poi.poifs.crypt.ChainingMode;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionHeader;
import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
import org.apache.poi.poifs.crypt.EncryptionVerifier;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.filesystem.DirectoryNode;
@ -44,15 +43,15 @@ import org.apache.poi.util.LittleEndian;
/**
*/
public class StandardDecryptor extends Decryptor {
public class StandardDecryptor extends Decryptor implements Cloneable {
private long _length = -1;
protected StandardDecryptor(EncryptionInfoBuilder builder) {
super(builder);
protected StandardDecryptor() {
}
@Override
public boolean verifyPassword(String password) {
EncryptionVerifier ver = builder.getVerifier();
EncryptionVerifier ver = getEncryptionInfo().getVerifier();
SecretKey skey = generateSecretKey(password, ver, getKeySizeInBytes());
Cipher cipher = getCipher(skey);
@ -116,12 +115,13 @@ public class StandardDecryptor extends Decryptor {
}
private Cipher getCipher(SecretKey key) {
EncryptionHeader em = builder.getHeader();
EncryptionHeader em = getEncryptionInfo().getHeader();
ChainingMode cm = em.getChainingMode();
assert(cm == ChainingMode.ecb);
return CryptoFunctions.getCipher(key, em.getCipherAlgorithm(), cm, null, Cipher.DECRYPT_MODE);
}
@Override
@SuppressWarnings("resource")
public InputStream getDataStream(DirectoryNode dir) throws IOException {
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
@ -134,7 +134,7 @@ public class StandardDecryptor extends Decryptor {
// limit wrong calculated ole entries - (bug #57080)
// standard encryption always uses aes encoding, so blockSize is always 16
// http://stackoverflow.com/questions/3283787/size-of-data-after-aes-encryption
int blockSize = builder.getHeader().getCipherAlgorithm().blockSize;
int blockSize = getEncryptionInfo().getHeader().getCipherAlgorithm().blockSize;
long cipherLen = (_length/blockSize + 1) * blockSize;
Cipher cipher = getCipher(getSecretKey());
@ -145,8 +145,16 @@ public class StandardDecryptor extends Decryptor {
/**
* @return the length of the stream returned by {@link #getDataStream(DirectoryNode)}
*/
@Override
public long getLength(){
if(_length == -1) throw new IllegalStateException("Decryptor.getDataStream() was not called");
if(_length == -1) {
throw new IllegalStateException("Decryptor.getDataStream() was not called");
}
return _length;
}
@Override
public StandardDecryptor clone() throws CloneNotSupportedException {
return (StandardDecryptor)super.clone();
}
}

View File

@ -22,6 +22,7 @@ import static org.apache.poi.poifs.crypt.EncryptionInfo.flagCryptoAPI;
import java.io.IOException;
import java.io.InputStream;
import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.poifs.crypt.ChainingMode;
import org.apache.poi.poifs.crypt.CipherAlgorithm;
import org.apache.poi.poifs.crypt.CipherProvider;
@ -33,7 +34,7 @@ import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianOutput;
import org.apache.poi.util.StringUtil;
public class StandardEncryptionHeader extends EncryptionHeader implements EncryptionRecord {
public class StandardEncryptionHeader extends EncryptionHeader implements EncryptionRecord, Cloneable {
protected StandardEncryptionHeader(LittleEndianInput is) throws IOException {
setFlags(is.readInt());
@ -55,9 +56,17 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
// CSPName may not always be specified
// In some cases, the salt value of the EncryptionVerifier is the next chunk of data
((InputStream)is).mark(LittleEndianConsts.INT_SIZE+1);
if (is instanceof RecordInputStream) {
((RecordInputStream)is).mark(LittleEndianConsts.INT_SIZE+1);
} else {
((InputStream)is).mark(LittleEndianConsts.INT_SIZE+1);
}
int checkForSalt = is.readInt();
((InputStream)is).reset();
if (is instanceof RecordInputStream) {
((RecordInputStream)is).reset();
} else {
((InputStream)is).reset();
}
if (checkForSalt == 16) {
setCspName("");
@ -65,7 +74,9 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
StringBuilder builder = new StringBuilder();
while (true) {
char c = (char) is.readShort();
if (c == 0) break;
if (c == 0) {
break;
}
builder.append(c);
}
setCspName(builder.toString());
@ -90,6 +101,7 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
/**
* serializes the header
*/
@Override
public void write(LittleEndianByteArrayOutputStream bos) {
int startIdx = bos.getWriteIndex();
LittleEndianOutput sizeOutput = bos.createDelayedOutput(LittleEndianConsts.INT_SIZE);
@ -102,10 +114,17 @@ public class StandardEncryptionHeader extends EncryptionHeader implements Encryp
bos.writeInt(0); // reserved1
bos.writeInt(0); // reserved2
String cspName = getCspName();
if (cspName == null) cspName = getCipherProvider().cipherProviderName;
if (cspName == null) {
cspName = getCipherProvider().cipherProviderName;
}
bos.write(StringUtil.getToUnicodeLE(cspName));
bos.writeShort(0);
int headerSize = bos.getWriteIndex()-startIdx-LittleEndianConsts.INT_SIZE;
sizeOutput.writeInt(headerSize);
}
@Override
public StandardEncryptionHeader clone() throws CloneNotSupportedException {
return (StandardEncryptionHeader)super.clone();
}
}

View File

@ -27,34 +27,29 @@ import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.util.LittleEndianInput;
public class StandardEncryptionInfoBuilder implements EncryptionInfoBuilder {
EncryptionInfo info;
StandardEncryptionHeader header;
StandardEncryptionVerifier verifier;
StandardDecryptor decryptor;
StandardEncryptor encryptor;
/**
* initialize the builder from a stream
*/
@Override
public void initialize(EncryptionInfo info, LittleEndianInput dis) throws IOException {
this.info = info;
/* int hSize = */ dis.readInt();
header = new StandardEncryptionHeader(dis);
verifier = new StandardEncryptionVerifier(dis, header);
StandardEncryptionHeader header = new StandardEncryptionHeader(dis);
info.setHeader(header);
info.setVerifier(new StandardEncryptionVerifier(dis, header));
if (info.getVersionMinor() == 2 && (info.getVersionMajor() == 3 || info.getVersionMajor() == 4)) {
decryptor = new StandardDecryptor(this);
StandardDecryptor dec = new StandardDecryptor();
dec.setEncryptionInfo(info);
info.setDecryptor(dec);
}
}
/**
* initialize the builder from scratch
*/
@Override
public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
this.info = info;
if (cipherAlgorithm == null) {
cipherAlgorithm = CipherAlgorithm.aes128;
}
@ -89,29 +84,13 @@ public class StandardEncryptionInfoBuilder implements EncryptionInfoBuilder {
if (!found) {
throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for Cipher "+cipherAlgorithm.toString());
}
header = new StandardEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
verifier = new StandardEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
decryptor = new StandardDecryptor(this);
encryptor = new StandardEncryptor(this);
}
public StandardEncryptionHeader getHeader() {
return header;
}
public StandardEncryptionVerifier getVerifier() {
return verifier;
}
public StandardDecryptor getDecryptor() {
return decryptor;
}
public StandardEncryptor getEncryptor() {
return encryptor;
}
public EncryptionInfo getEncryptionInfo() {
return info;
info.setHeader(new StandardEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
info.setVerifier(new StandardEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
StandardDecryptor dec = new StandardDecryptor();
dec.setEncryptionInfo(info);
info.setDecryptor(dec);
StandardEncryptor enc = new StandardEncryptor();
enc.setEncryptionInfo(info);
info.setEncryptor(enc);
}
}

View File

@ -27,7 +27,7 @@ import org.apache.poi.util.LittleEndianInput;
/**
* Used when checking if a key is valid for a document
*/
public class StandardEncryptionVerifier extends EncryptionVerifier implements EncryptionRecord {
public class StandardEncryptionVerifier extends EncryptionVerifier implements EncryptionRecord, Cloneable {
private static final int SPIN_COUNT = 50000;
private final int verifierHashSize;
@ -68,6 +68,7 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En
}
// make method visible for this package
@Override
protected void setSalt(byte salt[]) {
if (salt == null || salt.length != 16) {
throw new EncryptedDocumentException("invalid verifier salt");
@ -76,15 +77,18 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En
}
// make method visible for this package
@Override
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
super.setEncryptedVerifier(encryptedVerifier);
}
// make method visible for this package
@Override
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
super.setEncryptedVerifierHash(encryptedVerifierHash);
}
@Override
public void write(LittleEndianByteArrayOutputStream bos) {
// see [MS-OFFCRYPTO] - 2.3.4.9
byte salt[] = getSalt();
@ -115,4 +119,9 @@ public class StandardEncryptionVerifier extends EncryptionVerifier implements En
protected int getVerifierHashSize() {
return verifierHashSize;
}
@Override
public StandardEncryptionVerifier clone() throws CloneNotSupportedException {
return (StandardEncryptionVerifier)super.clone();
}
}

View File

@ -53,15 +53,13 @@ import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.TempFile;
public class StandardEncryptor extends Encryptor {
public class StandardEncryptor extends Encryptor implements Cloneable {
private static final POILogger logger = POILogFactory.getLogger(StandardEncryptor.class);
private final StandardEncryptionInfoBuilder builder;
protected StandardEncryptor(StandardEncryptionInfoBuilder builder) {
this.builder = builder;
protected StandardEncryptor() {
}
@Override
public void confirmPassword(String password) {
// see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier
Random r = new SecureRandom();
@ -79,8 +77,9 @@ public class StandardEncryptor extends Encryptor {
*
* see [MS-OFFCRYPTO] - 2.3.4.7 ECMA-376 Document Encryption Key Generation
*/
@Override
public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) {
StandardEncryptionVerifier ver = builder.getVerifier();
StandardEncryptionVerifier ver = (StandardEncryptionVerifier)getEncryptionInfo().getVerifier();
ver.setSalt(verifierSalt);
SecretKey secretKey = generateSecretKey(password, ver, getKeySizeInBytes());
@ -111,10 +110,11 @@ public class StandardEncryptor extends Encryptor {
}
private Cipher getCipher(SecretKey key, String padding) {
EncryptionVerifier ver = builder.getVerifier();
EncryptionVerifier ver = getEncryptionInfo().getVerifier();
return CryptoFunctions.getCipher(key, ver.getCipherAlgorithm(), ver.getChainingMode(), null, Cipher.ENCRYPT_MODE, padding);
}
@Override
public OutputStream getDataStream(final DirectoryNode dir)
throws IOException, GeneralSecurityException {
createEncryptionInfoEntry(dir);
@ -163,6 +163,7 @@ public class StandardEncryptor extends Encryptor {
countBytes++;
}
@Override
public void close() throws IOException {
// the CipherOutputStream adds the padding bytes on close()
super.close();
@ -175,6 +176,7 @@ public class StandardEncryptor extends Encryptor {
// TODO: any properties???
}
@Override
public void processPOIFSWriterEvent(POIFSWriterEvent event) {
try {
LittleEndianOutputStream leos = new LittleEndianOutputStream(event.getStream());
@ -200,15 +202,16 @@ public class StandardEncryptor extends Encryptor {
}
protected int getKeySizeInBytes() {
return builder.getHeader().getKeySize()/8;
return getEncryptionInfo().getHeader().getKeySize()/8;
}
protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
final EncryptionInfo info = builder.getEncryptionInfo();
final StandardEncryptionHeader header = builder.getHeader();
final StandardEncryptionVerifier verifier = builder.getVerifier();
final EncryptionInfo info = getEncryptionInfo();
final StandardEncryptionHeader header = (StandardEncryptionHeader)info.getHeader();
final StandardEncryptionVerifier verifier = (StandardEncryptionVerifier)info.getVerifier();
EncryptionRecord er = new EncryptionRecord(){
@Override
public void write(LittleEndianByteArrayOutputStream bos) {
bos.writeShort(info.getVersionMajor());
bos.writeShort(info.getVersionMinor());
@ -222,4 +225,9 @@ public class StandardEncryptor extends Encryptor {
// TODO: any properties???
}
@Override
public StandardEncryptor clone() throws CloneNotSupportedException {
return (StandardEncryptor)super.clone();
}
}

View File

@ -17,103 +17,91 @@
package org.apache.poi.util;
import java.io.ByteArrayInputStream;
/**
* Adapts a plain byte array to {@link LittleEndianInput}
*
* @author Josh Micich
*/
public final class LittleEndianByteArrayInputStream implements LittleEndianInput {
private final byte[] _buf;
private final int _endIndex;
private int _readIndex;
public final class LittleEndianByteArrayInputStream extends ByteArrayInputStream implements LittleEndianInput {
public LittleEndianByteArrayInputStream(byte[] buf, int startOffset, int maxReadLen) { // NOSONAR
_buf = buf;
_readIndex = startOffset;
_endIndex = startOffset + maxReadLen;
super(buf, startOffset, maxReadLen);
}
public LittleEndianByteArrayInputStream(byte[] buf, int startOffset) {
this(buf, startOffset, buf.length - startOffset);
super(buf, startOffset, buf.length - startOffset);
}
public LittleEndianByteArrayInputStream(byte[] buf) {
this(buf, 0, buf.length);
super(buf);
}
public int available() {
return _endIndex - _readIndex;
}
private void checkPosition(int i) {
if (i > _endIndex - _readIndex) {
if (i > count - pos) {
throw new RuntimeException("Buffer overrun");
}
}
public int getReadIndex() {
return _readIndex;
return pos;
}
public byte readByte() {
@Override
public byte readByte() {
checkPosition(1);
return _buf[_readIndex++];
return (byte)read();
}
public int readInt() {
checkPosition(4);
int i = _readIndex;
int b0 = _buf[i++] & 0xFF;
int b1 = _buf[i++] & 0xFF;
int b2 = _buf[i++] & 0xFF;
int b3 = _buf[i++] & 0xFF;
_readIndex = i;
return (b3 << 24) + (b2 << 16) + (b1 << 8) + (b0 << 0);
@Override
public int readInt() {
final int size = LittleEndianConsts.INT_SIZE;
checkPosition(size);
int le = LittleEndian.getInt(buf, pos);
super.skip(size);
return le;
}
public long readLong() {
checkPosition(8);
int i = _readIndex;
int b0 = _buf[i++] & 0xFF;
int b1 = _buf[i++] & 0xFF;
int b2 = _buf[i++] & 0xFF;
int b3 = _buf[i++] & 0xFF;
int b4 = _buf[i++] & 0xFF;
int b5 = _buf[i++] & 0xFF;
int b6 = _buf[i++] & 0xFF;
int b7 = _buf[i++] & 0xFF;
_readIndex = i;
return (((long)b7 << 56) +
((long)b6 << 48) +
((long)b5 << 40) +
((long)b4 << 32) +
((long)b3 << 24) +
(b2 << 16) +
(b1 << 8) +
(b0 << 0));
@Override
public long readLong() {
final int size = LittleEndianConsts.LONG_SIZE;
checkPosition(size);
long le = LittleEndian.getLong(buf, pos);
super.skip(size);
return le;
}
public short readShort() {
@Override
public short readShort() {
return (short)readUShort();
}
public int readUByte() {
checkPosition(1);
return _buf[_readIndex++] & 0xFF;
}
public int readUShort() {
checkPosition(2);
int i = _readIndex;
int b0 = _buf[i++] & 0xFF;
int b1 = _buf[i++] & 0xFF;
_readIndex = i;
return (b1 << 8) + (b0 << 0);
@Override
public int readUByte() {
return readByte() & 0xFF;
}
public void readFully(byte[] buf, int off, int len) {
@Override
public int readUShort() {
final int size = LittleEndianConsts.SHORT_SIZE;
checkPosition(size);
int le = LittleEndian.getUShort(buf, pos);
super.skip(size);
return le;
}
@Override
public double readDouble() {
return Double.longBitsToDouble(readLong());
}
@Override
public void readFully(byte[] buffer, int off, int len) {
checkPosition(len);
System.arraycopy(_buf, _readIndex, buf, off, len);
_readIndex+=len;
read(buffer, off, len);
}
public void readFully(byte[] buf) {
readFully(buf, 0, buf.length);
}
public double readDouble() {
return Double.longBitsToDouble(readLong());
@Override
public void readFully(byte[] buffer) {
checkPosition(buffer.length);
read(buffer, 0, buffer.length);
}
}

View File

@ -17,28 +17,26 @@
package org.apache.poi.util;
import java.io.OutputStream;
/**
* Adapts a plain byte array to {@link LittleEndianOutput}
*
*
* @author Josh Micich
* Adapts a plain byte array to {@link LittleEndianOutput}
*/
public final class LittleEndianByteArrayOutputStream implements LittleEndianOutput, DelayableLittleEndianOutput {
public final class LittleEndianByteArrayOutputStream extends OutputStream implements LittleEndianOutput, DelayableLittleEndianOutput {
private final byte[] _buf;
private final int _endIndex;
private int _writeIndex;
public LittleEndianByteArrayOutputStream(byte[] buf, int startOffset, int maxWriteLen) { // NOSONAR
if (startOffset < 0 || startOffset > buf.length) {
throw new IllegalArgumentException("Specified startOffset (" + startOffset
throw new IllegalArgumentException("Specified startOffset (" + startOffset
+ ") is out of allowable range (0.." + buf.length + ")");
}
_buf = buf;
_writeIndex = startOffset;
_endIndex = startOffset + maxWriteLen;
if (_endIndex < startOffset || _endIndex > buf.length) {
throw new IllegalArgumentException("calculated end index (" + _endIndex
throw new IllegalArgumentException("calculated end index (" + _endIndex
+ ") is out of allowable range (" + _writeIndex + ".." + buf.length + ")");
}
}
@ -52,16 +50,19 @@ public final class LittleEndianByteArrayOutputStream implements LittleEndianOutp
}
}
public void writeByte(int v) {
@Override
public void writeByte(int v) {
checkPosition(1);
_buf[_writeIndex++] = (byte)v;
}
public void writeDouble(double v) {
@Override
public void writeDouble(double v) {
writeLong(Double.doubleToLongBits(v));
}
public void writeInt(int v) {
@Override
public void writeInt(int v) {
checkPosition(4);
int i = _writeIndex;
_buf[i++] = (byte)((v >>> 0) & 0xFF);
@ -71,33 +72,47 @@ public final class LittleEndianByteArrayOutputStream implements LittleEndianOutp
_writeIndex = i;
}
public void writeLong(long v) {
@Override
public void writeLong(long v) {
writeInt((int)(v >> 0));
writeInt((int)(v >> 32));
}
public void writeShort(int v) {
@Override
public void writeShort(int v) {
checkPosition(2);
int i = _writeIndex;
_buf[i++] = (byte)((v >>> 0) & 0xFF);
_buf[i++] = (byte)((v >>> 8) & 0xFF);
_writeIndex = i;
}
public void write(byte[] b) {
@Override
public void write(int b) {
writeByte(b);
}
@Override
public void write(byte[] b) {
int len = b.length;
checkPosition(len);
System.arraycopy(b, 0, _buf, _writeIndex, len);
_writeIndex += len;
}
public void write(byte[] b, int offset, int len) {
@Override
public void write(byte[] b, int offset, int len) {
checkPosition(len);
System.arraycopy(b, offset, _buf, _writeIndex, len);
_writeIndex += len;
}
public int getWriteIndex() {
return _writeIndex;
}
public LittleEndianOutput createDelayedOutput(int size) {
@Override
public LittleEndianOutput createDelayedOutput(int size) {
checkPosition(size);
LittleEndianOutput result = new LittleEndianByteArrayOutputStream(_buf, _writeIndex, size);
_writeIndex += size;

View File

@ -45,7 +45,7 @@ import org.apache.poi.poifs.crypt.CipherAlgorithm;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionHeader;
import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.EncryptionVerifier;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier.AgileCertificateEntry;
@ -56,14 +56,14 @@ import org.apache.poi.util.LittleEndian;
/**
* Decryptor implementation for Agile Encryption
*/
public class AgileDecryptor extends Decryptor {
public class AgileDecryptor extends Decryptor implements Cloneable {
private long _length = -1;
protected static final byte[] kVerifierInputBlock;
protected static final byte[] kHashedVerifierBlock;
protected static final byte[] kCryptoKeyBlock;
protected static final byte[] kIntegrityKeyBlock;
protected static final byte[] kIntegrityValueBlock;
/* package */ static final byte[] kVerifierInputBlock;
/* package */ static final byte[] kHashedVerifierBlock;
/* package */ static final byte[] kCryptoKeyBlock;
/* package */ static final byte[] kIntegrityKeyBlock;
/* package */ static final byte[] kIntegrityValueBlock;
static {
kVerifierInputBlock =
@ -83,16 +83,16 @@ public class AgileDecryptor extends Decryptor {
(byte)0xb2, (byte)0x2c, (byte)0x84, (byte)0x33 };
}
protected AgileDecryptor(AgileEncryptionInfoBuilder builder) {
super(builder);
protected AgileDecryptor() {
}
/**
* set decryption password
*/
@Override
public boolean verifyPassword(String password) throws GeneralSecurityException {
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)builder.getVerifier();
AgileEncryptionHeader header = (AgileEncryptionHeader)builder.getHeader();
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
int blockSize = header.getBlockSize();
@ -113,7 +113,7 @@ public class AgileDecryptor extends Decryptor {
* blockSize bytes.
* 4. Use base64 to encode the result of step 3.
*/
byte verfierInputEnc[] = hashInput(builder, pwHash, kVerifierInputBlock, ver.getEncryptedVerifier(), Cipher.DECRYPT_MODE);
byte verfierInputEnc[] = hashInput(getEncryptionInfo(), pwHash, kVerifierInputBlock, ver.getEncryptedVerifier(), Cipher.DECRYPT_MODE);
setVerifier(verfierInputEnc);
MessageDigest hashMD = getMessageDigest(hashAlgo);
byte[] verifierHash = hashMD.digest(verfierInputEnc);
@ -130,7 +130,7 @@ public class AgileDecryptor extends Decryptor {
* blockSize bytes, pad the hash value with 0x00 to an integral multiple of blockSize bytes.
* 4. Use base64 to encode the result of step 3.
*/
byte verifierHashDec[] = hashInput(builder, pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE);
byte verifierHashDec[] = hashInput(getEncryptionInfo(), pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE);
verifierHashDec = getBlock0(verifierHashDec, hashAlgo.hashSize);
/**
@ -146,7 +146,7 @@ public class AgileDecryptor extends Decryptor {
* blockSize bytes.
* 4. Use base64 to encode the result of step 3.
*/
byte keyspec[] = hashInput(builder, pwHash, kCryptoKeyBlock, ver.getEncryptedKey(), Cipher.DECRYPT_MODE);
byte keyspec[] = hashInput(getEncryptionInfo(), pwHash, kCryptoKeyBlock, ver.getEncryptedKey(), Cipher.DECRYPT_MODE);
keyspec = getBlock0(keyspec, keySize);
SecretKeySpec secretKey = new SecretKeySpec(keyspec, ver.getCipherAlgorithm().jceId);
@ -204,8 +204,8 @@ public class AgileDecryptor extends Decryptor {
* @throws GeneralSecurityException
*/
public boolean verifyPassword(KeyPair keyPair, X509Certificate x509) throws GeneralSecurityException {
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)builder.getVerifier();
AgileEncryptionHeader header = (AgileEncryptionHeader)builder.getHeader();
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
int blockSize = header.getBlockSize();
@ -217,7 +217,9 @@ public class AgileDecryptor extends Decryptor {
break;
}
}
if (ace == null) return false;
if (ace == null) {
return false;
}
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
@ -255,9 +257,9 @@ public class AgileDecryptor extends Decryptor {
return fillSize;
}
protected static byte[] hashInput(EncryptionInfoBuilder builder, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) {
EncryptionVerifier ver = builder.getVerifier();
AgileDecryptor dec = (AgileDecryptor)builder.getDecryptor();
protected static byte[] hashInput(EncryptionInfo encryptionInfo, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) {
EncryptionVerifier ver = encryptionInfo.getVerifier();
AgileDecryptor dec = (AgileDecryptor)encryptionInfo.getDecryptor();
int keySize = dec.getKeySizeInBytes();
int blockSize = dec.getBlockSizeInBytes();
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
@ -278,6 +280,7 @@ public class AgileDecryptor extends Decryptor {
}
}
@Override
@SuppressWarnings("resource")
public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
@ -285,17 +288,20 @@ public class AgileDecryptor extends Decryptor {
return new AgileCipherInputStream(dis, _length);
}
@Override
public long getLength(){
if(_length == -1) throw new IllegalStateException("EcmaDecryptor.getDataStream() was not called");
if(_length == -1) {
throw new IllegalStateException("EcmaDecryptor.getDataStream() was not called");
}
return _length;
}
protected static Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk, EncryptionInfoBuilder builder, SecretKey skey, int encryptionMode)
protected static Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk, EncryptionInfo encryptionInfo, SecretKey skey, int encryptionMode)
throws GeneralSecurityException {
EncryptionHeader header = builder.getHeader();
if (existing == null || lastChunk) {
String padding = (lastChunk ? "PKCS5Padding" : "NoPadding");
EncryptionHeader header = encryptionInfo.getHeader();
String padding = (lastChunk ? "PKCS5Padding" : "NoPadding");
if (existing == null || !existing.getAlgorithm().endsWith(padding)) {
existing = getCipher(skey, header.getCipherAlgorithm(), header.getChainingMode(), header.getKeySalt(), encryptionMode, padding);
}
@ -339,9 +345,15 @@ public class AgileDecryptor extends Decryptor {
// TODO: calculate integrity hmac while reading the stream
// for a post-validation of the data
@Override
protected Cipher initCipherForBlock(Cipher cipher, int block)
throws GeneralSecurityException {
return AgileDecryptor.initCipherForBlock(cipher, block, false, builder, getSecretKey(), Cipher.DECRYPT_MODE);
return AgileDecryptor.initCipherForBlock(cipher, block, false, getEncryptionInfo(), getSecretKey(), Cipher.DECRYPT_MODE);
}
}
@Override
public AgileDecryptor clone() throws CloneNotSupportedException {
return (AgileDecryptor)super.clone();
}
}

View File

@ -27,7 +27,7 @@ import com.microsoft.schemas.office.x2006.encryption.CTKeyData;
import com.microsoft.schemas.office.x2006.encryption.EncryptionDocument;
import com.microsoft.schemas.office.x2006.encryption.STCipherChaining;
public class AgileEncryptionHeader extends EncryptionHeader {
public class AgileEncryptionHeader extends EncryptionHeader implements Cloneable {
private byte encryptedHmacKey[], encryptedHmacValue[];
public AgileEncryptionHeader(String descriptor) {
@ -99,6 +99,7 @@ public class AgileEncryptionHeader extends EncryptionHeader {
}
// make method visible for this package
@Override
protected void setKeySalt(byte salt[]) {
if (salt == null || salt.length != getBlockSize()) {
throw new EncryptedDocumentException("invalid verifier salt");
@ -121,4 +122,13 @@ public class AgileEncryptionHeader extends EncryptionHeader {
protected void setEncryptedHmacValue(byte[] encryptedHmacValue) {
this.encryptedHmacValue = (encryptedHmacValue == null) ? null : encryptedHmacValue.clone();
}
@Override
public AgileEncryptionHeader clone() throws CloneNotSupportedException {
AgileEncryptionHeader other = (AgileEncryptionHeader)super.clone();
other.encryptedHmacKey = (encryptedHmacKey == null) ? null : encryptedHmacKey.clone();
other.encryptedHmacValue = (encryptedHmacValue == null) ? null : encryptedHmacValue.clone();
return other;
}
}

View File

@ -35,30 +35,24 @@ import com.microsoft.schemas.office.x2006.encryption.EncryptionDocument;
public class AgileEncryptionInfoBuilder implements EncryptionInfoBuilder {
EncryptionInfo info;
AgileEncryptionHeader header;
AgileEncryptionVerifier verifier;
AgileDecryptor decryptor;
AgileEncryptor encryptor;
@Override
public void initialize(EncryptionInfo ei, LittleEndianInput dis) throws IOException {
this.info = ei;
public void initialize(EncryptionInfo info, LittleEndianInput dis) throws IOException {
EncryptionDocument ed = parseDescriptor((InputStream)dis);
header = new AgileEncryptionHeader(ed);
verifier = new AgileEncryptionVerifier(ed);
if (ei.getVersionMajor() == EncryptionMode.agile.versionMajor
&& ei.getVersionMinor() == EncryptionMode.agile.versionMinor) {
decryptor = new AgileDecryptor(this);
encryptor = new AgileEncryptor(this);
info.setHeader(new AgileEncryptionHeader(ed));
info.setVerifier(new AgileEncryptionVerifier(ed));
if (info.getVersionMajor() == EncryptionMode.agile.versionMajor
&& info.getVersionMinor() == EncryptionMode.agile.versionMinor) {
AgileDecryptor dec = new AgileDecryptor();
dec.setEncryptionInfo(info);
info.setDecryptor(dec);
AgileEncryptor enc = new AgileEncryptor();
enc.setEncryptionInfo(info);
info.setEncryptor(enc);
}
}
@Override
public void initialize(EncryptionInfo ei, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
this.info = ei;
public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
if (cipherAlgorithm == null) {
cipherAlgorithm = CipherAlgorithm.aes128;
}
@ -87,30 +81,14 @@ public class AgileEncryptionInfoBuilder implements EncryptionInfoBuilder {
if (!found) {
throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for Cipher "+cipherAlgorithm.toString());
}
header = new AgileEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
verifier = new AgileEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode);
decryptor = new AgileDecryptor(this);
encryptor = new AgileEncryptor(this);
}
public AgileEncryptionHeader getHeader() {
return header;
}
public AgileEncryptionVerifier getVerifier() {
return verifier;
}
public AgileDecryptor getDecryptor() {
return decryptor;
}
public AgileEncryptor getEncryptor() {
return encryptor;
}
protected EncryptionInfo getInfo() {
return info;
info.setHeader(new AgileEncryptionHeader(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
info.setVerifier(new AgileEncryptionVerifier(cipherAlgorithm, hashAlgorithm, keyBits, blockSize, chainingMode));
AgileDecryptor dec = new AgileDecryptor();
dec.setEncryptionInfo(info);
info.setDecryptor(dec);
AgileEncryptor enc = new AgileEncryptor();
enc.setEncryptionInfo(info);
info.setEncryptor(enc);
}
protected static EncryptionDocument parseDescriptor(String descriptor) {

View File

@ -39,7 +39,7 @@ import com.microsoft.schemas.office.x2006.keyEncryptor.password.CTPasswordKeyEnc
/**
* Used when checking if a key is valid for a document
*/
public class AgileEncryptionVerifier extends EncryptionVerifier {
public class AgileEncryptionVerifier extends EncryptionVerifier implements Cloneable {
public static class AgileCertificateEntry {
X509Certificate x509;
@ -87,8 +87,9 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
setEncryptedVerifierHash(keyData.getEncryptedVerifierHashValue());
int saltSize = keyData.getSaltSize();
if (saltSize != getSalt().length)
if (saltSize != getSalt().length) {
throw new EncryptedDocumentException("Invalid salt size");
}
switch (keyData.getCipherChaining().intValue()) {
case STCipherChaining.INT_CHAINING_MODE_CBC:
@ -101,7 +102,9 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
throw new EncryptedDocumentException("Unsupported chaining mode - "+keyData.getCipherChaining().toString());
}
if (!encList.hasNext()) return;
if (!encList.hasNext()) {
return;
}
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
@ -125,6 +128,7 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
setSpinCount(100000); // TODO: use parameter
}
@Override
protected void setSalt(byte salt[]) {
if (salt == null || salt.length != getCipherAlgorithm().blockSize) {
throw new EncryptedDocumentException("invalid verifier salt");
@ -133,16 +137,19 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
}
// make method visible for this package
@Override
protected void setEncryptedVerifier(byte encryptedVerifier[]) {
super.setEncryptedVerifier(encryptedVerifier);
}
// make method visible for this package
@Override
protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
super.setEncryptedVerifierHash(encryptedVerifierHash);
}
// make method visible for this package
@Override
protected void setEncryptedKey(byte[] encryptedKey) {
super.setEncryptedKey(encryptedKey);
}
@ -156,4 +163,12 @@ public class AgileEncryptionVerifier extends EncryptionVerifier {
public List<AgileCertificateEntry> getCertificates() {
return certList;
}
@Override
public AgileEncryptionVerifier clone() throws CloneNotSupportedException {
AgileEncryptionVerifier other = (AgileEncryptionVerifier)super.clone();
// TODO: deep copy of certList
other.certList = new ArrayList<AgileCertificateEntry>(certList);
return other;
}
}

View File

@ -60,6 +60,7 @@ import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.xmlbeans.XmlOptions;
import com.microsoft.schemas.office.x2006.encryption.CTDataIntegrity;
@ -74,21 +75,20 @@ import com.microsoft.schemas.office.x2006.encryption.STHashAlgorithm;
import com.microsoft.schemas.office.x2006.keyEncryptor.certificate.CTCertificateKeyEncryptor;
import com.microsoft.schemas.office.x2006.keyEncryptor.password.CTPasswordKeyEncryptor;
public class AgileEncryptor extends Encryptor {
private final AgileEncryptionInfoBuilder builder;
public class AgileEncryptor extends Encryptor implements Cloneable {
private byte integritySalt[];
private byte pwHash[];
protected AgileEncryptor(AgileEncryptionInfoBuilder builder) {
this.builder = builder;
protected AgileEncryptor() {
}
@Override
public void confirmPassword(String password) {
// see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier
Random r = new SecureRandom();
int blockSize = builder.getHeader().getBlockSize();
int keySize = builder.getHeader().getKeySize()/8;
int hashSize = builder.getHeader().getHashAlgorithmEx().hashSize;
int blockSize = getEncryptionInfo().getHeader().getBlockSize();
int keySize = getEncryptionInfo().getHeader().getKeySize()/8;
int hashSize = getEncryptionInfo().getHeader().getHashAlgorithmEx().hashSize;
byte[] newVerifierSalt = new byte[blockSize]
, newVerifier = new byte[blockSize]
@ -104,10 +104,11 @@ public class AgileEncryptor extends Encryptor {
confirmPassword(password, newKeySpec, newKeySalt, newVerifierSalt, newVerifier, newIntegritySalt);
}
public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) {
AgileEncryptionVerifier ver = builder.getVerifier();
@Override
public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) {
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
ver.setSalt(verifierSalt);
AgileEncryptionHeader header = builder.getHeader();
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
header.setKeySalt(keySalt);
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
@ -128,7 +129,7 @@ public class AgileEncryptor extends Encryptor {
* blockSize bytes.
* 4. Use base64 to encode the result of step 3.
*/
byte encryptedVerifier[] = hashInput(builder, pwHash, kVerifierInputBlock, verifier, Cipher.ENCRYPT_MODE);
byte encryptedVerifier[] = hashInput(getEncryptionInfo(), pwHash, kVerifierInputBlock, verifier, Cipher.ENCRYPT_MODE);
ver.setEncryptedVerifier(encryptedVerifier);
@ -146,7 +147,7 @@ public class AgileEncryptor extends Encryptor {
*/
MessageDigest hashMD = getMessageDigest(hashAlgo);
byte[] hashedVerifier = hashMD.digest(verifier);
byte encryptedVerifierHash[] = hashInput(builder, pwHash, kHashedVerifierBlock, hashedVerifier, Cipher.ENCRYPT_MODE);
byte encryptedVerifierHash[] = hashInput(getEncryptionInfo(), pwHash, kHashedVerifierBlock, hashedVerifier, Cipher.ENCRYPT_MODE);
ver.setEncryptedVerifierHash(encryptedVerifierHash);
/**
@ -162,7 +163,7 @@ public class AgileEncryptor extends Encryptor {
* blockSize bytes.
* 4. Use base64 to encode the result of step 3.
*/
byte encryptedKey[] = hashInput(builder, pwHash, kCryptoKeyBlock, keySpec, Cipher.ENCRYPT_MODE);
byte encryptedKey[] = hashInput(getEncryptionInfo(), pwHash, kCryptoKeyBlock, keySpec, Cipher.ENCRYPT_MODE);
ver.setEncryptedKey(encryptedKey);
SecretKey secretKey = new SecretKeySpec(keySpec, ver.getCipherAlgorithm().jceId);
@ -214,6 +215,7 @@ public class AgileEncryptor extends Encryptor {
}
}
@Override
public OutputStream getDataStream(DirectoryNode dir)
throws IOException, GeneralSecurityException {
// TODO: initialize headers
@ -234,14 +236,14 @@ public class AgileEncryptor extends Encryptor {
// as the integrity hmac needs to contain the StreamSize,
// it's not possible to calculate it on-the-fly while buffering
// TODO: add stream size parameter to getDataStream()
AgileEncryptionVerifier ver = builder.getVerifier();
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
Mac integrityMD = CryptoFunctions.getMac(hashAlgo);
integrityMD.init(new SecretKeySpec(integritySalt, hashAlgo.jceHmacId));
byte buf[] = new byte[1024];
LittleEndian.putLong(buf, 0, oleStreamSize);
integrityMD.update(buf, 0, LittleEndian.LONG_SIZE);
integrityMD.update(buf, 0, LittleEndianConsts.LONG_SIZE);
InputStream fis = new FileInputStream(tmpFile);
try {
@ -255,7 +257,7 @@ public class AgileEncryptor extends Encryptor {
byte hmacValue[] = integrityMD.doFinal();
AgileEncryptionHeader header = builder.getHeader();
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
int blockSize = header.getBlockSize();
byte iv[] = CryptoFunctions.generateIv(header.getHashAlgorithmEx(), header.getKeySalt(), kIntegrityValueBlock, blockSize);
Cipher cipher = CryptoFunctions.getCipher(getSecretKey(), header.getCipherAlgorithm(), header.getChainingMode(), iv, Cipher.ENCRYPT_MODE);
@ -271,8 +273,8 @@ public class AgileEncryptor extends Encryptor {
CTKeyEncryptor.Uri.HTTP_SCHEMAS_MICROSOFT_COM_OFFICE_2006_KEY_ENCRYPTOR_CERTIFICATE;
protected EncryptionDocument createEncryptionDocument() {
AgileEncryptionVerifier ver = builder.getVerifier();
AgileEncryptionHeader header = builder.getHeader();
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
EncryptionDocument ed = EncryptionDocument.Factory.newInstance();
CTEncryption edRoot = ed.addNewEncryption();
@ -379,9 +381,10 @@ public class AgileEncryptor extends Encryptor {
throws IOException, GeneralSecurityException {
DataSpaceMapUtils.addDefaultDataSpace(dir);
final EncryptionInfo info = builder.getInfo();
final EncryptionInfo info = getEncryptionInfo();
EncryptionRecord er = new EncryptionRecord(){
@Override
public void write(LittleEndianByteArrayOutputStream bos) {
// EncryptionVersionInfo (4 bytes): A Version structure (section 2.1.4), where
// Version.vMajor MUST be 0x0004 and Version.vMinor MUST be 0x0004
@ -422,7 +425,7 @@ public class AgileEncryptor extends Encryptor {
@Override
protected Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk)
throws GeneralSecurityException {
return AgileDecryptor.initCipherForBlock(existing, block, lastChunk, builder, getSecretKey(), Cipher.ENCRYPT_MODE);
return AgileDecryptor.initCipherForBlock(existing, block, lastChunk, getEncryptionInfo(), getSecretKey(), Cipher.ENCRYPT_MODE);
}
@Override
@ -439,4 +442,11 @@ public class AgileEncryptor extends Encryptor {
}
}
@Override
public AgileEncryptor clone() throws CloneNotSupportedException {
AgileEncryptor other = (AgileEncryptor)super.clone();
other.integritySalt = (integritySalt == null) ? null : integritySalt.clone();
other.pwHash = (pwHash == null) ? null : pwHash.clone();
return other;
}
}

View File

@ -16,8 +16,7 @@
==================================================================== */
package org.apache.poi.poifs.crypt;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
@ -94,6 +93,7 @@ public class TestAgileEncryptionParameters {
os.close();
bos.reset();
fsEnc.writeFilesystem(bos);
fsEnc.close();
POIFSFileSystem fsDec = new POIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()));
EncryptionInfo infoDec = new EncryptionInfo(fsDec);
@ -103,6 +103,7 @@ public class TestAgileEncryptionParameters {
InputStream is = dec.getDataStream(fsDec);
byte actualData[] = IOUtils.toByteArray(is);
is.close();
assertThat("Failed roundtrip - "+ca+"-"+ha+"-"+cm, testData, equalTo(actualData));
fsDec.close();
assertArrayEquals("Failed roundtrip - "+ca+"-"+ha+"-"+cm, testData, actualData);
}
}

View File

@ -17,6 +17,8 @@
package org.apache.poi.hslf.usermodel;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
@ -25,9 +27,6 @@ import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException;
import org.apache.poi.hslf.record.DocumentEncryptionAtom;
@ -36,26 +35,45 @@ import org.apache.poi.hslf.record.PositionDependentRecord;
import org.apache.poi.hslf.record.Record;
import org.apache.poi.hslf.record.UserEditAtom;
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
import org.apache.poi.poifs.crypt.ChunkedCipherInputStream;
import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor;
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor;
import org.apache.poi.util.BitField;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianByteArrayInputStream;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
/**
* This class provides helper functions for encrypted PowerPoint documents.
*/
@Internal
public class HSLFSlideShowEncrypted {
public class HSLFSlideShowEncrypted implements Closeable {
DocumentEncryptionAtom dea;
CryptoAPIEncryptor enc = null;
CryptoAPIDecryptor dec = null;
Cipher cipher = null;
CipherOutputStream cyos = null;
// Cipher cipher = null;
ChunkedCipherOutputStream cyos = null;
private static final BitField fieldRecInst = new BitField(0xFFF0);
private static final int BLIB_STORE_ENTRY_PARTS[] = {
1, // btWin32
1, // btMacOS
16, // rgbUid
2, // tag
4, // size
4, // cRef
4, // foDelay
1, // unused1
1, // cbName (@ index 33)
1, // unused2
1, // unused3
};
protected HSLFSlideShowEncrypted(DocumentEncryptionAtom dea) {
this.dea = dea;
@ -67,7 +85,9 @@ public class HSLFSlideShowEncrypted {
UserEditAtom userEditAtomWithEncryption = null;
for (Map.Entry<Integer, Record> me : recordMap.descendingMap().entrySet()) {
Record r = me.getValue();
if (!(r instanceof UserEditAtom)) continue;
if (!(r instanceof UserEditAtom)) {
continue;
}
UserEditAtom uea = (UserEditAtom)r;
if (uea.getEncryptSessionPersistIdRef() != -1) {
userEditAtomWithEncryption = uea;
@ -83,7 +103,7 @@ public class HSLFSlideShowEncrypted {
Record r = recordMap.get(userEditAtomWithEncryption.getPersistPointersOffset());
assert(r instanceof PersistPtrHolder);
PersistPtrHolder ptr = (PersistPtrHolder)r;
Integer encOffset = ptr.getSlideLocationsLookup().get(userEditAtomWithEncryption.getEncryptSessionPersistIdRef());
if (encOffset == null) {
// encryption info doesn't exist anymore
@ -91,7 +111,7 @@ public class HSLFSlideShowEncrypted {
dea = null;
return;
}
r = recordMap.get(encOffset);
if (r == null) {
r = Record.buildRecordAtOffset(docstream, encOffset);
@ -100,7 +120,7 @@ public class HSLFSlideShowEncrypted {
assert(r instanceof DocumentEncryptionAtom);
this.dea = (DocumentEncryptionAtom)r;
decryptInit();
String pass = Biff8EncryptionKey.getCurrentUserPassword();
if(!dec.verifyPassword(pass != null ? pass : Decryptor.DEFAULT_PASSWORD)) {
throw new EncryptedPowerPointFileException("PowerPoint file is encrypted. The correct password needs to be set via Biff8EncryptionKey.setCurrentUserPassword()");
@ -110,119 +130,144 @@ public class HSLFSlideShowEncrypted {
public DocumentEncryptionAtom getDocumentEncryptionAtom() {
return dea;
}
protected void setPersistId(int persistId) {
if (enc != null && dec != null) {
throw new EncryptedPowerPointFileException("Use instance either for en- or decryption");
}
try {
if (enc != null) cipher = enc.initCipherForBlock(cipher, persistId);
if (dec != null) cipher = dec.initCipherForBlock(cipher, persistId);
} catch (GeneralSecurityException e) {
throw new EncryptedPowerPointFileException(e);
}
}
protected void decryptInit() {
if (dec != null) return;
if (dec != null) {
return;
}
EncryptionInfo ei = dea.getEncryptionInfo();
dec = (CryptoAPIDecryptor)ei.getDecryptor();
}
protected void encryptInit() {
if (enc != null) return;
if (enc != null) {
return;
}
EncryptionInfo ei = dea.getEncryptionInfo();
enc = (CryptoAPIEncryptor)ei.getEncryptor();
}
protected OutputStream encryptRecord(OutputStream plainStream, int persistId, Record record) {
boolean isPlain = (dea == null
boolean isPlain = (dea == null
|| record instanceof UserEditAtom
|| record instanceof PersistPtrHolder
|| record instanceof DocumentEncryptionAtom
);
if (isPlain) return plainStream;
encryptInit();
setPersistId(persistId);
if (cyos == null) {
cyos = new CipherOutputStream(plainStream, cipher);
try {
if (isPlain) {
if (cyos != null) {
// write cached data to stream
cyos.flush();
}
return plainStream;
}
encryptInit();
if (cyos == null) {
enc.setChunkSize(-1);
cyos = enc.getDataStream(plainStream);
}
cyos.initCipherForBlock(persistId, false);
} catch (Exception e) {
throw new EncryptedPowerPointFileException(e);
}
return cyos;
}
private static void readFully(ChunkedCipherInputStream ccis, byte[] docstream, int offset, int len) throws IOException {
if (IOUtils.readFully(ccis, docstream, offset, len) == -1) {
throw new EncryptedPowerPointFileException("unexpected EOF");
}
}
protected void decryptRecord(byte[] docstream, int persistId, int offset) {
if (dea == null) return;
if (dea == null) {
return;
}
decryptInit();
setPersistId(persistId);
dec.setChunkSize(-1);
LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(docstream, offset);
ChunkedCipherInputStream ccis = null;
try {
ccis = dec.getDataStream(lei, docstream.length-offset, 0);
ccis.initCipherForBlock(persistId);
// decrypt header and read length to be decrypted
cipher.update(docstream, offset, 8, docstream, offset);
readFully(ccis, docstream, offset, 8);
// decrypt the rest of the record
int rlen = (int)LittleEndian.getUInt(docstream, offset+4);
cipher.update(docstream, offset+8, rlen, docstream, offset+8);
} catch (GeneralSecurityException e) {
throw new CorruptPowerPointFileException(e);
}
}
readFully(ccis, docstream, offset+8, rlen);
} catch (Exception e) {
throw new EncryptedPowerPointFileException(e);
} finally {
try {
if (ccis != null) {
ccis.close();
}
lei.close();
} catch (IOException e) {
throw new EncryptedPowerPointFileException(e);
}
}
}
private void decryptPicBytes(byte[] pictstream, int offset, int len)
throws IOException, GeneralSecurityException {
// when reading the picture elements, each time a segment is read, the cipher needs
// to be reset (usually done when calling Cipher.doFinal)
LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(pictstream, offset);
ChunkedCipherInputStream ccis = dec.getDataStream(lei, len, 0);
readFully(ccis, pictstream, offset, len);
ccis.close();
lei.close();
}
protected void decryptPicture(byte[] pictstream, int offset) {
if (dea == null) return;
if (dea == null) {
return;
}
decryptInit();
setPersistId(0);
try {
// decrypt header and read length to be decrypted
cipher.doFinal(pictstream, offset, 8, pictstream, offset);
decryptPicBytes(pictstream, offset, 8);
int recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
int recType = LittleEndian.getUShort(pictstream, offset+2);
int rlen = (int)LittleEndian.getUInt(pictstream, offset+4);
offset += 8;
int endOffset = offset + rlen;
int endOffset = offset + rlen;
if (recType == 0xF007) {
// TOOD: get a real example file ... to actual test the FBSE entry
// not sure where the foDelay block is
// File BLIP Store Entry (FBSE)
cipher.doFinal(pictstream, offset, 1, pictstream, offset); // btWin32
offset++;
cipher.doFinal(pictstream, offset, 1, pictstream, offset); // btMacOS
offset++;
cipher.doFinal(pictstream, offset, 16, pictstream, offset); // rgbUid
offset += 16;
cipher.doFinal(pictstream, offset, 2, pictstream, offset); // tag
offset += 2;
cipher.doFinal(pictstream, offset, 4, pictstream, offset); // size
offset += 4;
cipher.doFinal(pictstream, offset, 4, pictstream, offset); // cRef
offset += 4;
cipher.doFinal(pictstream, offset, 4, pictstream, offset); // foDelay
offset += 4;
cipher.doFinal(pictstream, offset+0, 1, pictstream, offset+0); // unused1
cipher.doFinal(pictstream, offset+1, 1, pictstream, offset+1); // cbName
cipher.doFinal(pictstream, offset+2, 1, pictstream, offset+2); // unused2
cipher.doFinal(pictstream, offset+3, 1, pictstream, offset+3); // unused3
int cbName = LittleEndian.getUShort(pictstream, offset+1);
offset += 4;
for (int part : BLIB_STORE_ENTRY_PARTS) {
decryptPicBytes(pictstream, offset, part);
}
offset += 36;
int cbName = LittleEndian.getUShort(pictstream, offset-3);
if (cbName > 0) {
cipher.doFinal(pictstream, offset, cbName, pictstream, offset); // nameData
// read nameData
decryptPicBytes(pictstream, offset, cbName);
offset += cbName;
}
if (offset == endOffset) {
return; // no embedded blip
}
// fall through, read embedded blip now
// update header data
cipher.doFinal(pictstream, offset, 8, pictstream, offset);
decryptPicBytes(pictstream, offset, 8);
recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
recType = LittleEndian.getUShort(pictstream, offset+2);
// rlen = (int)LittleEndian.getUInt(pictstream, offset+4);
@ -231,70 +276,73 @@ public class HSLFSlideShowEncrypted {
int rgbUidCnt = (recInst == 0x217 || recInst == 0x3D5 || recInst == 0x46B || recInst == 0x543 ||
recInst == 0x6E1 || recInst == 0x6E3 || recInst == 0x6E5 || recInst == 0x7A9) ? 2 : 1;
// rgbUid 1/2
for (int i=0; i<rgbUidCnt; i++) {
cipher.doFinal(pictstream, offset, 16, pictstream, offset); // rgbUid 1/2
decryptPicBytes(pictstream, offset, 16);
offset += 16;
}
int nextBytes;
if (recType == 0xF01A || recType == 0XF01B || recType == 0XF01C) {
cipher.doFinal(pictstream, offset, 34, pictstream, offset); // metafileHeader
offset += 34;
// metafileHeader
nextBytes = 34;
} else {
cipher.doFinal(pictstream, offset, 1, pictstream, offset); // tag
offset += 1;
// tag
nextBytes = 1;
}
decryptPicBytes(pictstream, offset, nextBytes);
offset += nextBytes;
int blipLen = endOffset - offset;
cipher.doFinal(pictstream, offset, blipLen, pictstream, offset);
} catch (GeneralSecurityException e) {
decryptPicBytes(pictstream, offset, blipLen);
} catch (Exception e) {
throw new CorruptPowerPointFileException(e);
}
}
}
protected void encryptPicture(byte[] pictstream, int offset) {
if (dea == null) return;
if (dea == null) {
return;
}
encryptInit();
setPersistId(0);
LittleEndianByteArrayOutputStream los = new LittleEndianByteArrayOutputStream(pictstream, offset);
ChunkedCipherOutputStream ccos = null;
try {
enc.setChunkSize(-1);
ccos = enc.getDataStream(los);
int recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
int recType = LittleEndian.getUShort(pictstream, offset+2);
int rlen = (int)LittleEndian.getUInt(pictstream, offset+4);
cipher.doFinal(pictstream, offset, 8, pictstream, offset);
final int rlen = (int)LittleEndian.getUInt(pictstream, offset+4);
ccos.write(pictstream, offset, 8);
ccos.flush();
offset += 8;
int endOffset = offset + rlen;
int endOffset = offset + rlen;
if (recType == 0xF007) {
// TOOD: get a real example file ... to actual test the FBSE entry
// not sure where the foDelay block is
// File BLIP Store Entry (FBSE)
cipher.doFinal(pictstream, offset, 1, pictstream, offset); // btWin32
offset++;
cipher.doFinal(pictstream, offset, 1, pictstream, offset); // btMacOS
offset++;
cipher.doFinal(pictstream, offset, 16, pictstream, offset); // rgbUid
offset += 16;
cipher.doFinal(pictstream, offset, 2, pictstream, offset); // tag
offset += 2;
cipher.doFinal(pictstream, offset, 4, pictstream, offset); // size
offset += 4;
cipher.doFinal(pictstream, offset, 4, pictstream, offset); // cRef
offset += 4;
cipher.doFinal(pictstream, offset, 4, pictstream, offset); // foDelay
offset += 4;
int cbName = LittleEndian.getUShort(pictstream, offset+1);
cipher.doFinal(pictstream, offset+0, 1, pictstream, offset+0); // unused1
cipher.doFinal(pictstream, offset+1, 1, pictstream, offset+1); // cbName
cipher.doFinal(pictstream, offset+2, 1, pictstream, offset+2); // unused2
cipher.doFinal(pictstream, offset+3, 1, pictstream, offset+3); // unused3
offset += 4;
int cbName = LittleEndian.getUShort(pictstream, offset+33);
for (int part : BLIB_STORE_ENTRY_PARTS) {
ccos.write(pictstream, offset, part);
ccos.flush();
offset += part;
}
if (cbName > 0) {
cipher.doFinal(pictstream, offset, cbName, pictstream, offset); // nameData
ccos.write(pictstream, offset, cbName);
ccos.flush();
offset += cbName;
}
if (offset == endOffset) {
return; // no embedded blip
}
@ -303,32 +351,45 @@ public class HSLFSlideShowEncrypted {
// update header data
recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
recType = LittleEndian.getUShort(pictstream, offset+2);
// rlen = (int) LittleEndian.getUInt(pictstream, offset+4);
cipher.doFinal(pictstream, offset, 8, pictstream, offset);
ccos.write(pictstream, offset, 8);
ccos.flush();
offset += 8;
}
int rgbUidCnt = (recInst == 0x217 || recInst == 0x3D5 || recInst == 0x46B || recInst == 0x543 ||
recInst == 0x6E1 || recInst == 0x6E3 || recInst == 0x6E5 || recInst == 0x7A9) ? 2 : 1;
for (int i=0; i<rgbUidCnt; i++) {
cipher.doFinal(pictstream, offset, 16, pictstream, offset); // rgbUid 1/2
ccos.write(pictstream, offset, 16); // rgbUid 1/2
ccos.flush();
offset += 16;
}
if (recType == 0xF01A || recType == 0XF01B || recType == 0XF01C) {
cipher.doFinal(pictstream, offset, 34, pictstream, offset); // metafileHeader
ccos.write(pictstream, offset, 34); // metafileHeader
offset += 34;
ccos.flush();
} else {
cipher.doFinal(pictstream, offset, 1, pictstream, offset); // tag
ccos.write(pictstream, offset, 1); // tag
offset += 1;
ccos.flush();
}
int blipLen = endOffset - offset;
cipher.doFinal(pictstream, offset, blipLen, pictstream, offset);
} catch (GeneralSecurityException e) {
throw new CorruptPowerPointFileException(e);
}
ccos.write(pictstream, offset, blipLen);
ccos.flush();
} catch (Exception e) {
throw new EncryptedPowerPointFileException(e);
} finally {
try {
if (ccos != null) {
ccos.close();
}
los.close();
} catch (IOException e) {
throw new EncryptedPowerPointFileException(e);
}
}
}
protected Record[] updateEncryptionRecord(Record records[]) {
@ -372,7 +433,7 @@ public class HSLFSlideShowEncrypted {
protected static Record[] normalizeRecords(Record records[]) {
// http://msdn.microsoft.com/en-us/library/office/gg615594(v=office.14).aspx
// repeated slideIds can be overwritten, i.e. ignored
UserEditAtom uea = null;
PersistPtrHolder pph = null;
TreeMap<Integer,Integer> slideLocations = new TreeMap<Integer,Integer>();
@ -386,7 +447,7 @@ public class HSLFSlideShowEncrypted {
uea = (UserEditAtom)pdr;
continue;
}
if (pdr instanceof PersistPtrHolder) {
if (pph != null) {
duplicatedCount++;
@ -394,16 +455,18 @@ public class HSLFSlideShowEncrypted {
pph = (PersistPtrHolder)pdr;
for (Map.Entry<Integer,Integer> me : pph.getSlideLocationsLookup().entrySet()) {
Integer oldOffset = slideLocations.put(me.getKey(), me.getValue());
if (oldOffset != null) obsoleteOffsets.add(oldOffset);
if (oldOffset != null) {
obsoleteOffsets.add(oldOffset);
}
}
continue;
}
recordMap.put(pdr.getLastOnDiskOffset(), r);
}
assert(uea != null && pph != null && uea.getPersistPointersOffset() == pph.getLastOnDiskOffset());
recordMap.put(pph.getLastOnDiskOffset(), pph);
recordMap.put(uea.getLastOnDiskOffset(), uea);
@ -416,15 +479,15 @@ public class HSLFSlideShowEncrypted {
for (Map.Entry<Integer,Integer> me : slideLocations.entrySet()) {
pph.addSlideLookup(me.getKey(), me.getValue());
}
for (Integer oldOffset : obsoleteOffsets) {
recordMap.remove(oldOffset);
}
return recordMap.values().toArray(new Record[recordMap.size()]);
}
protected static Record[] removeEncryptionRecord(Record records[]) {
int deaSlideId = -1;
int deaOffset = -1;
@ -444,23 +507,27 @@ public class HSLFSlideShowEncrypted {
}
recordList.add(r);
}
assert(ptr != null);
if (deaSlideId == -1 && deaOffset == -1) return records;
if (deaSlideId == -1 && deaOffset == -1) {
return records;
}
TreeMap<Integer,Integer> tm = new TreeMap<Integer,Integer>(ptr.getSlideLocationsLookup());
ptr.clear();
int maxSlideId = -1;
for (Map.Entry<Integer,Integer> me : tm.entrySet()) {
if (me.getKey() == deaSlideId || me.getValue() == deaOffset) continue;
if (me.getKey() == deaSlideId || me.getValue() == deaOffset) {
continue;
}
ptr.addSlideLookup(me.getKey(), me.getValue());
maxSlideId = Math.max(me.getKey(), maxSlideId);
}
uea.setMaxPersistWritten(maxSlideId);
records = recordList.toArray(new Record[recordList.size()]);
return records;
}
@ -470,9 +537,13 @@ public class HSLFSlideShowEncrypted {
int ueaIdx = -1, ptrIdx = -1, deaIdx = -1, idx = -1;
for (Record r : records) {
idx++;
if (r instanceof UserEditAtom) ueaIdx = idx;
else if (r instanceof PersistPtrHolder) ptrIdx = idx;
else if (r instanceof DocumentEncryptionAtom) deaIdx = idx;
if (r instanceof UserEditAtom) {
ueaIdx = idx;
} else if (r instanceof PersistPtrHolder) {
ptrIdx = idx;
} else if (r instanceof DocumentEncryptionAtom) {
deaIdx = idx;
}
}
assert(ueaIdx != -1 && ptrIdx != -1 && ptrIdx < ueaIdx);
if (deaIdx != -1) {
@ -488,13 +559,22 @@ public class HSLFSlideShowEncrypted {
ptr.addSlideLookup(nextSlideId, ptr.getLastOnDiskOffset()-1);
uea.setEncryptSessionPersistIdRef(nextSlideId);
uea.setMaxPersistWritten(nextSlideId);
Record newRecords[] = new Record[records.length+1];
if (ptrIdx > 0) System.arraycopy(records, 0, newRecords, 0, ptrIdx);
if (ptrIdx < records.length-1) System.arraycopy(records, ptrIdx, newRecords, ptrIdx+1, records.length-ptrIdx);
if (ptrIdx > 0) {
System.arraycopy(records, 0, newRecords, 0, ptrIdx);
}
if (ptrIdx < records.length-1) {
System.arraycopy(records, ptrIdx, newRecords, ptrIdx+1, records.length-ptrIdx);
}
newRecords[ptrIdx] = dea;
return newRecords;
}
}
public void close() throws IOException {
if (cyos != null) {
cyos.close();
}
}
}

View File

@ -54,6 +54,7 @@ import org.apache.poi.poifs.filesystem.EntryUtils;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.sl.usermodel.PictureData.PictureType;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
@ -205,14 +206,12 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable {
// Grab the document stream
int len = docProps.getSize();
_docstream = new byte[len];
InputStream is = directory.createDocumentInputStream("PowerPoint Document");
int readLen = is.read(_docstream);
is.close();
if (len != readLen) {
throw new IOException("Document input stream ended prematurely - expected "+len+" bytes - received "+readLen+" bytes");
}
InputStream is = directory.createDocumentInputStream("PowerPoint Document");
try {
_docstream = IOUtils.toByteArray(is, len);
} finally {
is.close();
}
}
/**
@ -364,17 +363,10 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable {
HSLFSlideShowEncrypted decryptData = new HSLFSlideShowEncrypted(getDocumentEncryptionAtom());
DocumentEntry entry = (DocumentEntry)directory.getEntry("Pictures");
int len = entry.getSize();
byte[] pictstream = new byte[len];
DocumentInputStream is = directory.createDocumentInputStream(entry);
int readLen = is.read(pictstream);
DocumentInputStream is = directory.createDocumentInputStream(entry);
byte[] pictstream = IOUtils.toByteArray(is, entry.getSize());
is.close();
if (len != readLen) {
throw new IOException("Picture stream ended prematurely - expected "+len+" bytes - received "+readLen+" bytes");
}
int pos = 0;
// An empty picture record (length 0) will take up 8 bytes
while (pos <= (pictstream.length-8)) {
@ -512,7 +504,7 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable {
}
HSLFSlideShowEncrypted encData = new HSLFSlideShowEncrypted(getDocumentEncryptionAtom());
for (Record record : _records) {
assert(record instanceof PositionDependentRecord);
// We've already figured out their new location, and
@ -533,6 +525,8 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable {
record.writeOut(encData.encryptRecord(os, persistId, record));
}
}
encData.close();
// Update and write out the Current User atom
int oldLastUserEditAtomPos = (int)currentUser.getCurrentEditOffset();
@ -733,7 +727,7 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable {
if (dea != null) {
CryptoAPIEncryptor enc = (CryptoAPIEncryptor)dea.getEncryptionInfo().getEncryptor();
try {
enc.getDataStream(outFS.getRoot()); // ignore OutputStream
enc.getSummaryEntries(outFS.getRoot()); // ignore OutputStream
} catch (IOException e) {
throw e;
} catch (GeneralSecurityException e) {

View File

@ -25,6 +25,7 @@ import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.security.MessageDigest;
import java.util.List;
@ -44,6 +45,7 @@ import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor;
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptionHeader;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
@ -176,7 +178,7 @@ public class TestDocumentEncryption {
DocumentEncryptionAtom dea = hss.getDocumentEncryptionAtom();
POIFSFileSystem fs2 = new POIFSFileSystem(dea.getEncryptionInfo().getDecryptor().getDataStream(fs));
POIFSFileSystem fs2 = ((CryptoAPIDecryptor)dea.getEncryptionInfo().getDecryptor()).getSummaryEntries(fs.getRoot(), "EncryptedSummary");
PropertySet ps = PropertySetFactory.create(fs2.getRoot(), SummaryInformation.DEFAULT_STREAM_NAME);
assertTrue(ps.isSummaryInformation());
assertEquals("RC4 CryptoAPI Encryption", ps.getProperties()[1].getValue());