package org.apache.poi.poifs.crypt.xor;
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.crypt.ChunkedCipherInputStream;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.util.LittleEndian;
public class XORDecryptor extends Decryptor implements Cloneable {
private long length = -1L;
private int chunkSize = 512;
protected XORDecryptor() {
}
@Override
public boolean verifyPassword(String password) {
XOREncryptionVerifier ver = (XOREncryptionVerifier)getEncryptionInfo().getVerifier();
int keyVer = LittleEndian.getUShort(ver.getEncryptedKey());
int verifierVer = LittleEndian.getUShort(ver.getEncryptedVerifier());
int keyComp = CryptoFunctions.createXorKey1(password);
int verifierComp = CryptoFunctions.createXorVerifier1(password);
if (keyVer == keyComp && verifierVer == verifierComp) {
byte[] xorArray = CryptoFunctions.createXorArray1(password);
setSecretKey(new SecretKeySpec(xorArray, "XOR"));
return true;
} else {
return false;
}
}
@Override
public Cipher initCipherForBlock(Cipher cipher, int block)
throws GeneralSecurityException {
return null;
}
protected static Cipher initCipherForBlock(Cipher cipher, int block,
EncryptionInfo encryptionInfo, SecretKey skey, int encryptMode)
throws GeneralSecurityException {
return null;
}
@Override
public ChunkedCipherInputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
throw new EncryptedDocumentException("not supported");
}
@Override
public InputStream getDataStream(InputStream stream, int size, int initialPos)
throws IOException, GeneralSecurityException {
return new XORCipherInputStream(stream, initialPos);
}
@Override
public long getLength() {
if (length == -1L) {
throw new IllegalStateException("Decryptor.getDataStream() was not called");
}
return length;
}
@Override
public void setChunkSize(int chunkSize) {
this.chunkSize = chunkSize;
}
@Override
public XORDecryptor clone() throws CloneNotSupportedException {
return (XORDecryptor)super.clone();
}
private class XORCipherInputStream extends ChunkedCipherInputStream {
private final int initialOffset;
private int recordStart;
private int recordEnd;
public XORCipherInputStream(InputStream stream, int initialPos)
throws GeneralSecurityException {
super(stream, Integer.MAX_VALUE, chunkSize);
this.initialOffset = initialPos;
}
@Override
protected Cipher initCipherForBlock(Cipher existing, int block)
throws GeneralSecurityException {
return XORDecryptor.this.initCipherForBlock(existing, block);
}
@Override
protected int invokeCipher(int totalBytes, boolean doFinal) {
final int pos = (int)getPos();
final byte[] xorArray = getEncryptionInfo().getDecryptor().getSecretKey().getEncoded();
final byte[] chunk = getChunk();
final byte[] plain = getPlain();
final int posInChunk = pos & getChunkMask();
final int xorArrayIndex = initialOffset+recordEnd+(pos-recordStart);
for (int i=0; pos+i < recordEnd && i < totalBytes; i++) {
byte value = plain[posInChunk+i];
value = rotateLeft(value, 3);
value ^= xorArray[(xorArrayIndex+i) & 0x0F];
chunk[posInChunk+i] = value;
}
return totalBytes;
}
private byte rotateLeft(byte bits, int shift) {
return (byte)(((bits & 0xff) << shift) | ((bits & 0xff) >>> (8 - shift)));
}
@Override
public void setNextRecordSize(int recordSize) {
final int pos = (int)getPos();
final byte[] chunk = getChunk();
final int chunkMask = getChunkMask();
recordStart = pos;
recordEnd = recordStart+recordSize;
int nextBytes = Math.min(recordSize, chunk.length-(pos & chunkMask));
invokeCipher(nextBytes, true);
}
}
}