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.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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.poi.EncryptedDocumentException;
  33. import org.apache.poi.poifs.crypt.CryptoFunctions;
  34. import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
  35. import org.apache.poi.poifs.crypt.EncryptionInfo;
  36. import org.apache.poi.poifs.crypt.EncryptionVerifier;
  37. import org.apache.poi.poifs.crypt.Encryptor;
  38. import org.apache.poi.poifs.filesystem.DirectoryNode;
  39. import org.apache.poi.poifs.filesystem.POIFSWriterEvent;
  40. import org.apache.poi.poifs.filesystem.POIFSWriterListener;
  41. import org.apache.poi.util.IOUtils;
  42. import org.apache.poi.util.LittleEndianByteArrayOutputStream;
  43. import org.apache.poi.util.LittleEndianConsts;
  44. import org.apache.poi.util.LittleEndianOutputStream;
  45. import org.apache.poi.util.TempFile;
  46. public class StandardEncryptor extends Encryptor {
  47. private final StandardEncryptionInfoBuilder builder;
  48. protected StandardEncryptor(StandardEncryptionInfoBuilder builder) {
  49. this.builder = builder;
  50. }
  51. public void confirmPassword(String password) {
  52. // see [MS-OFFCRYPTO] - 2.3.3 EncryptionVerifier
  53. Random r = new SecureRandom();
  54. byte[] salt = new byte[16], verifier = new byte[16];
  55. r.nextBytes(salt);
  56. r.nextBytes(verifier);
  57. confirmPassword(password, null, null, salt, verifier, null);
  58. }
  59. /**
  60. * Fills the fields of verifier and header with the calculated hashes based
  61. * on the password and a random salt
  62. *
  63. * see [MS-OFFCRYPTO] - 2.3.4.7 ECMA-376 Document Encryption Key Generation
  64. */
  65. public void confirmPassword(String password, byte keySpec[], byte keySalt[], byte verifier[], byte verifierSalt[], byte integritySalt[]) {
  66. StandardEncryptionVerifier ver = builder.getVerifier();
  67. ver.setSalt(verifierSalt);
  68. SecretKey secretKey = generateSecretKey(password, ver, getKeySizeInBytes());
  69. setSecretKey(secretKey);
  70. Cipher cipher = getCipher(secretKey, null);
  71. try {
  72. byte encryptedVerifier[] = cipher.doFinal(verifier);
  73. MessageDigest hashAlgo = CryptoFunctions.getMessageDigest(ver.getHashAlgorithm());
  74. byte calcVerifierHash[] = hashAlgo.digest(verifier);
  75. // 2.3.3 EncryptionVerifier ...
  76. // An array of bytes that contains the encrypted form of the
  77. // hash of the randomly generated Verifier value. The length of the array MUST be the size of
  78. // the encryption block size multiplied by the number of blocks needed to encrypt the hash of the
  79. // Verifier. If the encryption algorithm is RC4, the length MUST be 20 bytes. If the encryption
  80. // algorithm is AES, the length MUST be 32 bytes. After decrypting the EncryptedVerifierHash
  81. // field, only the first VerifierHashSize bytes MUST be used.
  82. int encVerHashSize = ver.getCipherAlgorithm().encryptedVerifierHashLength;
  83. byte encryptedVerifierHash[] = cipher.doFinal(Arrays.copyOf(calcVerifierHash, encVerHashSize));
  84. ver.setEncryptedVerifier(encryptedVerifier);
  85. ver.setEncryptedVerifierHash(encryptedVerifierHash);
  86. } catch (GeneralSecurityException e) {
  87. throw new EncryptedDocumentException("Password confirmation failed", e);
  88. }
  89. }
  90. private Cipher getCipher(SecretKey key, String padding) {
  91. EncryptionVerifier ver = builder.getVerifier();
  92. return CryptoFunctions.getCipher(key, ver.getCipherAlgorithm(), ver.getChainingMode(), null, Cipher.ENCRYPT_MODE, padding);
  93. }
  94. public OutputStream getDataStream(final DirectoryNode dir)
  95. throws IOException, GeneralSecurityException {
  96. createEncryptionInfoEntry(dir);
  97. DataSpaceMapUtils.addDefaultDataSpace(dir);
  98. OutputStream countStream = new StandardCipherOutputStream(dir);
  99. return countStream;
  100. }
  101. protected class StandardCipherOutputStream extends FilterOutputStream implements POIFSWriterListener {
  102. protected long countBytes;
  103. protected final File fileOut;
  104. protected final DirectoryNode dir;
  105. protected StandardCipherOutputStream(DirectoryNode dir) throws IOException {
  106. super(null);
  107. this.dir = dir;
  108. fileOut = TempFile.createTempFile("encrypted_package", "crypt");
  109. FileOutputStream rawStream = new FileOutputStream(fileOut);
  110. // although not documented, we need the same padding as with agile encryption
  111. // and instead of calculating the missing bytes for the block size ourselves
  112. // we leave it up to the CipherOutputStream, which generates/saves them on close()
  113. // ... we can't use "NoPadding" here
  114. //
  115. // see also [MS-OFFCRYPT] - 2.3.4.15
  116. // The final data block MUST be padded to the next integral multiple of the
  117. // KeyData.blockSize value. Any padding bytes can be used. Note that the StreamSize
  118. // field of the EncryptedPackage field specifies the number of bytes of
  119. // unencrypted data as specified in section 2.3.4.4.
  120. CipherOutputStream cryptStream = new CipherOutputStream(rawStream, getCipher(getSecretKey(), "PKCS5Padding"));
  121. this.out = cryptStream;
  122. }
  123. @Override
  124. public void write(byte[] b, int off, int len) throws IOException {
  125. out.write(b, off, len);
  126. countBytes += len;
  127. }
  128. @Override
  129. public void write(int b) throws IOException {
  130. out.write(b);
  131. countBytes++;
  132. }
  133. public void close() throws IOException {
  134. // the CipherOutputStream adds the padding bytes on close()
  135. super.close();
  136. writeToPOIFS();
  137. }
  138. void writeToPOIFS() throws IOException {
  139. int oleStreamSize = (int)(fileOut.length()+LittleEndianConsts.LONG_SIZE);
  140. dir.createDocument(DEFAULT_POIFS_ENTRY, oleStreamSize, this);
  141. // TODO: any properties???
  142. }
  143. public void processPOIFSWriterEvent(POIFSWriterEvent event) {
  144. try {
  145. LittleEndianOutputStream leos = new LittleEndianOutputStream(event.getStream());
  146. // StreamSize (8 bytes): An unsigned integer that specifies the number of bytes used by data
  147. // encrypted within the EncryptedData field, not including the size of the StreamSize field.
  148. // Note that the actual size of the \EncryptedPackage stream (1) can be larger than this
  149. // value, depending on the block size of the chosen encryption algorithm
  150. leos.writeLong(countBytes);
  151. FileInputStream fis = new FileInputStream(fileOut);
  152. IOUtils.copy(fis, leos);
  153. fis.close();
  154. fileOut.delete();
  155. leos.close();
  156. } catch (IOException e) {
  157. throw new EncryptedDocumentException(e);
  158. }
  159. }
  160. }
  161. protected int getKeySizeInBytes() {
  162. return builder.getHeader().getKeySize()/8;
  163. }
  164. protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
  165. final EncryptionInfo info = builder.getEncryptionInfo();
  166. final StandardEncryptionHeader header = builder.getHeader();
  167. final StandardEncryptionVerifier verifier = builder.getVerifier();
  168. EncryptionRecord er = new EncryptionRecord(){
  169. public void write(LittleEndianByteArrayOutputStream bos) {
  170. bos.writeShort(info.getVersionMajor());
  171. bos.writeShort(info.getVersionMinor());
  172. bos.writeInt(info.getEncryptionFlags());
  173. header.write(bos);
  174. verifier.write(bos);
  175. }
  176. };
  177. createEncryptionEntry(dir, "EncryptionInfo", er);
  178. // TODO: any properties???
  179. }
  180. }