You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

StandardEncryptor.java 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.poifs.crypt.standard;
  16. import static org.apache.poi.poifs.crypt.DataSpaceMapUtils.createEncryptionEntry;
  17. import static org.apache.poi.poifs.crypt.standard.StandardDecryptor.generateSecretKey;
  18. import java.io.File;
  19. import java.io.FileInputStream;
  20. import java.io.FileOutputStream;
  21. import java.io.FilterOutputStream;
  22. import java.io.IOException;
  23. import java.io.OutputStream;
  24. import java.security.GeneralSecurityException;
  25. import java.security.MessageDigest;
  26. import java.security.SecureRandom;
  27. import java.util.Arrays;
  28. import java.util.Random;
  29. import javax.crypto.Cipher;
  30. import javax.crypto.CipherOutputStream;
  31. import javax.crypto.SecretKey;
  32. import org.apache.logging.log4j.LogManager;
  33. import org.apache.logging.log4j.Logger;
  34. import org.apache.poi.EncryptedDocumentException;
  35. import org.apache.poi.poifs.crypt.CryptoFunctions;
  36. import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
  37. import org.apache.poi.poifs.crypt.EncryptionInfo;
  38. import org.apache.poi.poifs.crypt.EncryptionVerifier;
  39. import org.apache.poi.poifs.crypt.Encryptor;
  40. import org.apache.poi.poifs.filesystem.DirectoryNode;
  41. import org.apache.poi.poifs.filesystem.POIFSWriterEvent;
  42. import org.apache.poi.poifs.filesystem.POIFSWriterListener;
  43. import org.apache.poi.util.IOUtils;
  44. import org.apache.poi.util.LittleEndianByteArrayOutputStream;
  45. import org.apache.poi.util.LittleEndianConsts;
  46. import org.apache.poi.util.LittleEndianOutputStream;
  47. import org.apache.poi.util.TempFile;
  48. public class StandardEncryptor extends Encryptor {
  49. private static final Logger LOG = LogManager.getLogger(StandardEncryptor.class);
  50. protected StandardEncryptor() {}
  51. protected StandardEncryptor(StandardEncryptor other) {
  52. super(other);
  53. }
  54. @Override
  55. public void confirmPassword(String password) {
  56. // see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier
  57. Random r = new SecureRandom();
  58. byte[] salt = new byte[16], verifier = new byte[16];
  59. r.nextBytes(salt);
  60. r.nextBytes(verifier);
  61. confirmPassword(password, null, null, salt, verifier, null);
  62. }
  63. /**
  64. * Fills the fields of verifier and header with the calculated hashes based
  65. * on the password and a random salt
  66. *
  67. * see [MS-OFFCRYPTO] - 2.3.4.7 ECMA-376 Document Encryption Key Generation
  68. */
  69. @Override
  70. public void confirmPassword(String password, byte[] keySpec, byte[] keySalt, byte[] verifier, byte[] verifierSalt, byte[] integritySalt) {
  71. StandardEncryptionVerifier ver = (StandardEncryptionVerifier)getEncryptionInfo().getVerifier();
  72. ver.setSalt(verifierSalt);
  73. SecretKey secretKey = generateSecretKey(password, ver, getKeySizeInBytes());
  74. setSecretKey(secretKey);
  75. Cipher cipher = getCipher(secretKey, null);
  76. try {
  77. byte[] encryptedVerifier = cipher.doFinal(verifier);
  78. MessageDigest hashAlgo = CryptoFunctions.getMessageDigest(ver.getHashAlgorithm());
  79. byte[] calcVerifierHash = hashAlgo.digest(verifier);
  80. // 2.3.3 EncryptionVerifier ...
  81. // An array of bytes that contains the encrypted form of the
  82. // hash of the randomly generated Verifier value. The length of the array MUST be the size of
  83. // the encryption block size multiplied by the number of blocks needed to encrypt the hash of the
  84. // Verifier. If the encryption algorithm is RC4, the length MUST be 20 bytes. If the encryption
  85. // algorithm is AES, the length MUST be 32 bytes. After decrypting the EncryptedVerifierHash
  86. // field, only the first VerifierHashSize bytes MUST be used.
  87. int encVerHashSize = ver.getCipherAlgorithm().encryptedVerifierHashLength;
  88. byte[] encryptedVerifierHash = cipher.doFinal(Arrays.copyOf(calcVerifierHash, encVerHashSize));
  89. ver.setEncryptedVerifier(encryptedVerifier);
  90. ver.setEncryptedVerifierHash(encryptedVerifierHash);
  91. } catch (GeneralSecurityException e) {
  92. throw new EncryptedDocumentException("Password confirmation failed", e);
  93. }
  94. }
  95. private Cipher getCipher(SecretKey key, String padding) {
  96. EncryptionVerifier ver = getEncryptionInfo().getVerifier();
  97. return CryptoFunctions.getCipher(key, ver.getCipherAlgorithm(), ver.getChainingMode(), null, Cipher.ENCRYPT_MODE, padding);
  98. }
  99. @Override
  100. public OutputStream getDataStream(final DirectoryNode dir)
  101. throws IOException, GeneralSecurityException {
  102. createEncryptionInfoEntry(dir);
  103. DataSpaceMapUtils.addDefaultDataSpace(dir);
  104. return new StandardCipherOutputStream(dir);
  105. }
  106. protected class StandardCipherOutputStream extends FilterOutputStream implements POIFSWriterListener {
  107. protected long countBytes;
  108. protected final File fileOut;
  109. protected final DirectoryNode dir;
  110. @SuppressWarnings({"resource", "squid:S2095"})
  111. private StandardCipherOutputStream(DirectoryNode dir, File fileOut) throws IOException {
  112. // although not documented, we need the same padding as with agile encryption
  113. // and instead of calculating the missing bytes for the block size ourselves
  114. // we leave it up to the CipherOutputStream, which generates/saves them on close()
  115. // ... we can't use "NoPadding" here
  116. //
  117. // see also [MS-OFFCRYPT] - 2.3.4.15
  118. // The final data block MUST be padded to the next integral multiple of the
  119. // KeyData.blockSize value. Any padding bytes can be used. Note that the StreamSize
  120. // field of the EncryptedPackage field specifies the number of bytes of
  121. // unencrypted data as specified in section 2.3.4.4.
  122. super(
  123. new CipherOutputStream(new FileOutputStream(fileOut), getCipher(getSecretKey(), "PKCS5Padding"))
  124. );
  125. this.fileOut = fileOut;
  126. this.dir = dir;
  127. }
  128. protected StandardCipherOutputStream(DirectoryNode dir) throws IOException {
  129. this(dir, TempFile.createTempFile("encrypted_package", "crypt"));
  130. }
  131. @Override
  132. public void write(byte[] b, int off, int len) throws IOException {
  133. out.write(b, off, len);
  134. countBytes += len;
  135. }
  136. @Override
  137. public void write(int b) throws IOException {
  138. out.write(b);
  139. countBytes++;
  140. }
  141. @Override
  142. public void close() throws IOException {
  143. // the CipherOutputStream adds the padding bytes on close()
  144. super.close();
  145. writeToPOIFS();
  146. }
  147. void writeToPOIFS() throws IOException {
  148. int oleStreamSize = (int)(fileOut.length()+LittleEndianConsts.LONG_SIZE);
  149. dir.createDocument(DEFAULT_POIFS_ENTRY, oleStreamSize, this);
  150. // TODO: any properties???
  151. }
  152. @Override
  153. public void processPOIFSWriterEvent(POIFSWriterEvent event) {
  154. try {
  155. LittleEndianOutputStream leos = new LittleEndianOutputStream(event.getStream());
  156. // StreamSize (8 bytes): An unsigned integer that specifies the number of bytes used by data
  157. // encrypted within the EncryptedData field, not including the size of the StreamSize field.
  158. // Note that the actual size of the \EncryptedPackage stream (1) can be larger than this
  159. // value, depending on the block size of the chosen encryption algorithm
  160. leos.writeLong(countBytes);
  161. try (FileInputStream fis = new FileInputStream(fileOut)) {
  162. IOUtils.copy(fis, leos);
  163. }
  164. if (!fileOut.delete()) {
  165. LOG.atError().log("Can't delete temporary encryption file: {}", fileOut);
  166. }
  167. leos.close();
  168. } catch (IOException e) {
  169. throw new EncryptedDocumentException(e);
  170. }
  171. }
  172. }
  173. protected int getKeySizeInBytes() {
  174. return getEncryptionInfo().getHeader().getKeySize()/8;
  175. }
  176. protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
  177. final EncryptionInfo info = getEncryptionInfo();
  178. final StandardEncryptionHeader header = (StandardEncryptionHeader)info.getHeader();
  179. final StandardEncryptionVerifier verifier = (StandardEncryptionVerifier)info.getVerifier();
  180. EncryptionRecord er = new EncryptionRecord(){
  181. @Override
  182. public void write(LittleEndianByteArrayOutputStream bos) {
  183. bos.writeShort(info.getVersionMajor());
  184. bos.writeShort(info.getVersionMinor());
  185. bos.writeInt(info.getEncryptionFlags());
  186. header.write(bos);
  187. verifier.write(bos);
  188. }
  189. };
  190. createEncryptionEntry(dir, "EncryptionInfo", er);
  191. // TODO: any properties???
  192. }
  193. @Override
  194. public StandardEncryptor copy() {
  195. return new StandardEncryptor(this);
  196. }
  197. }