You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by ki...@apache.org on 2013/12/25 00:13:22 UTC
svn commit: r1553336 [2/4] - in /poi: site/src/documentation/content/xdocs/
trunk/ trunk/src/java/org/apache/poi/
trunk/src/java/org/apache/poi/poifs/crypt/
trunk/src/java/org/apache/poi/poifs/crypt/standard/
trunk/src/ooxml/java/org/apache/poi/ trunk/...
Added: poi/trunk/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java (added)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,66 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.crypt;
+
+import org.apache.poi.EncryptedDocumentException;
+
+public enum HashAlgorithm {
+ none ( "", 0x0000, "", 0, "", false),
+ sha1 ( "SHA-1", 0x8004, "SHA1", 20, "HmacSHA1", false),
+ sha256 ( "SHA-256", 0x800C, "SHA256", 32, "HmacSHA256", false),
+ sha384 ( "SHA-384", 0x800D, "SHA384", 48, "HmacSHA384", false),
+ sha512 ( "SHA-512", 0x800E, "SHA512", 64, "HmacSHA512", false),
+ /* only for agile encryption */
+ md5 ( "MD5", -1, "MD5", 16, "HmacMD5", false),
+ // although sunjc2 supports md2, hmac-md2 is only supported by bouncycastle
+ md2 ( "MD2", -1, "MD2", 16, "Hmac-MD2", true),
+ ripemd128("RipeMD128", -1, "RIPEMD-128", 16, "HMac-RipeMD128", true),
+ ripemd160("RipeMD160", -1, "RIPEMD-160", 20, "HMac-RipeMD160", true),
+ whirlpool("Whirlpool", -1, "WHIRLPOOL", 64, "HMac-Whirlpool", true),
+ ;
+
+ public final String jceId;
+ public final int ecmaId;
+ public final String ecmaString;
+ public final int hashSize;
+ public final String jceHmacId;
+ public final boolean needsBouncyCastle;
+
+ HashAlgorithm(String jceId, int ecmaId, String ecmaString, int hashSize, String jceHmacId, boolean needsBouncyCastle) {
+ this.jceId = jceId;
+ this.ecmaId = ecmaId;
+ this.ecmaString = ecmaString;
+ this.hashSize = hashSize;
+ this.jceHmacId = jceHmacId;
+ this.needsBouncyCastle = needsBouncyCastle;
+ }
+
+ public static HashAlgorithm fromEcmaId(int ecmaId) {
+ for (HashAlgorithm ha : values()) {
+ if (ha.ecmaId == ecmaId) return ha;
+ }
+ throw new EncryptedDocumentException("hash algorithm not found");
+ }
+
+ public static HashAlgorithm fromEcmaId(String ecmaString) {
+ for (HashAlgorithm ha : values()) {
+ if (ha.ecmaString.equals(ecmaString)) return ha;
+ }
+ throw new EncryptedDocumentException("hash algorithm not found");
+ }
+}
\ No newline at end of file
Added: poi/trunk/src/java/org/apache/poi/poifs/crypt/package.html
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/package.html?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/package.html (added)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/package.html Tue Dec 24 23:13:21 2013
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<!--
+ ====================================================================
+ 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.
+ ====================================================================
+-->
+<html>
+<head>
+</head>
+<body bgcolor="white">
+
+<p>Implementation of the <a href="http://msdn.microsoft.com/en-us/library/dd952186(v=office.12).aspx">ECMA-376 Document Encryption</a></p>
+<p>The implementation is split into three packages:</p>
+<ul>
+<li>This package contains common functions for both current implemented cipher modes.</li>
+<li>the {@link org.apache.poi.poifs.crypt.standard standard} package is part of the base poi jar and contains classes for the standard encryption ...</li>
+<li>the {@link org.apache.poi.poifs.crypt.agile agile} package is part of the poi ooxml jar and the provides agile encryption support.</li>
+</ul>
+
+<h2>Related Documentation</h2>
+
+Some implementations informations can be found under:
+<ul>
+<li><a href="http://poi.apache.org/encryption.html">Apache POI - Encryption support</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+@see org.apache.poi.poifs.crypt.standard
+@see org.apache.poi.poifs.crypt.agile
+</body>
+</html>
Added: poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/EncryptionRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/EncryptionRecord.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/EncryptionRecord.java (added)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/EncryptionRecord.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,23 @@
+/* ====================================================================
+ 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.standard;
+
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+
+public interface EncryptionRecord {
+ void write(LittleEndianByteArrayOutputStream os);
+}
Copied: poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java (from r1541009, poi/trunk/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java)
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java?p2=poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java&p1=poi/trunk/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java&r1=1541009&r2=1553336&rev=1553336&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java Tue Dec 24 23:13:21 2013
@@ -14,7 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-package org.apache.poi.poifs.crypt;
+package org.apache.poi.poifs.crypt.standard;
+
+import static org.apache.poi.poifs.crypt.CryptoFunctions.hashPassword;
import java.io.IOException;
import java.io.InputStream;
@@ -28,70 +30,90 @@ import javax.crypto.CipherInputStream;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
+import org.apache.poi.EncryptedDocumentException;
+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.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.util.BoundedInputStream;
import org.apache.poi.util.LittleEndian;
/**
*/
-public class EcmaDecryptor extends Decryptor {
- private final EncryptionInfo info;
- private byte[] passwordHash;
+public class StandardDecryptor extends Decryptor {
private long _length = -1;
- public EcmaDecryptor(EncryptionInfo info) {
- this.info = info;
+ protected StandardDecryptor(EncryptionInfo info) {
+ super(info);
}
- private byte[] generateKey(int block) throws NoSuchAlgorithmException {
- MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
-
- sha1.update(passwordHash);
- byte[] blockValue = new byte[4];
- LittleEndian.putInt(blockValue, 0, block);
- byte[] finalHash = sha1.digest(blockValue);
-
- int requiredKeyLength = info.getHeader().getKeySize()/8;
-
- byte[] buff = new byte[64];
-
- Arrays.fill(buff, (byte) 0x36);
-
- for (int i=0; i<finalHash.length; i++) {
- buff[i] = (byte) (buff[i] ^ finalHash[i]);
+ public boolean verifyPassword(String password) {
+ EncryptionVerifier ver = info.getVerifier();
+ SecretKey skey = generateSecretKey(password, ver, getKeySizeInBytes());
+ Cipher cipher = getCipher(skey);
+
+ try {
+ byte encryptedVerifier[] = ver.getEncryptedVerifier();
+ byte verifier[] = cipher.doFinal(encryptedVerifier);
+ setVerifier(verifier);
+ MessageDigest sha1 = MessageDigest.getInstance(ver.getHashAlgorithm().jceId);
+ byte[] calcVerifierHash = sha1.digest(verifier);
+ byte encryptedVerifierHash[] = ver.getEncryptedVerifierHash();
+ byte decryptedVerifierHash[] = cipher.doFinal(encryptedVerifierHash);
+ byte[] verifierHash = truncateOrPad(decryptedVerifierHash, calcVerifierHash.length);
+
+ if (Arrays.equals(calcVerifierHash, verifierHash)) {
+ setSecretKey(skey);
+ return true;
+ } else {
+ return false;
+ }
+ } catch (GeneralSecurityException e) {
+ throw new EncryptedDocumentException(e);
}
-
- sha1.reset();
- byte[] x1 = sha1.digest(buff);
-
- Arrays.fill(buff, (byte) 0x5c);
- for (int i=0; i<finalHash.length; i++) {
- buff[i] = (byte) (buff[i] ^ finalHash[i]);
- }
-
- sha1.reset();
- byte[] x2 = sha1.digest(buff);
+ }
+
+ protected static SecretKey generateSecretKey(String password, EncryptionVerifier ver, int keySize) {
+ HashAlgorithm hashAlgo = ver.getHashAlgorithm();
+
+ byte pwHash[] = hashPassword(password, hashAlgo, ver.getSalt(), ver.getSpinCount());
+
+ byte[] blockKey = new byte[4];
+ LittleEndian.putInt(blockKey, 0, 0);
+
+ byte[] finalHash = CryptoFunctions.generateKey(pwHash, hashAlgo, blockKey, hashAlgo.hashSize);
+ byte x1[] = fillAndXor(finalHash, (byte) 0x36);
+ byte x2[] = fillAndXor(finalHash, (byte) 0x5c);
byte[] x3 = new byte[x1.length + x2.length];
System.arraycopy(x1, 0, x3, 0, x1.length);
System.arraycopy(x2, 0, x3, x1.length, x2.length);
+
+ byte[] key = truncateOrPad(x3, keySize);
- return truncateOrPad(x3, requiredKeyLength);
+ SecretKey skey = new SecretKeySpec(key, ver.getCipherAlgorithm().jceId);
+ return skey;
}
- public boolean verifyPassword(String password) throws GeneralSecurityException {
- passwordHash = hashPassword(info, password);
-
- Cipher cipher = getCipher();
-
- byte[] verifier = cipher.doFinal(info.getVerifier().getVerifier());
-
- MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
- byte[] calcVerifierHash = sha1.digest(verifier);
+ protected static byte[] fillAndXor(byte hash[], byte fillByte) {
+ byte[] buff = new byte[64];
+ Arrays.fill(buff, fillByte);
- byte[] verifierHash = truncateOrPad(cipher.doFinal(info.getVerifier().getVerifierHash()), calcVerifierHash.length);
+ for (int i=0; i<hash.length; i++) {
+ buff[i] = (byte) (buff[i] ^ hash[i]);
+ }
- return Arrays.equals(calcVerifierHash, verifierHash);
+ try {
+ MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+ return sha1.digest(buff);
+ } catch (NoSuchAlgorithmException e) {
+ throw new EncryptedDocumentException("hash algo not supported", e);
+ }
}
/**
@@ -99,7 +121,7 @@ public class EcmaDecryptor extends Decry
* truncated or zero padded as needed.
* Behaves like Arrays.copyOf in Java 1.6
*/
- private byte[] truncateOrPad(byte[] source, int length) {
+ protected static byte[] truncateOrPad(byte[] source, int length) {
byte[] result = new byte[length];
System.arraycopy(source, 0, result, 0, Math.min(length, source.length));
if(length > source.length) {
@@ -110,25 +132,27 @@ public class EcmaDecryptor extends Decry
return result;
}
- private Cipher getCipher() throws GeneralSecurityException {
- byte[] key = generateKey(0);
- Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
- SecretKey skey = new SecretKeySpec(key, "AES");
- cipher.init(Cipher.DECRYPT_MODE, skey);
-
- return cipher;
+ private Cipher getCipher(SecretKey key) {
+ EncryptionHeader em = info.getHeader();
+ ChainingMode cm = em.getChainingMode();
+ assert(cm == ChainingMode.ecb);
+ return CryptoFunctions.getCipher(key, em.getCipherAlgorithm(), cm, null, Cipher.DECRYPT_MODE);
}
- public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
+ public InputStream getDataStream(DirectoryNode dir) throws IOException {
DocumentInputStream dis = dir.createDocumentInputStream("EncryptedPackage");
_length = dis.readLong();
- return new CipherInputStream(dis, getCipher());
+ return new BoundedInputStream(new CipherInputStream(dis, getCipher(getSecretKey())), _length);
}
public long getLength(){
- if(_length == -1) throw new IllegalStateException("EcmaDecryptor.getDataStream() was not called");
+ if(_length == -1) throw new IllegalStateException("Decryptor.getDataStream() was not called");
return _length;
}
+
+ protected int getKeySizeInBytes() {
+ return info.getHeader().getKeySize()/8;
+ }
}
Added: poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java (added)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,116 @@
+/* ====================================================================
+ 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.standard;
+
+import static org.apache.poi.poifs.crypt.CryptoFunctions.getUtf16LeString;
+
+import java.io.IOException;
+
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.CipherProvider;
+import org.apache.poi.poifs.crypt.EncryptionHeader;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianOutput;
+
+public class StandardEncryptionHeader extends EncryptionHeader implements EncryptionRecord {
+ // A flag that specifies whether CryptoAPI RC4 or ECMA-376 encryption
+ // [ECMA-376] is used. It MUST be 1 unless fExternal is 1. If fExternal is 1, it MUST be 0.
+ private static BitField flagsCryptoAPI = new BitField(0x04);
+
+ // A value that MUST be 0 if document properties are encrypted. The
+ // encryption of document properties is specified in section 2.3.5.4 [MS-OFFCRYPTO].
+ @SuppressWarnings("unused")
+ private static BitField flagsDocProps = new BitField(0x08);
+
+ // A value that MUST be 1 if extensible encryption is used,. If this value is 1,
+ // the value of every other field in this structure MUST be 0.
+ @SuppressWarnings("unused")
+ private static BitField flagsExternal = new BitField(0x10);
+
+ // A value that MUST be 1 if the protected content is an ECMA-376 document
+ // [ECMA-376]. If the fAES bit is 1, the fCryptoAPI bit MUST also be 1.
+ private static BitField flagsAES = new BitField(0x20);
+
+ protected StandardEncryptionHeader(DocumentInputStream is) throws IOException {
+ setFlags(is.readInt());
+ setSizeExtra(is.readInt());
+ setCipherAlgorithm(CipherAlgorithm.fromEcmaId(is.readInt()));
+ setHashAlgorithm(HashAlgorithm.fromEcmaId(is.readInt()));
+ setKeySize(is.readInt());
+ setBlockSize(getKeySize());
+ setCipherProvider(CipherProvider.fromEcmaId(is.readInt()));
+
+ is.readLong(); // skip reserved
+
+ // CSPName may not always be specified
+ // In some cases, the salt value of the EncryptionVerifier is the next chunk of data
+ is.mark(LittleEndianConsts.INT_SIZE+1);
+ int checkForSalt = is.readInt();
+ is.reset();
+
+ if (checkForSalt == 16) {
+ setCspName("");
+ } else {
+ StringBuilder builder = new StringBuilder();
+ while (true) {
+ char c = (char) is.readShort();
+ if (c == 0) break;
+ builder.append(c);
+ }
+ setCspName(builder.toString());
+ }
+
+ setChainingMode(ChainingMode.ecb);
+ setKeySalt(null);
+ }
+
+ protected StandardEncryptionHeader(CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
+ setCipherAlgorithm(cipherAlgorithm);
+ setHashAlgorithm(hashAlgorithm);
+ setKeySize(keyBits);
+ setBlockSize(blockSize);
+ setCipherProvider(cipherAlgorithm.provider);
+ setFlags(flagsCryptoAPI.setBoolean(0, true)
+ | flagsAES.setBoolean(0, cipherAlgorithm.provider == CipherProvider.aes));
+ // see http://msdn.microsoft.com/en-us/library/windows/desktop/bb931357(v=vs.85).aspx for a full list
+ // setCspName("Microsoft Enhanced RSA and AES Cryptographic Provider");
+ }
+
+ public void write(LittleEndianByteArrayOutputStream bos) {
+ int startIdx = bos.getWriteIndex();
+ LittleEndianOutput sizeOutput = bos.createDelayedOutput(LittleEndianConsts.INT_SIZE);
+ bos.writeInt(getFlags());
+ bos.writeInt(0); // size extra
+ bos.writeInt(getCipherAlgorithm().ecmaId);
+ bos.writeInt(getHashAlgorithmEx().ecmaId);
+ bos.writeInt(getKeySize());
+ bos.writeInt(getCipherProvider().ecmaId);
+ bos.writeInt(0); // reserved1
+ bos.writeInt(0); // reserved2
+ if (getCspName() != null) {
+ bos.write(getUtf16LeString(getCspName()));
+ bos.writeShort(0);
+ }
+ int headerSize = bos.getWriteIndex()-startIdx-LittleEndianConsts.INT_SIZE;
+ sizeOutput.writeInt(headerSize);
+ }
+}
Added: poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java (added)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,106 @@
+/* ====================================================================
+ 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.standard;
+
+import java.io.IOException;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+
+public class StandardEncryptionInfoBuilder implements EncryptionInfoBuilder {
+
+ EncryptionInfo info;
+ StandardEncryptionHeader header;
+ StandardEncryptionVerifier verifier;
+ StandardDecryptor decryptor;
+ StandardEncryptor encryptor;
+
+ public void initialize(EncryptionInfo info, DocumentInputStream dis) throws IOException {
+ this.info = info;
+
+ @SuppressWarnings("unused")
+ int hSize = dis.readInt();
+ header = new StandardEncryptionHeader(dis);
+ verifier = new StandardEncryptionVerifier(dis, header);
+
+ if (info.getVersionMinor() == 2 && (info.getVersionMajor() == 3 || info.getVersionMajor() == 4)) {
+ decryptor = new StandardDecryptor(info);
+ }
+ }
+
+ 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 (hashAlgorithm != HashAlgorithm.sha1) {
+ throw new EncryptedDocumentException("Standard encryption only supports SHA-1.");
+ }
+ if (chainingMode == null) {
+ chainingMode = ChainingMode.ecb;
+ }
+ if (chainingMode != ChainingMode.ecb) {
+ throw new EncryptedDocumentException("Standard encryption only supports ECB chaining.");
+ }
+ if (keyBits == -1) {
+ keyBits = cipherAlgorithm.defaultKeySize;
+ }
+ if (blockSize == -1) {
+ blockSize = cipherAlgorithm.blockSize;
+ }
+ boolean found = false;
+ for (int ks : cipherAlgorithm.allowedKeySize) {
+ found |= (ks == keyBits);
+ }
+ 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(info);
+ 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;
+ }
+}
Added: poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java (added)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,112 @@
+/* ====================================================================
+ 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.standard;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.EncryptionVerifier;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+
+/**
+ * Used when checking if a key is valid for a document
+ */
+public class StandardEncryptionVerifier extends EncryptionVerifier implements EncryptionRecord {
+ private static final int SPIN_COUNT = 50000;
+ private final int verifierHashSize;
+
+ protected StandardEncryptionVerifier(DocumentInputStream is, StandardEncryptionHeader header) {
+ int saltSize = is.readInt();
+
+ if (saltSize!=16) {
+ throw new RuntimeException("Salt size != 16 !?");
+ }
+
+ byte salt[] = new byte[16];
+ is.readFully(salt);
+ setSalt(salt);
+
+ byte encryptedVerifier[] = new byte[16];
+ is.readFully(encryptedVerifier);
+ setEncryptedVerifier(encryptedVerifier);
+
+ verifierHashSize = is.readInt();
+
+ byte encryptedVerifierHash[] = new byte[header.getCipherAlgorithm().encryptedVerifierHashLength];
+ is.readFully(encryptedVerifierHash);
+ setEncryptedVerifierHash(encryptedVerifierHash);
+
+ setSpinCount(SPIN_COUNT);
+ setCipherAlgorithm(CipherAlgorithm.aes128);
+ setChainingMode(ChainingMode.ecb);
+ setEncryptedKey(null);
+ setHashAlgorithm(HashAlgorithm.sha1);
+ }
+
+ protected StandardEncryptionVerifier(CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
+ setCipherAlgorithm(cipherAlgorithm);
+ setHashAlgorithm(hashAlgorithm);
+ setChainingMode(chainingMode);
+ setSpinCount(SPIN_COUNT);
+ verifierHashSize = hashAlgorithm.hashSize;
+ }
+
+ // make method visible for this package
+ protected void setSalt(byte salt[]) {
+ if (salt == null || salt.length != 16) {
+ throw new EncryptedDocumentException("invalid verifier salt");
+ }
+ super.setSalt(salt);
+ }
+
+ // make method visible for this package
+ protected void setEncryptedVerifier(byte encryptedVerifier[]) {
+ super.setEncryptedVerifier(encryptedVerifier);
+ }
+
+ // make method visible for this package
+ protected void setEncryptedVerifierHash(byte encryptedVerifierHash[]) {
+ super.setEncryptedVerifierHash(encryptedVerifierHash);
+ }
+
+ public void write(LittleEndianByteArrayOutputStream bos) {
+ // see [MS-OFFCRYPTO] - 2.3.4.9
+ byte salt[] = getSalt();
+ assert(salt.length == 16);
+ bos.writeInt(salt.length); // salt size
+ bos.write(salt);
+
+ // The resulting Verifier value MUST be an array of 16 bytes.
+ byte encryptedVerifier[] = getEncryptedVerifier();
+ assert(encryptedVerifier.length == 16);
+ bos.write(encryptedVerifier);
+
+ // The number of bytes used by the encrypted Verifier hash MUST be 32.
+ // The number of bytes used by the decrypted Verifier hash is given by
+ // the VerifierHashSize field, which MUST be 20
+ byte encryptedVerifierHash[] = getEncryptedVerifierHash();
+ assert(encryptedVerifierHash.length == 32);
+ bos.writeInt(20);
+ bos.write(encryptedVerifierHash);
+ }
+
+ protected int getVerifierHashSize() {
+ return verifierHashSize;
+ }
+}
Added: poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java (added)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,218 @@
+/* ====================================================================
+ 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.standard;
+
+import static org.apache.poi.poifs.crypt.DataSpaceMapUtils.createEncryptionEntry;
+import static org.apache.poi.poifs.crypt.standard.StandardDecryptor.generateSecretKey;
+import static org.apache.poi.poifs.crypt.standard.StandardDecryptor.truncateOrPad;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.Random;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.SecretKey;
+
+import org.apache.poi.EncryptedDocumentException;
+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.EncryptionVerifier;
+import org.apache.poi.poifs.crypt.Encryptor;
+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.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianOutputStream;
+import org.apache.poi.util.TempFile;
+
+public class StandardEncryptor extends Encryptor {
+ private final StandardEncryptionInfoBuilder builder;
+
+ protected StandardEncryptor(StandardEncryptionInfoBuilder builder) {
+ this.builder = builder;
+ }
+
+ public void confirmPassword(String password) {
+ // see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier
+ Random r = new SecureRandom();
+ byte[] salt = new byte[16], verifier = new byte[16];
+ r.nextBytes(salt);
+ r.nextBytes(verifier);
+
+ confirmPassword(password, null, null, salt, verifier, null);
+ }
+
+
+ /**
+ * Fills the fields of verifier and header with the calculated hashes based
+ * on the password and a random salt
+ *
+ * see [MS-OFFCRYPTO] - 2.3.4.7 ECMA-376 Document Encryption Key Generation
+ */
+ public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) {
+ StandardEncryptionVerifier ver = builder.getVerifier();
+
+ ver.setSalt(verifierSalt);
+ SecretKey secretKey = generateSecretKey(password, ver, getKeySizeInBytes());
+ setSecretKey(secretKey);
+ Cipher cipher = getCipher(secretKey, null);
+
+ try {
+ byte encryptedVerifier[] = cipher.doFinal(verifier);
+ MessageDigest hashAlgo = MessageDigest.getInstance(ver.getHashAlgorithm().jceId);
+ byte calcVerifierHash[] = hashAlgo.digest(verifier);
+
+ // 2.3.3 EncryptionVerifier ...
+ // An array of bytes that contains the encrypted form of the
+ // hash of the randomly generated Verifier value. The length of the array MUST be the size of
+ // the encryption block size multiplied by the number of blocks needed to encrypt the hash of the
+ // Verifier. If the encryption algorithm is RC4, the length MUST be 20 bytes. If the encryption
+ // algorithm is AES, the length MUST be 32 bytes. After decrypting the EncryptedVerifierHash
+ // field, only the first VerifierHashSize bytes MUST be used.
+ int encVerHashSize = ver.getCipherAlgorithm().encryptedVerifierHashLength;
+ byte encryptedVerifierHash[] = cipher.doFinal(truncateOrPad(calcVerifierHash, encVerHashSize));
+
+ ver.setEncryptedVerifier(encryptedVerifier);
+ ver.setEncryptedVerifierHash(encryptedVerifierHash);
+ } catch (GeneralSecurityException e) {
+ throw new EncryptedDocumentException("Password confirmation failed", e);
+ }
+
+ }
+
+ private Cipher getCipher(SecretKey key, String padding) {
+ EncryptionVerifier ver = builder.getVerifier();
+ return CryptoFunctions.getCipher(key, ver.getCipherAlgorithm(), ver.getChainingMode(), null, Cipher.ENCRYPT_MODE, padding);
+ }
+
+ public OutputStream getDataStream(final DirectoryNode dir)
+ throws IOException, GeneralSecurityException {
+ createEncryptionInfoEntry(dir);
+ DataSpaceMapUtils.addDefaultDataSpace(dir);
+ OutputStream countStream = new StandardCipherOutputStream(dir);
+ return countStream;
+ }
+
+ protected class StandardCipherOutputStream extends FilterOutputStream implements POIFSWriterListener {
+ protected long countBytes;
+ protected final File fileOut;
+ protected final DirectoryNode dir;
+
+ protected StandardCipherOutputStream(DirectoryNode dir) throws IOException {
+ super(null);
+
+ this.dir = dir;
+ fileOut = TempFile.createTempFile("encrypted_package", "crypt");
+ FileOutputStream rawStream = new FileOutputStream(fileOut);
+
+ // although not documented, we need the same padding as with agile encryption
+ // and instead of calculating the missing bytes for the block size ourselves
+ // we leave it up to the CipherOutputStream, which generates/saves them on close()
+ // ... we can't use "NoPadding" here
+ //
+ // see also [MS-OFFCRYPT] - 2.3.4.15
+ // The final data block MUST be padded to the next integral multiple of the
+ // KeyData.blockSize value. Any padding bytes can be used. Note that the StreamSize
+ // field of the EncryptedPackage field specifies the number of bytes of
+ // unencrypted data as specified in section 2.3.4.4.
+ CipherOutputStream cryptStream = new CipherOutputStream(rawStream, getCipher(getSecretKey(), "PKCS5Padding"));
+
+ this.out = cryptStream;
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ out.write(b, off, len);
+ countBytes += len;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ out.write(b);
+ countBytes++;
+ }
+
+ public void close() throws IOException {
+ // the CipherOutputStream adds the padding bytes on close()
+ super.close();
+ writeToPOIFS();
+ }
+
+ void writeToPOIFS() throws IOException {
+ int oleStreamSize = (int)(fileOut.length()+LittleEndianConsts.LONG_SIZE);
+ dir.createDocument("EncryptedPackage", oleStreamSize, this);
+ // TODO: any properties???
+ }
+
+ public void processPOIFSWriterEvent(POIFSWriterEvent event) {
+ try {
+ LittleEndianOutputStream leos = new LittleEndianOutputStream(event.getStream());
+
+ // 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
+ leos.writeLong(countBytes);
+
+ FileInputStream fis = new FileInputStream(fileOut);
+ IOUtils.copy(fis, leos);
+ fis.close();
+ fileOut.delete();
+
+ leos.close();
+ } catch (IOException e) {
+ throw new EncryptedDocumentException(e);
+ }
+ }
+ }
+
+ protected int getKeySizeInBytes() {
+ return builder.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();
+
+ EncryptionRecord er = new EncryptionRecord(){
+ public void write(LittleEndianByteArrayOutputStream bos) {
+ bos.writeShort(info.getVersionMajor());
+ bos.writeShort(info.getVersionMinor());
+ bos.writeInt(info.getEncryptionFlags());
+ header.write(bos);
+ verifier.write(bos);
+ }
+ };
+
+ createEncryptionEntry(dir, "EncryptionInfo", er);
+
+ // TODO: any properties???
+ }
+}
Modified: poi/trunk/src/ooxml/java/org/apache/poi/POIXMLException.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/POIXMLException.java?rev=1553336&r1=1553335&r2=1553336&view=diff
==============================================================================
--- poi/trunk/src/ooxml/java/org/apache/poi/POIXMLException.java (original)
+++ poi/trunk/src/ooxml/java/org/apache/poi/POIXMLException.java Tue Dec 24 23:13:21 2013
@@ -21,6 +21,7 @@ package org.apache.poi;
*
* @author Yegor Kozlov
*/
+@SuppressWarnings("serial")
public final class POIXMLException extends RuntimeException{
/**
* Create a new <code>POIXMLException</code> with no
Copied: poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java (from r1544367, poi/trunk/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java)
URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java?p2=poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java&p1=poi/trunk/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java&r1=1544367&r2=1553336&rev=1553336&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java (original)
+++ poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java Tue Dec 24 23:13:21 2013
@@ -14,37 +14,55 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-package org.apache.poi.poifs.crypt;
+package org.apache.poi.poifs.crypt.agile;
+
+import static org.apache.poi.poifs.crypt.CryptoFunctions.generateIv;
+import static org.apache.poi.poifs.crypt.CryptoFunctions.generateKey;
+import static org.apache.poi.poifs.crypt.CryptoFunctions.getBlock0;
+import static org.apache.poi.poifs.crypt.CryptoFunctions.getCipher;
+import static org.apache.poi.poifs.crypt.CryptoFunctions.getMessageDigest;
+import static org.apache.poi.poifs.crypt.CryptoFunctions.hashPassword;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
+import java.security.KeyPair;
import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
import java.util.Arrays;
import javax.crypto.Cipher;
+import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.poi.EncryptedDocumentException;
+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.EncryptionVerifier;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier.AgileCertificateEntry;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.util.LittleEndian;
/**
- *
+ * Decryptor implementation for Agile Encryption
*/
public class AgileDecryptor extends Decryptor {
+ private final AgileEncryptionInfoBuilder builder;
+
- private final EncryptionInfo _info;
- private SecretKey _secretKey;
private long _length = -1;
- private static final byte[] kVerifierInputBlock;
- private static final byte[] kHashedVerifierBlock;
- private static final byte[] kCryptoKeyBlock;
+ 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;
static {
kVerifierInputBlock =
@@ -56,50 +74,215 @@ public class AgileDecryptor extends Decr
kCryptoKeyBlock =
new byte[] { (byte)0x14, (byte)0x6e, (byte)0x0b, (byte)0xe7,
(byte)0xab, (byte)0xac, (byte)0xd0, (byte)0xd6 };
+ kIntegrityKeyBlock =
+ new byte[] { (byte)0x5f, (byte)0xb2, (byte)0xad, (byte)0x01,
+ (byte)0x0c, (byte)0xb9, (byte)0xe1, (byte)0xf6 };
+ kIntegrityValueBlock =
+ new byte[] { (byte)0xa0, (byte)0x67, (byte)0x7f, (byte)0x02,
+ (byte)0xb2, (byte)0x2c, (byte)0x84, (byte)0x33 };
}
+ protected AgileDecryptor(AgileEncryptionInfoBuilder builder) {
+ super(builder.getInfo());
+ this.builder = builder;
+ }
+
+ /**
+ * set decryption password
+ */
public boolean verifyPassword(String password) throws GeneralSecurityException {
- EncryptionVerifier verifier = _info.getVerifier();
- byte[] salt = verifier.getSalt();
-
- byte[] pwHash = hashPassword(_info, password);
- byte[] iv = generateIv(salt, null);
+ AgileEncryptionVerifier ver = builder.getVerifier();
+ AgileEncryptionHeader header = builder.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());
+
+ /**
+ * encryptedVerifierHashInput: This attribute MUST be generated by using the following steps:
+ * 1. Generate a random array of bytes with the number of bytes used specified by the saltSize
+ * attribute.
+ * 2. Generate an encryption key as specified in section 2.3.4.11 by using the user-supplied password,
+ * the binary byte array used to create the saltValue attribute, and a blockKey byte array
+ * consisting of the following bytes: 0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, and 0x79.
+ * 3. Encrypt the random array of bytes generated in step 1 by using the binary form of the saltValue
+ * attribute as an initialization vector as specified in section 2.3.4.12. If the array of bytes is not an
+ * integral multiple of blockSize bytes, pad the array with 0x00 to the next integral multiple of
+ * blockSize bytes.
+ * 4. Use base64 to encode the result of step 3.
+ */
+ byte verfierInputEnc[] = hashInput(builder, pwHash, kVerifierInputBlock, ver.getEncryptedVerifier(), Cipher.DECRYPT_MODE);
+ setVerifier(verfierInputEnc);
+ MessageDigest hashMD = getMessageDigest(hashAlgo);
+ byte[] verifierHash = hashMD.digest(verfierInputEnc);
+
+ /**
+ * encryptedVerifierHashValue: This attribute MUST be generated by using the following steps:
+ * 1. Obtain the hash value of the random array of bytes generated in step 1 of the steps for
+ * encryptedVerifierHashInput.
+ * 2. Generate an encryption key as specified in section 2.3.4.11 by using the user-supplied password,
+ * the binary byte array used to create the saltValue attribute, and a blockKey byte array
+ * consisting of the following bytes: 0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, and 0x4e.
+ * 3. Encrypt the hash value obtained in step 1 by using the binary form of the saltValue attribute as
+ * an initialization vector as specified in section 2.3.4.12. If hashSize is not an integral multiple of
+ * 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);
+ verifierHashDec = getBlock0(verifierHashDec, hashAlgo.hashSize);
+
+ /**
+ * encryptedKeyValue: This attribute MUST be generated by using the following steps:
+ * 1. Generate a random array of bytes that is the same size as specified by the
+ * Encryptor.KeyData.keyBits attribute of the parent element.
+ * 2. Generate an encryption key as specified in section 2.3.4.11, using the user-supplied password,
+ * the binary byte array used to create the saltValue attribute, and a blockKey byte array
+ * consisting of the following bytes: 0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, and 0xd6.
+ * 3. Encrypt the random array of bytes generated in step 1 by using the binary form of the saltValue
+ * attribute as an initialization vector as specified in section 2.3.4.12. If the array of bytes is not an
+ * integral multiple of blockSize bytes, pad the array with 0x00 to an integral multiple of
+ * blockSize bytes.
+ * 4. Use base64 to encode the result of step 3.
+ */
+ byte keyspec[] = hashInput(builder, pwHash, kCryptoKeyBlock, ver.getEncryptedKey(), Cipher.DECRYPT_MODE);
+ keyspec = getBlock0(keyspec, keySize);
+ SecretKeySpec secretKey = new SecretKeySpec(keyspec, ver.getCipherAlgorithm().jceId);
+
+ /**
+ * 1. Obtain the intermediate key by decrypting the encryptedKeyValue from a KeyEncryptor
+ * contained within the KeyEncryptors sequence. Use this key for encryption operations in the
+ * remaining steps of this section.
+ * 2. Generate a random array of bytes, known as Salt, of the same length as the value of the
+ * KeyData.hashSize attribute.
+ * 3. Encrypt the random array of bytes generated in step 2 by using the binary form of the
+ * KeyData.saltValue attribute and a blockKey byte array consisting of the following bytes: 0x5f,
+ * 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, and 0xf6 used to form an initialization vector as specified in
+ * section 2.3.4.12. If the array of bytes is not an integral multiple of blockSize bytes, pad the
+ * 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 hmacKey[] = cipher.doFinal(header.getEncryptedHmacKey());
+ hmacKey = getBlock0(hmacKey, hashAlgo.hashSize);
+
+ /**
+ * 5. Generate an HMAC, as specified in [RFC2104], of the encrypted form of the data (message),
+ * which the DataIntegrity element will verify by using the Salt generated in step 2 as the key.
+ * Note that the entire EncryptedPackage stream (1), including the StreamSize field, MUST be
+ * used as the message.
+ * 6. Encrypt the HMAC as in step 3 by using a blockKey byte array consisting of the following bytes:
+ * 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);
+ cipher = getCipher(secretKey, cipherAlgo, ver.getChainingMode(), vec, Cipher.DECRYPT_MODE);
+ byte hmacValue[] = cipher.doFinal(header.getEncryptedHmacValue());
+ hmacValue = getBlock0(hmacValue, hashAlgo.hashSize);
+
+ if (Arrays.equals(verifierHashDec, verifierHash)) {
+ setSecretKey(secretKey);
+ setIntegrityHmacKey(hmacKey);
+ setIntegrityHmacValue(hmacValue);
+ return true;
+ } else {
+ return false;
+ }
+ }
- SecretKey skey;
- skey = new SecretKeySpec(generateKey(pwHash, kVerifierInputBlock), "AES");
- Cipher cipher = getCipher(skey, iv);
- byte[] verifierHashInput = cipher.doFinal(verifier.getVerifier());
-
- MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
- byte[] trimmed = new byte[salt.length];
- System.arraycopy(verifierHashInput, 0, trimmed, 0, trimmed.length);
- byte[] hashedVerifier = sha1.digest(trimmed);
-
- skey = new SecretKeySpec(generateKey(pwHash, kHashedVerifierBlock), "AES");
- iv = generateIv(salt, null);
- cipher = getCipher(skey, iv);
- byte[] verifierHash = cipher.doFinal(verifier.getVerifierHash());
- trimmed = new byte[hashedVerifier.length];
- System.arraycopy(verifierHash, 0, trimmed, 0, trimmed.length);
-
- if (Arrays.equals(trimmed, hashedVerifier)) {
- skey = new SecretKeySpec(generateKey(pwHash, kCryptoKeyBlock), "AES");
- iv = generateIv(salt, null);
- cipher = getCipher(skey, iv);
- byte[] inter = cipher.doFinal(verifier.getEncryptedKey());
- byte[] keyspec = new byte[getKeySizeInBytes()];
- System.arraycopy(inter, 0, keyspec, 0, keyspec.length);
- _secretKey = new SecretKeySpec(keyspec, "AES");
+ /**
+ * instead of a password, it's also possible to decrypt via certificate.
+ * Warning: this code is experimental and hasn't been validated
+ *
+ * {@linkplain http://social.msdn.microsoft.com/Forums/en-US/cc9092bb-0c82-4b5b-ae21-abf643bdb37c/agile-encryption-with-certificates}
+ *
+ * @param keyPair
+ * @param x509
+ * @return
+ * @throws GeneralSecurityException
+ */
+ public boolean verifyPassword(KeyPair keyPair, X509Certificate x509) throws GeneralSecurityException {
+ AgileEncryptionVerifier ver = builder.getVerifier();
+ AgileEncryptionHeader header = builder.getHeader();
+ HashAlgorithm hashAlgo = header.getHashAlgorithmEx();
+ CipherAlgorithm cipherAlgo = header.getCipherAlgorithm();
+ int blockSize = header.getBlockSize();
+
+ AgileCertificateEntry ace = null;
+ for (AgileCertificateEntry aceEntry : ver.getCertificates()) {
+ if (x509.equals(aceEntry.x509)) {
+ ace = aceEntry;
+ break;
+ }
+ }
+ if (ace == null) return false;
+
+ Cipher cipher = Cipher.getInstance("RSA");
+ cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
+ byte keyspec[] = cipher.doFinal(ace.encryptedKey);
+ SecretKeySpec secretKey = new SecretKeySpec(keyspec, ver.getCipherAlgorithm().jceId);
+
+ Mac x509Hmac = CryptoFunctions.getMac(hashAlgo);
+ x509Hmac.init(secretKey);
+ 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);
+ 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);
+ byte hmacValue[] = cipher.doFinal(header.getEncryptedHmacValue());
+ hmacValue = getBlock0(hmacValue, hashAlgo.hashSize);
+
+
+ if (Arrays.equals(ace.certVerifier, certVerifier)) {
+ setSecretKey(secretKey);
+ setIntegrityHmacKey(hmacKey);
+ setIntegrityHmacValue(hmacValue);
return true;
} else {
return false;
}
}
+ protected static int getNextBlockSize(int inputLen, int blockSize) {
+ int fillSize;
+ for (fillSize=blockSize; fillSize<inputLen; fillSize+=blockSize);
+ return fillSize;
+ }
+
+ protected static byte[] hashInput(AgileEncryptionInfoBuilder builder, byte pwHash[], byte blockKey[], byte inputKey[], int cipherMode) {
+ EncryptionVerifier ver = builder.getVerifier();
+ int keySize = builder.getDecryptor().getKeySizeInBytes();
+ int blockSize = builder.getDecryptor().getBlockSizeInBytes();
+ 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);
+ byte[] hashFinal;
+
+ try {
+ inputKey = getBlock0(inputKey, getNextBlockSize(inputKey.length, blockSize));
+ hashFinal = cipher.doFinal(inputKey);
+ return hashFinal;
+ } catch (GeneralSecurityException e) {
+ throw new EncryptedDocumentException(e);
+ }
+ }
+
public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
DocumentInputStream dis = dir.createDocumentInputStream("EncryptedPackage");
_length = dis.readLong();
- return new ChunkedCipherInputStream(dis, _length);
+
+ ChunkedCipherInputStream cipherStream = new ChunkedCipherInputStream(dis, _length);
+ return cipherStream;
}
public long getLength(){
@@ -107,23 +290,35 @@ public class AgileDecryptor extends Decr
return _length;
}
- protected AgileDecryptor(EncryptionInfo info) {
- _info = info;
- }
-
+ /**
+ * 2.3.4.15 Data Encryption (Agile Encryption)
+ *
+ * The EncryptedPackage stream (1) MUST be encrypted in 4096-byte segments to facilitate nearly
+ * random access while allowing CBC modes to be used in the encryption process.
+ * The initialization vector for the encryption process MUST be obtained by using the zero-based
+ * segment number as a blockKey and the binary form of the KeyData.saltValue as specified in
+ * section 2.3.4.12. The block number MUST be represented as a 32-bit unsigned integer.
+ * Data blocks MUST then be encrypted by using the initialization vector and the intermediate key
+ * obtained by decrypting the encryptedKeyValue from a KeyEncryptor contained within the
+ * KeyEncryptors sequence as specified in section 2.3.4.10. The final data block MUST be padded to
+ * the next integral multiple of the KeyData.blockSize value. Any padding bytes can be used. Note
+ * that the StreamSize field of the EncryptedPackage field specifies the number of bytes of
+ * unencrypted data as specified in section 2.3.4.4.
+ */
private class ChunkedCipherInputStream extends InputStream {
private int _lastIndex = 0;
private long _pos = 0;
private final long _size;
- private final DocumentInputStream _stream;
+ private final InputStream _stream;
private byte[] _chunk;
private Cipher _cipher;
public ChunkedCipherInputStream(DocumentInputStream stream, long size)
throws GeneralSecurityException {
+ EncryptionHeader header = info.getHeader();
_size = size;
_stream = stream;
- _cipher = getCipher(_secretKey, _info.getHeader().getKeySalt());
+ _cipher = getCipher(getSecretKey(), header.getCipherAlgorithm(), header.getChainingMode(), header.getKeySalt(), Cipher.DECRYPT_MODE);
}
public int read() throws IOException {
@@ -139,6 +334,8 @@ public class AgileDecryptor extends Decr
public int read(byte[] b, int off, int len) throws IOException {
int total = 0;
+
+ if (available() <= 0) return -1;
while (len > 0) {
if (_chunk == null) {
@@ -149,7 +346,11 @@ public class AgileDecryptor extends Decr
}
}
int count = (int)(4096L - (_pos & 0xfff));
- count = Math.min(available(), Math.min(count, len));
+ int avail = available();
+ if (avail == 0) {
+ return total;
+ }
+ count = Math.min(avail, Math.min(count, len));
System.arraycopy(_chunk, (int)(_pos & 0xfff), b, off, count);
off += count;
len -= count;
@@ -180,89 +381,24 @@ public class AgileDecryptor extends Decr
int index = (int)(_pos >> 12);
byte[] blockKey = new byte[4];
LittleEndian.putInt(blockKey, 0, index);
- byte[] iv = generateIv(_info.getHeader().getKeySalt(), blockKey);
- _cipher.init(Cipher.DECRYPT_MODE, _secretKey, new IvParameterSpec(iv));
+ EncryptionHeader header = info.getHeader();
+ byte[] iv = generateIv(header.getHashAlgorithmEx(), header.getKeySalt(), blockKey, getBlockSizeInBytes());
+ _cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), new IvParameterSpec(iv));
if (_lastIndex != index)
_stream.skip((index - _lastIndex) << 12);
byte[] block = new byte[Math.min(_stream.available(), 4096)];
- _stream.readFully(block);
+ _stream.read(block);
_lastIndex = index + 1;
return _cipher.doFinal(block);
}
}
- private Cipher getCipher(SecretKey key, byte[] vec)
- throws GeneralSecurityException {
-
- String name = null;
- String chain = null;
-
- EncryptionVerifier verifier = _info.getVerifier();
-
- switch (verifier.getAlgorithm()) {
- case EncryptionHeader.ALGORITHM_AES_128:
- case EncryptionHeader.ALGORITHM_AES_192:
- case EncryptionHeader.ALGORITHM_AES_256:
- name = "AES";
- break;
- default:
- throw new EncryptedDocumentException("Unsupported algorithm");
- }
-
- // Ensure the JCE policies files allow for this sized key
- if (Cipher.getMaxAllowedKeyLength(name) < _info.getHeader().getKeySize()) {
- throw new EncryptedDocumentException("Export Restrictions in place - please install JCE Unlimited Strength Jurisdiction Policy files");
- }
-
- switch (verifier.getCipherMode()) {
- case EncryptionHeader.MODE_CBC:
- chain = "CBC";
- break;
- case EncryptionHeader.MODE_CFB:
- chain = "CFB";
- break;
- default:
- throw new EncryptedDocumentException("Unsupported chain mode");
- }
-
- Cipher cipher = Cipher.getInstance(name + "/" + chain + "/NoPadding");
- IvParameterSpec iv = new IvParameterSpec(vec);
- cipher.init(Cipher.DECRYPT_MODE, key, iv);
- return cipher;
- }
-
- private byte[] getBlock(byte[] hash, int size) {
- byte[] result = new byte[size];
- Arrays.fill(result, (byte)0x36);
- System.arraycopy(hash, 0, result, 0, Math.min(result.length, hash.length));
- return result;
- }
-
- private byte[] generateKey(byte[] hash, byte[] blockKey) throws NoSuchAlgorithmException {
- MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
- sha1.update(hash);
- byte[] key = sha1.digest(blockKey);
- return getBlock(key, getKeySizeInBytes());
- }
-
- protected byte[] generateIv(byte[] salt, byte[] blockKey)
- throws NoSuchAlgorithmException {
-
-
- if (blockKey == null)
- return getBlock(salt, getBlockSizeInBytes());
-
- MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
- sha1.update(salt);
- return getBlock(sha1.digest(blockKey), getBlockSizeInBytes());
- }
-
protected int getBlockSizeInBytes() {
- return _info.getHeader().getBlockSize();
+ return info.getHeader().getBlockSize();
}
protected int getKeySizeInBytes() {
- return _info.getHeader().getKeySize()/8;
+ return info.getHeader().getKeySize()/8;
}
}
Added: poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java (added)
+++ poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,130 @@
+/* ====================================================================
+ 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.agile;
+
+import java.io.IOException;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.EncryptionHeader;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.xmlbeans.XmlException;
+
+import com.microsoft.schemas.office.x2006.encryption.CTDataIntegrity;
+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 {
+ private byte encryptedHmacKey[], encryptedHmacValue[];
+
+ public AgileEncryptionHeader(String descriptor) throws IOException {
+ EncryptionDocument ed;
+ try {
+ ed = EncryptionDocument.Factory.parse(descriptor);
+ } catch (XmlException e) {
+ throw new EncryptedDocumentException("Unable to parse encryption descriptor", e);
+ }
+
+ CTKeyData keyData;
+ try {
+ keyData = ed.getEncryption().getKeyData();
+ if (keyData == null) {
+ throw new NullPointerException("keyData not set");
+ }
+ } catch (Exception e) {
+ throw new EncryptedDocumentException("Unable to parse keyData");
+ }
+
+ setKeySize((int)keyData.getKeyBits());
+ setFlags(0);
+ setSizeExtra(0);
+ setCspName(null);
+ setBlockSize(keyData.getBlockSize());
+
+ int keyBits = (int)keyData.getKeyBits();
+
+ CipherAlgorithm ca = CipherAlgorithm.fromXmlId(keyData.getCipherAlgorithm().toString(), keyBits);
+ setCipherAlgorithm(ca);
+ setCipherProvider(ca.provider);
+
+ switch (keyData.getCipherChaining().intValue()) {
+ case STCipherChaining.INT_CHAINING_MODE_CBC:
+ setChainingMode(ChainingMode.cbc);
+ break;
+ case STCipherChaining.INT_CHAINING_MODE_CFB:
+ setChainingMode(ChainingMode.cfb);
+ break;
+ default:
+ throw new EncryptedDocumentException("Unsupported chaining mode - "+keyData.getCipherChaining().toString());
+ }
+
+ int hashSize = keyData.getHashSize();
+
+ HashAlgorithm ha = HashAlgorithm.fromEcmaId(keyData.getHashAlgorithm().toString());
+ setHashAlgorithm(ha);
+
+ if (getHashAlgorithmEx().hashSize != hashSize) {
+ throw new EncryptedDocumentException("Unsupported hash algorithm: " +
+ keyData.getHashAlgorithm() + " @ " + hashSize + " bytes");
+ }
+
+ int saltLength = keyData.getSaltSize();
+ setKeySalt(keyData.getSaltValue());
+ if (getKeySalt().length != saltLength) {
+ throw new EncryptedDocumentException("Invalid salt length");
+ }
+
+ CTDataIntegrity di = ed.getEncryption().getDataIntegrity();
+ setEncryptedHmacKey(di.getEncryptedHmacKey());
+ setEncryptedHmacValue(di.getEncryptedHmacValue());
+ }
+
+
+ public AgileEncryptionHeader(CipherAlgorithm algorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
+ setCipherAlgorithm(algorithm);
+ setHashAlgorithm(hashAlgorithm);
+ setKeySize(keyBits);
+ setBlockSize(blockSize);
+ setChainingMode(chainingMode);
+ }
+
+ // make method visible for this package
+ protected void setKeySalt(byte salt[]) {
+ if (salt == null || salt.length != getBlockSize()) {
+ throw new EncryptedDocumentException("invalid verifier salt");
+ }
+ super.setKeySalt(salt);
+ }
+
+ public byte[] getEncryptedHmacKey() {
+ return encryptedHmacKey;
+ }
+
+ protected void setEncryptedHmacKey(byte[] encryptedHmacKey) {
+ this.encryptedHmacKey = encryptedHmacKey;
+ }
+
+ public byte[] getEncryptedHmacValue() {
+ return encryptedHmacValue;
+ }
+
+ protected void setEncryptedHmacValue(byte[] encryptedHmacValue) {
+ this.encryptedHmacValue = encryptedHmacValue;
+ }
+}
Added: poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java?rev=1553336&view=auto
==============================================================================
--- poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java (added)
+++ poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java Tue Dec 24 23:13:21 2013
@@ -0,0 +1,111 @@
+/* ====================================================================
+ 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.agile;
+
+import java.io.IOException;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+
+public class AgileEncryptionInfoBuilder implements EncryptionInfoBuilder {
+
+ EncryptionInfo info;
+ AgileEncryptionHeader header;
+ AgileEncryptionVerifier verifier;
+ AgileDecryptor decryptor;
+ AgileEncryptor encryptor;
+
+ public void initialize(EncryptionInfo info, DocumentInputStream dis) throws IOException {
+ this.info = info;
+
+ StringBuilder builder = new StringBuilder();
+ byte[] xmlDescriptor = new byte[dis.available()];
+ dis.read(xmlDescriptor);
+ for (byte b : xmlDescriptor)
+ builder.append((char)b);
+ String descriptor = builder.toString();
+ header = new AgileEncryptionHeader(descriptor);
+ verifier = new AgileEncryptionVerifier(descriptor);
+ if (info.getVersionMajor() == 4 && info.getVersionMinor() == 4) {
+ decryptor = new AgileDecryptor(this);
+ }
+ }
+
+ public void initialize(EncryptionInfo info, CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm, int keyBits, int blockSize, ChainingMode chainingMode) {
+ this.info = info;
+
+ if (cipherAlgorithm == null) {
+ cipherAlgorithm = CipherAlgorithm.aes128;
+ }
+ if (cipherAlgorithm == CipherAlgorithm.rc4) {
+ throw new EncryptedDocumentException("RC4 must not be used with agile encryption.");
+ }
+ if (hashAlgorithm == null) {
+ hashAlgorithm = HashAlgorithm.sha1;
+ }
+ if (chainingMode == null) {
+ chainingMode = ChainingMode.cbc;
+ }
+ if (!(chainingMode == ChainingMode.cbc || chainingMode == ChainingMode.cfb)) {
+ throw new EncryptedDocumentException("Agile encryption only supports CBC/CFB chaining.");
+ }
+ if (keyBits == -1) {
+ keyBits = cipherAlgorithm.defaultKeySize;
+ }
+ if (blockSize == -1) {
+ blockSize = cipherAlgorithm.blockSize;
+ }
+ boolean found = false;
+ for (int ks : cipherAlgorithm.allowedKeySize) {
+ found |= (ks == keyBits);
+ }
+ 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;
+ }
+
+
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org