168 lines
6.6 KiB
Java
168 lines
6.6 KiB
Java
/* ====================================================================
|
|
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.binaryrc4;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.security.GeneralSecurityException;
|
|
import java.security.MessageDigest;
|
|
import java.util.Arrays;
|
|
|
|
import javax.crypto.Cipher;
|
|
import javax.crypto.SecretKey;
|
|
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 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.this.initCipherForBlock(existing, block);
|
|
}
|
|
|
|
public BinaryRC4CipherInputStream(DocumentInputStream stream, long size)
|
|
throws GeneralSecurityException {
|
|
super(stream, size, _chunkSize);
|
|
}
|
|
|
|
public BinaryRC4CipherInputStream(LittleEndianInput stream)
|
|
throws GeneralSecurityException {
|
|
super(stream, Integer.MAX_VALUE, _chunkSize);
|
|
}
|
|
}
|
|
|
|
protected BinaryRC4Decryptor() {
|
|
}
|
|
|
|
@Override
|
|
public boolean verifyPassword(String password) {
|
|
EncryptionVerifier ver = getEncryptionInfo().getVerifier();
|
|
SecretKey skey = generateSecretKey(password, ver);
|
|
try {
|
|
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);
|
|
setVerifier(verifier);
|
|
byte encryptedVerifierHash[] = ver.getEncryptedVerifierHash();
|
|
byte verifierHash[] = cipher.doFinal(encryptedVerifierHash);
|
|
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
|
|
MessageDigest hashAlg = CryptoFunctions.getMessageDigest(hashAlgo);
|
|
byte calcVerifierHash[] = hashAlg.digest(verifier);
|
|
if (Arrays.equals(calcVerifierHash, verifierHash)) {
|
|
setSecretKey(skey);
|
|
return true;
|
|
}
|
|
} catch (GeneralSecurityException e) {
|
|
throw new EncryptedDocumentException(e);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public Cipher initCipherForBlock(Cipher cipher, int block)
|
|
throws GeneralSecurityException {
|
|
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 = encryptionInfo.getHeader();
|
|
cipher = CryptoFunctions.getCipher(key, em.getCipherAlgorithm(), null, null, encryptMode);
|
|
} else {
|
|
cipher.init(encryptMode, key);
|
|
}
|
|
return cipher;
|
|
}
|
|
|
|
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));
|
|
byte salt[] = ver.getSalt();
|
|
hashAlg.reset();
|
|
for (int i = 0; i < 16; i++) {
|
|
hashAlg.update(hash, 0, 5);
|
|
hashAlg.update(salt);
|
|
}
|
|
|
|
hash = new byte[5];
|
|
System.arraycopy(hashAlg.digest(), 0, hash, 0, 5);
|
|
SecretKey skey = new SecretKeySpec(hash, ver.getCipherAlgorithm().jceId);
|
|
return skey;
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("resource")
|
|
public ChunkedCipherInputStream getDataStream(DirectoryNode dir) throws IOException,
|
|
GeneralSecurityException {
|
|
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
|
|
_length = dis.readLong();
|
|
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");
|
|
}
|
|
|
|
return _length;
|
|
}
|
|
|
|
@Override
|
|
public void setChunkSize(int chunkSize) {
|
|
_chunkSize = chunkSize;
|
|
}
|
|
|
|
@Override
|
|
public BinaryRC4Decryptor clone() throws CloneNotSupportedException {
|
|
return (BinaryRC4Decryptor)super.clone();
|
|
}
|
|
}
|