//EXPECTED_FAILURES.add("poifs/extenxls_pwd123.xlsx");
//EXPECTED_FAILURES.add("poifs/protected_agile.docx");
EXPECTED_FAILURES.add("spreadsheet/58616.xlsx");
+ EXPECTED_FAILURES.add("poifs/60320-protected.xlsx");
// TODO: fails XMLExportTest, is this ok?
EXPECTED_FAILURES.add("spreadsheet/CustomXMLMapping-singleattributenamespace.xlsx");
==================================================================== */
package org.apache.poi.poifs.crypt;
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.util.Removal;
+
/**
* Reads and processes OOXML Encryption Headers
* The constants are largely based on ZIP constants.
protected void setCipherAlgorithm(CipherAlgorithm cipherAlgorithm) {
this.cipherAlgorithm = cipherAlgorithm;
+ if (cipherAlgorithm.allowedKeySize.length == 1) {
+ setKeySize(cipherAlgorithm.defaultKeySize);
+ }
+ }
+
+ public HashAlgorithm getHashAlgorithm() {
+ return hashAlgorithm;
}
+ /**
+ * @deprecated POI 3.16 beta 1. use {@link #getHashAlgorithm()}
+ */
+ @Removal(version="3.18")
public HashAlgorithm getHashAlgorithmEx() {
return hashAlgorithm;
}
return keyBits;
}
+ /**
+ * Sets the keySize (in bits). Before calling this method, make sure
+ * to set the cipherAlgorithm, as the amount of keyBits gets validated against
+ * the list of allowed keyBits of the corresponding cipherAlgorithm
+ *
+ * @param keyBits
+ */
protected void setKeySize(int keyBits) {
this.keyBits = keyBits;
+ for (int allowedBits : getCipherAlgorithm().allowedKeySize) {
+ if (allowedBits == keyBits) {
+ return;
+ }
+ }
+ throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for cipher "+getCipherAlgorithm());
}
public int getBlockSize() {
==================================================================== */
package org.apache.poi.poifs.crypt;
+import org.apache.poi.util.Removal;
+
/**
* Used when checking if a key is valid for a document
*/
return spinCount;
}
+ /**
+ * @deprecated POI 3.16 beta 1. use {@link #getChainingMode()}
+ */
+ @Removal(version="3.18")
public int getCipherMode() {
return chainingMode.ecmaId;
}
+ /**
+ * @deprecated POI 3.16 beta 1. use {@link #getCipherAlgorithm()}
+ */
public int getAlgorithm() {
return cipherAlgorithm.ecmaId;
}
bos.writeInt(getFlags());\r
bos.writeInt(0); // size extra\r
bos.writeInt(getCipherAlgorithm().ecmaId);\r
- bos.writeInt(getHashAlgorithmEx().ecmaId);\r
+ bos.writeInt(getHashAlgorithm().ecmaId);\r
bos.writeInt(getKeySize());\r
bos.writeInt(getCipherProvider().ecmaId);\r
bos.writeInt(0); // reserved1\r
setCipherAlgorithm(header.getCipherAlgorithm());
setChainingMode(header.getChainingMode());
setEncryptedKey(null);
- setHashAlgorithm(header.getHashAlgorithmEx());
+ setHashAlgorithm(header.getHashAlgorithm());
}
protected StandardEncryptionVerifier(CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
import javax.crypto.spec.SecretKeySpec;
import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.ChainingMode;
import org.apache.poi.poifs.crypt.ChunkedCipherInputStream;
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.EncryptionInfo;
-import org.apache.poi.poifs.crypt.EncryptionVerifier;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier.AgileCertificateEntry;
import org.apache.poi.poifs.filesystem.DirectoryNode;
public boolean verifyPassword(String password) throws GeneralSecurityException {
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
- HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
- CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
+
int blockSize = header.getBlockSize();
- int keySize = header.getKeySize()/8;
byte[] pwHash = hashPassword(password, ver.getHashAlgorithm(), ver.getSalt(), ver.getSpinCount());
* blockSize bytes.
* 4. Use base64 to encode the result of step 3.
*/
- byte verfierInputEnc[] = hashInput(getEncryptionInfo(), pwHash, kVerifierInputBlock, ver.getEncryptedVerifier(), Cipher.DECRYPT_MODE);
+ byte verfierInputEnc[] = hashInput(ver, pwHash, kVerifierInputBlock, ver.getEncryptedVerifier(), Cipher.DECRYPT_MODE);
setVerifier(verfierInputEnc);
- MessageDigest hashMD = getMessageDigest(hashAlgo);
+ MessageDigest hashMD = getMessageDigest(ver.getHashAlgorithm());
byte[] verifierHash = hashMD.digest(verfierInputEnc);
/**
* 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(getEncryptionInfo(), pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE);
- verifierHashDec = getBlock0(verifierHashDec, hashAlgo.hashSize);
+ byte verifierHashDec[] = hashInput(ver, pwHash, kHashedVerifierBlock, ver.getEncryptedVerifierHash(), Cipher.DECRYPT_MODE);
+ verifierHashDec = getBlock0(verifierHashDec, ver.getHashAlgorithm().hashSize);
/**
* encryptedKeyValue: This attribute MUST be generated by using the following steps:
* blockSize bytes.
* 4. Use base64 to encode the result of step 3.
*/
- byte keyspec[] = hashInput(getEncryptionInfo(), pwHash, kCryptoKeyBlock, ver.getEncryptedKey(), Cipher.DECRYPT_MODE);
- keyspec = getBlock0(keyspec, keySize);
- SecretKeySpec secretKey = new SecretKeySpec(keyspec, ver.getCipherAlgorithm().jceId);
+ byte keyspec[] = hashInput(ver, pwHash, kCryptoKeyBlock, ver.getEncryptedKey(), Cipher.DECRYPT_MODE);
+ keyspec = getBlock0(keyspec, header.getKeySize()/8);
+ SecretKeySpec secretKey = new SecretKeySpec(keyspec, header.getCipherAlgorithm().jceId);
/**
* 1. Obtain the intermediate key by decrypting the encryptedKeyValue from a KeyEncryptor
* array with 0x00 to the next integral multiple of blockSize bytes.
* 4. Assign the encryptedHmacKey attribute to the base64-encoded form of the result of step 3.
*/
- byte vec[] = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityKeyBlock, blockSize);
- Cipher cipher = getCipher(secretKey, cipherAlgo, ver.getChainingMode(), vec, Cipher.DECRYPT_MODE);
+ byte vec[] = CryptoFunctions.generateIv(header.getHashAlgorithm(), header.getKeySalt(), kIntegrityKeyBlock, blockSize);
+ CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
+ Cipher cipher = getCipher(secretKey, cipherAlgo, header.getChainingMode(), vec, Cipher.DECRYPT_MODE);
byte hmacKey[] = cipher.doFinal(header.getEncryptedHmacKey());
- hmacKey = getBlock0(hmacKey, hashAlgo.hashSize);
+ hmacKey = getBlock0(hmacKey, header.getHashAlgorithm().hashSize);
/**
* 5. Generate an HMAC, as specified in [RFC2104], of the encrypted form of the data (message),
* 0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, and 0x33.
* 7. Assign the encryptedHmacValue attribute to the base64-encoded form of the result of step 6.
*/
- vec = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityValueBlock, blockSize);
+ vec = CryptoFunctions.generateIv(header.getHashAlgorithm(), header.getKeySalt(), kIntegrityValueBlock, blockSize);
cipher = getCipher(secretKey, cipherAlgo, ver.getChainingMode(), vec, Cipher.DECRYPT_MODE);
byte hmacValue[] = cipher.doFinal(header.getEncryptedHmacValue());
- hmacValue = getBlock0(hmacValue, hashAlgo.hashSize);
+ hmacValue = getBlock0(hmacValue, header.getHashAlgorithm().hashSize);
if (Arrays.equals(verifierHashDec, verifierHash)) {
setSecretKey(secretKey);
public boolean verifyPassword(KeyPair keyPair, X509Certificate x509) throws GeneralSecurityException {
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();
- HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
+ HashAlgorithm hashAlgo = header.getHashAlgorithm();
CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
int blockSize = header.getBlockSize();
byte certVerifier[] = x509Hmac.doFinal(ace.x509.getEncoded());
byte vec[] = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityKeyBlock, blockSize);
- cipher = getCipher(secretKey, cipherAlgo, ver.getChainingMode(), vec, Cipher.DECRYPT_MODE);
+ cipher = getCipher(secretKey, cipherAlgo, header.getChainingMode(), vec, Cipher.DECRYPT_MODE);
byte hmacKey[] = cipher.doFinal(header.getEncryptedHmacKey());
hmacKey = getBlock0(hmacKey, hashAlgo.hashSize);
vec = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityValueBlock, blockSize);
- cipher = getCipher(secretKey, cipherAlgo, ver.getChainingMode(), vec, Cipher.DECRYPT_MODE);
+ cipher = getCipher(secretKey, cipherAlgo, header.getChainingMode(), vec, Cipher.DECRYPT_MODE);
byte hmacValue[] = cipher.doFinal(header.getEncryptedHmacValue());
hmacValue = getBlock0(hmacValue, hashAlgo.hashSize);
return fillSize;
}
- 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();
+ /* package */ static byte[] hashInput(AgileEncryptionVerifier ver, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) {
+ CipherAlgorithm cipherAlgo = ver.getCipherAlgorithm();
+ ChainingMode chainMode = ver.getChainingMode();
+ int keySize = ver.getKeySize()/8;
+ int blockSize = ver.getBlockSize();
HashAlgorithm hashAlgo = ver.getHashAlgorithm();
- byte[] salt = ver.getSalt();
-
+
byte intermedKey[] = generateKey(pwHash, hashAlgo, blockKey, keySize);
- SecretKey skey = new SecretKeySpec(intermedKey, ver.getCipherAlgorithm().jceId);
- byte[] iv = generateIv(hashAlgo, salt, null, blockSize);
- Cipher cipher = getCipher(skey, ver.getCipherAlgorithm(), ver.getChainingMode(), iv, cipherMode);
+ SecretKey skey = new SecretKeySpec(intermedKey, cipherAlgo.jceId);
+ byte[] iv = generateIv(hashAlgo, ver.getSalt(), null, blockSize);
+ Cipher cipher = getCipher(skey, cipherAlgo, chainMode, iv, cipherMode);
byte[] hashFinal;
try {
}
@Override
- @SuppressWarnings("resource")
public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
DocumentInputStream dis = dir.createDocumentInputStream(DEFAULT_POIFS_ENTRY);
_length = dis.readLong();
byte[] blockKey = new byte[4];
LittleEndian.putInt(blockKey, 0, block);
- byte[] iv = generateIv(header.getHashAlgorithmEx(), header.getKeySalt(), blockKey, header.getBlockSize());
+ byte[] iv = generateIv(header.getHashAlgorithm(), header.getKeySalt(), blockKey, header.getBlockSize());
AlgorithmParameterSpec aps;
if (header.getCipherAlgorithm() == CipherAlgorithm.rc2) {
throw new EncryptedDocumentException("Unable to parse keyData");\r
}\r
\r
- setKeySize((int)keyData.getKeyBits());\r
- setFlags(0);\r
- setSizeExtra(0);\r
- setCspName(null);\r
- setBlockSize(keyData.getBlockSize());\r
-\r
int keyBits = (int)keyData.getKeyBits();\r
\r
CipherAlgorithm ca = CipherAlgorithm.fromXmlId(keyData.getCipherAlgorithm().toString(), keyBits);\r
setCipherAlgorithm(ca);\r
setCipherProvider(ca.provider);\r
\r
+ setKeySize(keyBits);\r
+ setFlags(0);\r
+ setSizeExtra(0);\r
+ setCspName(null);\r
+ setBlockSize(keyData.getBlockSize());\r
+\r
switch (keyData.getCipherChaining().intValue()) {\r
case STCipherChaining.INT_CHAINING_MODE_CBC:\r
setChainingMode(ChainingMode.cbc);\r
HashAlgorithm ha = HashAlgorithm.fromEcmaId(keyData.getHashAlgorithm().toString());\r
setHashAlgorithm(ha);\r
\r
- if (getHashAlgorithmEx().hashSize != hashSize) {\r
+ if (getHashAlgorithm().hashSize != hashSize) {\r
throw new EncryptedDocumentException("Unsupported hash algorithm: " + \r
keyData.getHashAlgorithm() + " @ " + hashSize + " bytes");\r
}\r
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
}
private List<AgileCertificateEntry> certList = new ArrayList<AgileCertificateEntry>();
+ private int keyBits = -1;
+ private int blockSize = -1;
public AgileEncryptionVerifier(String descriptor) {
this(AgileEncryptionInfoBuilder.parseDescriptor(descriptor));
}
int keyBits = (int)keyData.getKeyBits();
-
CipherAlgorithm ca = CipherAlgorithm.fromXmlId(keyData.getCipherAlgorithm().toString(), keyBits);
setCipherAlgorithm(ca);
+ setKeySize(keyBits);
+
+ int blockSize = keyData.getBlockSize();
+ setBlockSize(blockSize);
+
int hashSize = keyData.getHashSize();
HashAlgorithm ha = HashAlgorithm.fromEcmaId(keyData.getHashAlgorithm().toString());
setCipherAlgorithm(cipherAlgorithm);
setHashAlgorithm(hashAlgorithm);
setChainingMode(chainingMode);
+ setKeySize(keyBits);
+ setBlockSize(blockSize);
setSpinCount(100000); // TODO: use parameter
}
other.certList = new ArrayList<AgileCertificateEntry>(certList);
return other;
}
+
+
+ /**
+ * The keysize (in bits) of the verifier data. This usually equals the keysize of the header,
+ * but only on a few exceptions, like files generated by Office for Mac, can be
+ * different.
+ *
+ * @return the keysize (in bits) of the verifier.
+ */
+ public int getKeySize() {
+ return keyBits;
+ }
+
+
+ /**
+ * The blockSize (in bytes) of the verifier data.
+ * This usually equals the blocksize of the header.
+ *
+ * @return the blockSize (in bytes) of the verifier,
+ */
+ public int getBlockSize() {
+ return blockSize;
+ }
+
+ /**
+ * Sets the keysize (in bits) of the verifier
+ *
+ * @param keyBits the keysize (in bits)
+ */
+ protected void setKeySize(int keyBits) {
+ this.keyBits = keyBits;
+ for (int allowedBits : getCipherAlgorithm().allowedKeySize) {
+ if (allowedBits == keyBits) {
+ return;
+ }
+ }
+ throw new EncryptedDocumentException("KeySize "+keyBits+" not allowed for cipher "+getCipherAlgorithm());
+ }
+
+
+ /**
+ * Sets the blockSize (in bytes) of the verifier
+ *
+ * @param blockSize the blockSize (in bytes)
+ */
+ protected void setBlockSize(int blockSize) {
+ this.blockSize = blockSize;
+ }
+
+ @Override
+ protected void setCipherAlgorithm(CipherAlgorithm cipherAlgorithm) {
+ super.setCipherAlgorithm(cipherAlgorithm);
+ if (cipherAlgorithm.allowedKeySize.length == 1) {
+ setKeySize(cipherAlgorithm.defaultKeySize);
+ }
+ }
}
public void confirmPassword(String password) {\r
// see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier\r
Random r = new SecureRandom();\r
- int blockSize = getEncryptionInfo().getHeader().getBlockSize();\r
- int keySize = getEncryptionInfo().getHeader().getKeySize()/8;\r
- int hashSize = getEncryptionInfo().getHeader().getHashAlgorithmEx().hashSize;\r
+ AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();\r
+ int blockSize = header.getBlockSize();\r
+ int keySize = header.getKeySize()/8;\r
+ int hashSize = header.getHashAlgorithm().hashSize;\r
\r
byte[] newVerifierSalt = new byte[blockSize]\r
, newVerifier = new byte[blockSize]\r
@Override\r
public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) {\r
AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();\r
- ver.setSalt(verifierSalt);\r
AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();\r
+\r
+ ver.setSalt(verifierSalt);\r
header.setKeySalt(keySalt);\r
- HashAlgorithm hashAlgo = ver.getHashAlgorithm();\r
\r
int blockSize = header.getBlockSize();\r
\r
- pwHash = hashPassword(password, hashAlgo, verifierSalt, ver.getSpinCount());\r
+ pwHash = hashPassword(password, ver.getHashAlgorithm(), verifierSalt, ver.getSpinCount());\r
\r
/**\r
* encryptedVerifierHashInput: This attribute MUST be generated by using the following steps:\r
* blockSize bytes.\r
* 4. Use base64 to encode the result of step 3.\r
*/\r
- byte encryptedVerifier[] = hashInput(getEncryptionInfo(), pwHash, kVerifierInputBlock, verifier, Cipher.ENCRYPT_MODE);\r
+ byte encryptedVerifier[] = hashInput(ver, pwHash, kVerifierInputBlock, verifier, Cipher.ENCRYPT_MODE);\r
ver.setEncryptedVerifier(encryptedVerifier);\r
\r
\r
* blockSize bytes, pad the hash value with 0x00 to an integral multiple of blockSize bytes.\r
* 4. Use base64 to encode the result of step 3.\r
*/\r
- MessageDigest hashMD = getMessageDigest(hashAlgo);\r
+ MessageDigest hashMD = getMessageDigest(ver.getHashAlgorithm());\r
byte[] hashedVerifier = hashMD.digest(verifier);\r
- byte encryptedVerifierHash[] = hashInput(getEncryptionInfo(), pwHash, kHashedVerifierBlock, hashedVerifier, Cipher.ENCRYPT_MODE);\r
+ byte encryptedVerifierHash[] = hashInput(ver, pwHash, kHashedVerifierBlock, hashedVerifier, Cipher.ENCRYPT_MODE);\r
ver.setEncryptedVerifierHash(encryptedVerifierHash);\r
\r
/**\r
* blockSize bytes.\r
* 4. Use base64 to encode the result of step 3.\r
*/\r
- byte encryptedKey[] = hashInput(getEncryptionInfo(), pwHash, kCryptoKeyBlock, keySpec, Cipher.ENCRYPT_MODE);\r
+ byte encryptedKey[] = hashInput(ver, pwHash, kCryptoKeyBlock, keySpec, Cipher.ENCRYPT_MODE);\r
ver.setEncryptedKey(encryptedKey);\r
\r
- SecretKey secretKey = new SecretKeySpec(keySpec, ver.getCipherAlgorithm().jceId);\r
+ SecretKey secretKey = new SecretKeySpec(keySpec, header.getCipherAlgorithm().jceId);\r
setSecretKey(secretKey);\r
\r
/*\r
this.integritySalt = integritySalt.clone();\r
\r
try {\r
- byte vec[] = CryptoFunctions.generateIv(hashAlgo, header.getKeySalt(), kIntegrityKeyBlock, header.getBlockSize());\r
- Cipher cipher = getCipher(secretKey, ver.getCipherAlgorithm(), ver.getChainingMode(), vec, Cipher.ENCRYPT_MODE);\r
- byte filledSalt[] = getBlock0(this.integritySalt, getNextBlockSize(this.integritySalt.length, blockSize));\r
- byte encryptedHmacKey[] = cipher.doFinal(filledSalt);\r
+ byte vec[] = CryptoFunctions.generateIv(header.getHashAlgorithm(), header.getKeySalt(), kIntegrityKeyBlock, header.getBlockSize());\r
+ Cipher cipher = getCipher(secretKey, header.getCipherAlgorithm(), header.getChainingMode(), vec, Cipher.ENCRYPT_MODE);\r
+ byte hmacKey[] = getBlock0(this.integritySalt, getNextBlockSize(this.integritySalt.length, blockSize));\r
+ byte encryptedHmacKey[] = cipher.doFinal(hmacKey);\r
header.setEncryptedHmacKey(encryptedHmacKey);\r
\r
cipher = Cipher.getInstance("RSA");\r
for (AgileCertificateEntry ace : ver.getCertificates()) {\r
cipher.init(Cipher.ENCRYPT_MODE, ace.x509.getPublicKey());\r
ace.encryptedKey = cipher.doFinal(getSecretKey().getEncoded());\r
- Mac x509Hmac = CryptoFunctions.getMac(hashAlgo);\r
+ Mac x509Hmac = CryptoFunctions.getMac(header.getHashAlgorithm());\r
x509Hmac.init(getSecretKey());\r
ace.certVerifier = x509Hmac.doFinal(ace.x509.getEncoded());\r
}\r
// as the integrity hmac needs to contain the StreamSize,\r
// it's not possible to calculate it on-the-fly while buffering\r
// TODO: add stream size parameter to getDataStream()\r
- AgileEncryptionVerifier ver = (AgileEncryptionVerifier)getEncryptionInfo().getVerifier();\r
- HashAlgorithm hashAlgo = ver.getHashAlgorithm();\r
+ AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();\r
+ int blockSize = header.getBlockSize();\r
+ HashAlgorithm hashAlgo = header.getHashAlgorithm();\r
Mac integrityMD = CryptoFunctions.getMac(hashAlgo);\r
- integrityMD.init(new SecretKeySpec(integritySalt, hashAlgo.jceHmacId));\r
+ byte hmacKey[] = getBlock0(this.integritySalt, getNextBlockSize(this.integritySalt.length, blockSize));\r
+ integrityMD.init(new SecretKeySpec(hmacKey, hashAlgo.jceHmacId));\r
\r
byte buf[] = new byte[1024];\r
LittleEndian.putLong(buf, 0, oleStreamSize);\r
}\r
\r
byte hmacValue[] = integrityMD.doFinal();\r
+ byte hmacValueFilled[] = getBlock0(hmacValue, getNextBlockSize(hmacValue.length, blockSize));\r
\r
- AgileEncryptionHeader header = (AgileEncryptionHeader)getEncryptionInfo().getHeader();\r
- int blockSize = header.getBlockSize();\r
- byte iv[] = CryptoFunctions.generateIv(header.getHashAlgorithmEx(), header.getKeySalt(), kIntegrityValueBlock, blockSize);\r
+ byte iv[] = CryptoFunctions.generateIv(header.getHashAlgorithm(), header.getKeySalt(), kIntegrityValueBlock, blockSize);\r
Cipher cipher = CryptoFunctions.getCipher(getSecretKey(), header.getCipherAlgorithm(), header.getChainingMode(), iv, Cipher.ENCRYPT_MODE);\r
- byte hmacValueFilled[] = getBlock0(hmacValue, getNextBlockSize(hmacValue.length, blockSize));\r
byte encryptedHmacValue[] = cipher.doFinal(hmacValueFilled);\r
\r
header.setEncryptedHmacValue(encryptedHmacValue);\r
keyPass.setSpinCount(ver.getSpinCount());\r
\r
keyData.setSaltSize(header.getBlockSize());\r
- keyPass.setSaltSize(header.getBlockSize());\r
+ keyPass.setSaltSize(ver.getBlockSize());\r
\r
keyData.setBlockSize(header.getBlockSize());\r
- keyPass.setBlockSize(header.getBlockSize());\r
+ keyPass.setBlockSize(ver.getBlockSize());\r
\r
keyData.setKeyBits(header.getKeySize());\r
- keyPass.setKeyBits(header.getKeySize());\r
+ keyPass.setKeyBits(ver.getKeySize());\r
\r
- HashAlgorithm hashAlgo = header.getHashAlgorithmEx();\r
- keyData.setHashSize(hashAlgo.hashSize);\r
- keyPass.setHashSize(hashAlgo.hashSize);\r
+ keyData.setHashSize(header.getHashAlgorithm().hashSize);\r
+ keyPass.setHashSize(ver.getHashAlgorithm().hashSize);\r
\r
+ // header and verifier have to have the same cipher algorithm\r
+ if (!header.getCipherAlgorithm().xmlId.equals(ver.getCipherAlgorithm().xmlId)) {\r
+ throw new EncryptedDocumentException("Cipher algorithm of header and verifier have to match");\r
+ }\r
STCipherAlgorithm.Enum xmlCipherAlgo = STCipherAlgorithm.Enum.forString(header.getCipherAlgorithm().xmlId);\r
if (xmlCipherAlgo == null) {\r
throw new EncryptedDocumentException("CipherAlgorithm "+header.getCipherAlgorithm()+" not supported.");\r
throw new EncryptedDocumentException("ChainingMode "+header.getChainingMode()+" not supported.");\r
}\r
\r
- STHashAlgorithm.Enum xmlHashAlgo = STHashAlgorithm.Enum.forString(hashAlgo.ecmaString);\r
- if (xmlHashAlgo == null) {\r
- throw new EncryptedDocumentException("HashAlgorithm "+hashAlgo+" not supported.");\r
- }\r
- keyData.setHashAlgorithm(xmlHashAlgo);\r
- keyPass.setHashAlgorithm(xmlHashAlgo);\r
+ keyData.setHashAlgorithm(mapHashAlgorithm(header.getHashAlgorithm()));\r
+ keyPass.setHashAlgorithm(mapHashAlgorithm(ver.getHashAlgorithm()));\r
\r
keyData.setSaltValue(header.getKeySalt());\r
keyPass.setSaltValue(ver.getSalt());\r
\r
return ed;\r
}\r
+\r
+ private static STHashAlgorithm.Enum mapHashAlgorithm(HashAlgorithm hashAlgo) {\r
+ STHashAlgorithm.Enum xmlHashAlgo = STHashAlgorithm.Enum.forString(hashAlgo.ecmaString);\r
+ if (xmlHashAlgo == null) {\r
+ throw new EncryptedDocumentException("HashAlgorithm "+hashAlgo+" not supported.");\r
+ }\r
+ return xmlHashAlgo;\r
+ }\r
\r
protected void marshallEncryptionDocument(EncryptionDocument ed, LittleEndianByteArrayOutputStream os) {\r
XmlOptions xo = new XmlOptions();\r
try {\r
bos.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n".getBytes("UTF-8"));\r
ed.save(bos, xo);\r
- os.write(bos.toByteArray());\r
+ bos.writeTo(os);\r
} catch (IOException e) {\r
throw new EncryptedDocumentException("error marshalling encryption info document", e);\r
}\r
import org.apache.poi.xssf.XSSFTestDataSamples;\r
import org.junit.Test;\r
\r
-/**\r
- * @author Maxim Valyanskiy\r
- * @author Gary King\r
- */\r
public class TestDecryptor {\r
@Test\r
public void passwordVerification() throws IOException, GeneralSecurityException {\r
//dec.verifyPassword(null);\r
dec.getDataStream(pfs);\r
}\r
+\r
+ @Test\r
+ public void bug60320() throws IOException, GeneralSecurityException {\r
+ InputStream is = POIDataSamples.getPOIFSInstance().openResourceAsStream("60320-protected.xlsx");\r
+ POIFSFileSystem fs = new POIFSFileSystem(is);\r
+ is.close();\r
+\r
+ EncryptionInfo info = new EncryptionInfo(fs);\r
+\r
+ Decryptor d = Decryptor.getInstance(info);\r
+\r
+ boolean b = d.verifyPassword("Test001!!");\r
+ assertTrue(b);\r
+\r
+ zipOk(fs.getRoot(), d);\r
+ \r
+ fs.close();\r
+ } \r
}
\ No newline at end of file
assertEquals(2, info.getVersionMinor());\r
\r
assertEquals(CipherAlgorithm.aes128, info.getHeader().getCipherAlgorithm());\r
- assertEquals(HashAlgorithm.sha1, info.getHeader().getHashAlgorithmEx());\r
+ assertEquals(HashAlgorithm.sha1, info.getHeader().getHashAlgorithm());\r
assertEquals(128, info.getHeader().getKeySize());\r
assertEquals(32, info.getVerifier().getEncryptedVerifierHash().length);\r
assertEquals(CipherProvider.aes, info.getHeader().getCipherProvider()); \r
assertEquals(4, info.getVersionMinor());\r
\r
assertEquals(CipherAlgorithm.aes256, info.getHeader().getCipherAlgorithm());\r
- assertEquals(HashAlgorithm.sha512, info.getHeader().getHashAlgorithmEx());\r
+ assertEquals(HashAlgorithm.sha512, info.getHeader().getHashAlgorithm());\r
assertEquals(256, info.getHeader().getKeySize());\r
assertEquals(64, info.getVerifier().getEncryptedVerifierHash().length);\r
assertEquals(CipherProvider.aes, info.getHeader().getCipherProvider()); \r
import org.apache.poi.POIDataSamples;\r
import org.apache.poi.openxml4j.opc.ContentTypes;\r
import org.apache.poi.openxml4j.opc.OPCPackage;\r
+import org.apache.poi.poifs.crypt.agile.AgileDecryptor;\r
import org.apache.poi.poifs.crypt.agile.AgileEncryptionHeader;\r
+import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier;\r
import org.apache.poi.poifs.filesystem.DirectoryNode;\r
import org.apache.poi.poifs.filesystem.DocumentNode;\r
import org.apache.poi.poifs.filesystem.Entry;\r
\r
assertArrayEquals(payloadExpected.toByteArray(), payloadActual.toByteArray());\r
}\r
- \r
+\r
@Test\r
public void agileEncryption() throws Exception {\r
int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");\r
}\r
}\r
}\r
+\r
+ /*\r
+ * this test simulates the generation of bugs 60320 sample file\r
+ * as the padding bytes of the EncryptedPackage stream are random or in POIs case PKCS5-padded\r
+ * one would need to mock those bytes to get the same hmacValues - see diff below\r
+ *\r
+ * this use-case is experimental - for the time being the setters of the encryption classes\r
+ * are spreaded between two packages and are protected - so you would need to violate\r
+ * the packages rules and provide a helper class in the *poifs.crypt package-namespace.\r
+ * the default way of defining the encryption settings is via the EncryptionInfo class\r
+ */\r
+ @Test\r
+ public void bug60320CustomEncrypt() throws Exception {\r
+ // --- src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java (revision 1766745)\r
+ // +++ src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java (working copy)\r
+ // @@ -208,6 +208,13 @@\r
+ // protected int invokeCipher(int posInChunk, boolean doFinal) throws GeneralSecurityException {\r
+ // byte plain[] = (_plainByteFlags.isEmpty()) ? null : _chunk.clone();\r
+ // \r
+ // + if (posInChunk < 4096) {\r
+ // + _cipher.update(_chunk, 0, posInChunk, _chunk);\r
+ // + byte bla[] = { (byte)0x7A,(byte)0x0F,(byte)0x27,(byte)0xF0,(byte)0x17,(byte)0x6E,(byte)0x77,(byte)0x05,(byte)0xB9,(byte)0xDA,(byte)0x49,(byte)0xF9,(byte)0xD7,(byte)0x8E,(byte)0x03,(byte)0x1D };\r
+ // + System.arraycopy(bla, 0, _chunk, posInChunk-2, bla.length);\r
+ // + return posInChunk-2+bla.length;\r
+ // + }\r
+ // + \r
+ // int ciLen = (doFinal)\r
+ // ? _cipher.doFinal(_chunk, 0, posInChunk, _chunk)\r
+ // : _cipher.update(_chunk, 0, posInChunk, _chunk);\r
+ //\r
+ // --- src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java (revision 1766745)\r
+ // +++ src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java (working copy)\r
+ // \r
+ // @@ -300,7 +297,7 @@\r
+ // protected static Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk, EncryptionInfo encryptionInfo, SecretKey skey, int encryptionMode)\r
+ // throws GeneralSecurityException {\r
+ // EncryptionHeader header = encryptionInfo.getHeader();\r
+ // - String padding = (lastChunk ? "PKCS5Padding" : "NoPadding");\r
+ // + String padding = "NoPadding"; // (lastChunk ? "PKCS5Padding" : "NoPadding");\r
+ // if (existing == null || !existing.getAlgorithm().endsWith(padding)) {\r
+ // existing = getCipher(skey, header.getCipherAlgorithm(), header.getChainingMode(), header.getKeySalt(), encryptionMode, padding);\r
+ // }\r
+\r
+ InputStream is = POIDataSamples.getPOIFSInstance().openResourceAsStream("60320-protected.xlsx");\r
+ POIFSFileSystem fsOrig = new POIFSFileSystem(is);\r
+ is.close();\r
+ EncryptionInfo infoOrig = new EncryptionInfo(fsOrig);\r
+ Decryptor decOrig = infoOrig.getDecryptor();\r
+ boolean b = decOrig.verifyPassword("Test001!!");\r
+ assertTrue(b);\r
+ InputStream decIn = decOrig.getDataStream(fsOrig);\r
+ byte[] zipInput = IOUtils.toByteArray(decIn);\r
+ decIn.close();\r
+\r
+ InputStream epOrig = fsOrig.getRoot().createDocumentInputStream("EncryptedPackage");\r
+ // ignore the 16 padding bytes\r
+ byte[] epOrigBytes = IOUtils.toByteArray(epOrig, 9400);\r
+ epOrig.close();\r
+ \r
+ EncryptionInfo eiNew = new EncryptionInfo(EncryptionMode.agile);\r
+ AgileEncryptionHeader aehHeader = (AgileEncryptionHeader)eiNew.getHeader();\r
+ aehHeader.setCipherAlgorithm(CipherAlgorithm.aes128);\r
+ aehHeader.setHashAlgorithm(HashAlgorithm.sha1);\r
+ AgileEncryptionVerifier aehVerifier = (AgileEncryptionVerifier)eiNew.getVerifier();\r
+ \r
+ // this cast might look strange - if the setters would be public, it will become obsolete\r
+ // see http://stackoverflow.com/questions/5637650/overriding-protected-methods-in-java\r
+ ((EncryptionVerifier)aehVerifier).setCipherAlgorithm(CipherAlgorithm.aes256);\r
+ aehVerifier.setHashAlgorithm(HashAlgorithm.sha512);\r
+ \r
+ Encryptor enc = eiNew.getEncryptor();\r
+ enc.confirmPassword("Test001!!",\r
+ infoOrig.getDecryptor().getSecretKey().getEncoded(),\r
+ infoOrig.getHeader().getKeySalt(),\r
+ infoOrig.getDecryptor().getVerifier(),\r
+ infoOrig.getVerifier().getSalt(),\r
+ infoOrig.getDecryptor().getIntegrityHmacKey()\r
+ );\r
+ NPOIFSFileSystem fsNew = new NPOIFSFileSystem();\r
+ OutputStream os = enc.getDataStream(fsNew);\r
+ os.write(zipInput);\r
+ os.close();\r
+\r
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();\r
+ fsNew.writeFilesystem(bos);\r
+ fsNew.close();\r
+ \r
+ NPOIFSFileSystem fsReload = new NPOIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()));\r
+ InputStream epReload = fsReload.getRoot().createDocumentInputStream("EncryptedPackage");\r
+ byte[] epNewBytes = IOUtils.toByteArray(epReload, 9400);\r
+ epReload.close();\r
+ \r
+ assertArrayEquals(epOrigBytes, epNewBytes);\r
+ \r
+ EncryptionInfo infoReload = new EncryptionInfo(fsOrig);\r
+ Decryptor decReload = infoReload.getDecryptor();\r
+ b = decReload.verifyPassword("Test001!!");\r
+ assertTrue(b);\r
+ \r
+ AgileEncryptionHeader aehOrig = (AgileEncryptionHeader)infoOrig.getHeader();\r
+ AgileEncryptionHeader aehReload = (AgileEncryptionHeader)infoReload.getHeader();\r
+ assertEquals(aehOrig.getBlockSize(), aehReload.getBlockSize());\r
+ assertEquals(aehOrig.getChainingMode(), aehReload.getChainingMode());\r
+ assertEquals(aehOrig.getCipherAlgorithm(), aehReload.getCipherAlgorithm());\r
+ assertEquals(aehOrig.getCipherProvider(), aehReload.getCipherProvider());\r
+ assertEquals(aehOrig.getCspName(), aehReload.getCspName());\r
+ assertArrayEquals(aehOrig.getEncryptedHmacKey(), aehReload.getEncryptedHmacKey());\r
+ // this only works, when the paddings are mocked to be the same ...\r
+ // assertArrayEquals(aehOrig.getEncryptedHmacValue(), aehReload.getEncryptedHmacValue());\r
+ assertEquals(aehOrig.getFlags(), aehReload.getFlags());\r
+ assertEquals(aehOrig.getHashAlgorithm(), aehReload.getHashAlgorithm());\r
+ assertArrayEquals(aehOrig.getKeySalt(), aehReload.getKeySalt());\r
+ assertEquals(aehOrig.getKeySize(), aehReload.getKeySize());\r
+ \r
+ AgileEncryptionVerifier aevOrig = (AgileEncryptionVerifier)infoOrig.getVerifier();\r
+ AgileEncryptionVerifier aevReload = (AgileEncryptionVerifier)infoReload.getVerifier();\r
+ assertEquals(aevOrig.getBlockSize(), aevReload.getBlockSize());\r
+ assertEquals(aevOrig.getChainingMode(), aevReload.getChainingMode());\r
+ assertEquals(aevOrig.getCipherAlgorithm(), aevReload.getCipherAlgorithm());\r
+ assertArrayEquals(aevOrig.getEncryptedKey(), aevReload.getEncryptedKey());\r
+ assertArrayEquals(aevOrig.getEncryptedVerifier(), aevReload.getEncryptedVerifier());\r
+ assertArrayEquals(aevOrig.getEncryptedVerifierHash(), aevReload.getEncryptedVerifierHash());\r
+ assertEquals(aevOrig.getHashAlgorithm(), aevReload.getHashAlgorithm());\r
+ assertEquals(aevOrig.getKeySize(), aevReload.getKeySize());\r
+ assertArrayEquals(aevOrig.getSalt(), aevReload.getSalt());\r
+ assertEquals(aevOrig.getSpinCount(), aevReload.getSpinCount());\r
+\r
+ AgileDecryptor adOrig = (AgileDecryptor)infoOrig.getDecryptor();\r
+ AgileDecryptor adReload = (AgileDecryptor)infoReload.getDecryptor();\r
+ \r
+ assertArrayEquals(adOrig.getIntegrityHmacKey(), adReload.getIntegrityHmacKey());\r
+ // doesn't work without mocking ... see above\r
+ // assertArrayEquals(adOrig.getIntegrityHmacValue(), adReload.getIntegrityHmacValue());\r
+ assertArrayEquals(adOrig.getSecretKey().getEncoded(), adReload.getSecretKey().getEncoded());\r
+ assertArrayEquals(adOrig.getVerifier(), adReload.getVerifier());\r
+\r
+ fsReload.close();\r
+ }\r
}\r