POI:设置密码以防止更改

时间:2017-12-12 11:51:59

标签: java apache-poi openxml

我想使用Apache POI在OOXML文件上启用密码保护。

通过Office程序,在保存文件(pptxxlsx,...)的同时,我可以选择Tools > Options并在那里提示设置打开和/或更改文件。

现在我通过谷歌搜索了几个小时,并阅读了几个API页面来查找POI方法,但无法找到任何内容。

是否有任何想法是否已实施或微软专业,因为他们不会对自己的标准化做出错误的判断?

编辑: 由于下面的第一条评论指向Office 2003文档,我可能会明确指出:我在谈论XSS *功能。我希望从2007年开始保护OOXML格式。我在不同的API上查找类似的功能,但无法找到它们。 HSSWorkBook#writeProtect ...对我而言。

1 个答案:

答案 0 :(得分:3)

虽然Excel在保存文件的同时一步完成此操作,但这是两个步骤。

首先ReadOnlyRecommended设置在/xl/workbook.xml中,如下所示:

<workbook>
 ...
 <fileSharing readOnlyRecommended="true" userName="user" reservationPassword="DC45"/>
 ...

可以使用org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFileSharing设置fileSharing元素,您可以在CTWorkbook中获取/设置XSSFWorkbook.getCTWorkbook

reservationPassword的密码哈希值是通过特殊算法计算的。遗憾的是,大多数Office Open XML规范都没有正确描述此算法。我在Office Open XML Part 4 - Transitional Migration Features.pdf, page 229 and 230找到了正确的说明。

在此步骤之后,您将拥有一个只读建议的工作簿和一个具有写访问权限的密码。

如果完成,您现在可以设置加密,如Apache POI - Encryption support

所示

示例:

import java.io.*;
import org.apache.poi.openxml4j.opc.*;
import org.apache.poi.poifs.filesystem.*;
import org.apache.poi.poifs.crypt.*;

import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

import java.nio.ByteBuffer;

import org.openxmlformats.schemas.spreadsheetml.x2006.main.*;

public class OOXMLEncryptionTest {

 //password hashed using the low-order word algorithm defined in §14.7.1 of ECMA-376
 static short getPasswordHash(String szPassword) {
  int wPasswordHash;
  byte[] pch = szPassword.getBytes();
  int cchPassword = pch.length;
  wPasswordHash = 0;
  if (cchPassword > 0) {
   for (int i = cchPassword; i > 0; i--) {
    wPasswordHash = ((wPasswordHash >> 14) & 0x01) | ((wPasswordHash << 1) & 0x7FFF);
    wPasswordHash ^= pch[i-1];
   }
   wPasswordHash = ((wPasswordHash >> 14) & 0x01) | ((wPasswordHash << 1) & 0x7FFF);
   wPasswordHash ^= cchPassword;
   wPasswordHash ^= (0x8000 | ('N' << 8) | 'K');
  }
System.out.println(wPasswordHash); 
  return (short)(wPasswordHash);
 }

 public static void main(String[] args) throws Exception {

  // Open an Excel workbook and set ReadOnlyRecommended
  XSSFWorkbook workbook = (XSSFWorkbook)WorkbookFactory.create(new FileInputStream("ExcelTest.xlsx"));
  CTWorkbook ctWorkbook = workbook.getCTWorkbook();
  CTFileSharing ctfilesharing = ctWorkbook.getFileSharing();
  if (ctfilesharing == null) ctfilesharing = ctWorkbook.addNewFileSharing();
  ctfilesharing.setReadOnlyRecommended(true);
  ctfilesharing.setUserName("user");

  short passwordhash = getPasswordHash("baafoo");
System.out.println(passwordhash); 

  byte[] bpasswordhash = ByteBuffer.allocate(2).putShort(passwordhash).array();
  ctfilesharing.setReservationPassword(bpasswordhash);

  workbook.write(new FileOutputStream("ExcelTestRORecommended.xlsx"));
  workbook.close();

  // Now do the encryption
  POIFSFileSystem fs = new POIFSFileSystem();
  EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile);
  // EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile, CipherAlgorithm.aes192, HashAlgorithm.sha384, -1, -1, null);

  Encryptor enc = info.getEncryptor();
  enc.confirmPassword("foobaa");

  // Read in an existing OOXML file
  OPCPackage opc = OPCPackage.open(new File("ExcelTestRORecommended.xlsx"), PackageAccess.READ_WRITE);
  OutputStream os = enc.getDataStream(fs);
  opc.save(os);
  opc.close();

  // Write out the encrypted version
  FileOutputStream fos = new FileOutputStream("ExcelTestEncrypted.xlsx");
  fs.writeFilesystem(fos);
  fos.close();

 }
}

对于所有Microsoft Office文件类型,设置只读建议似乎是一般的相同,因为您将在所有情况下设置文件保存时的只读建议,但在幕后不设置。 Microsoft将其存储到文件中的方式非常不同。

Excel中,ReadOnlyRecommended位于工作簿的FileSharing元素中,并且仅使用非常不安全的2字节密码哈希。

Word中,它是设置部分中的WriteProtection元素。它使用现代加密方法使用盐渍密码哈希。

PowerPoint中,演示文稿中的ModifyVerifier元素也使用现代加密方法使用盐渍密码哈希。

以下示例显示了所有三种方法:

import java.io.*;

import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.*;
import java.nio.ByteBuffer;

import org.apache.poi.xwpf.usermodel.*;
import org.apache.poi.POIXMLDocumentPart;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import org.apache.poi.xslf.usermodel.XMLSlideShow;
import org.openxmlformats.schemas.presentationml.x2006.main.*;

import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import java.security.SecureRandom;
import java.math.BigInteger;
import java.lang.reflect.Field;

public class RORecommendedTest {

 //password hashed using the low-order word algorithm defined in §14.7.1 of ECMA-376
 static short getPasswordHash(String szPassword) {
  int wPasswordHash;
  byte[] pch = szPassword.getBytes();
  int cchPassword = pch.length;
  wPasswordHash = 0;
  if (cchPassword > 0) {
   for (int i = cchPassword; i > 0; i--) {
    wPasswordHash = ((wPasswordHash >> 14) & 0x01) | ((wPasswordHash << 1) & 0x7FFF);
    wPasswordHash ^= pch[i-1];
   }
   wPasswordHash = ((wPasswordHash >> 14) & 0x01) | ((wPasswordHash << 1) & 0x7FFF);
   wPasswordHash ^= cchPassword;
   wPasswordHash ^= (0x8000 | ('N' << 8) | 'K');
  }
  return (short)(wPasswordHash);
 }

 public static void main(String[] args) throws Exception {

  // Open an Excel workbook and set ReadOnlyRecommended
  XSSFWorkbook workbook = (XSSFWorkbook)WorkbookFactory.create(new FileInputStream("ExcelTest.xlsx"));
  CTWorkbook ctWorkbook = workbook.getCTWorkbook();
  CTFileSharing ctfilesharing = ctWorkbook.getFileSharing();
  if (ctfilesharing == null) ctfilesharing = ctWorkbook.addNewFileSharing();
  ctfilesharing.setReadOnlyRecommended(true);
  ctfilesharing.setUserName("user");

  short passwordhash = getPasswordHash("baafoo");

  byte[] bpasswordhash = ByteBuffer.allocate(2).putShort(passwordhash).array();
  ctfilesharing.setReservationPassword(bpasswordhash);

  workbook.write(new FileOutputStream("ExcelTestRORecommended.xlsx"));
  workbook.close();


  // Open a Word document and set read only recommended aka WriteProtection
  XWPFDocument document = new XWPFDocument(new FileInputStream("WordTest.docx"));

  POIXMLDocumentPart part = null;
  for (int i = 0; i < document.getRelations().size(); i++) {
   part = document.getRelations().get(i);
   if (part instanceof XWPFSettings) break;
  }
  if (part instanceof XWPFSettings) {
   XWPFSettings settings = (XWPFSettings)part;

   Field _ctSettings = XWPFSettings.class.getDeclaredField("ctSettings"); 
   _ctSettings.setAccessible(true); 
   CTSettings ctSettings = (CTSettings)_ctSettings.get(settings);

   CTWriteProtection ctwriteprotection = ctSettings.getWriteProtection();
   if (ctwriteprotection == null) ctwriteprotection = ctSettings.addNewWriteProtection();
   ctwriteprotection.setRecommended(STOnOff.ON);

   ctwriteprotection.setCryptProviderType(org.openxmlformats.schemas.wordprocessingml.x2006.main.STCryptProv.RSA_FULL);
   ctwriteprotection.setCryptAlgorithmClass(org.openxmlformats.schemas.wordprocessingml.x2006.main.STAlgClass.HASH);
   ctwriteprotection.setCryptAlgorithmType(org.openxmlformats.schemas.wordprocessingml.x2006.main.STAlgType.TYPE_ANY);
   ctwriteprotection.setCryptAlgorithmSid(BigInteger.valueOf(4)); //SHA-1
   ctwriteprotection.setCryptSpinCount(BigInteger.valueOf(100000));

   SecureRandom random = new SecureRandom();
   byte[] salt = random.generateSeed(16);
   byte[] hash = CryptoFunctions.hashPassword("baafoo", HashAlgorithm.sha1, salt, 100000, false);

   ctwriteprotection.setHash(hash);
   ctwriteprotection.setSalt(salt);
  }

  document.write(new FileOutputStream("WordTestRORecommended.docx"));
  document.close();

  // Open a PowerPoint show and set read only recommended aka ModifyVerifier
  XMLSlideShow slideShow = new XMLSlideShow(new FileInputStream("PowerPntTest.pptx"));
  CTPresentation ctpresentation = slideShow.getCTPresentation();
  CTModifyVerifier ctmodifyverifier = ctpresentation.getModifyVerifier();
  if (ctmodifyverifier == null) ctmodifyverifier = ctpresentation.addNewModifyVerifier();

  ctmodifyverifier.setCryptProviderType(org.openxmlformats.schemas.presentationml.x2006.main.STCryptProv.RSA_FULL);
  ctmodifyverifier.setCryptAlgorithmClass(org.openxmlformats.schemas.presentationml.x2006.main.STAlgClass.HASH);
  ctmodifyverifier.setCryptAlgorithmType(org.openxmlformats.schemas.presentationml.x2006.main.STAlgType.TYPE_ANY);
  ctmodifyverifier.setCryptAlgorithmSid(4); //SHA-1
  ctmodifyverifier.setSpinCount(100000);

  SecureRandom random = new SecureRandom();
  byte[] salt = random.generateSeed(16);
  byte[] hash = CryptoFunctions.hashPassword("baafoo", HashAlgorithm.sha1, salt, 100000, false);

  ctmodifyverifier.setHashData(java.util.Base64.getEncoder().encodeToString(hash));
  ctmodifyverifier.setSaltData(java.util.Base64.getEncoder().encodeToString(salt));

  slideShow.write(new FileOutputStream("PowerPntTestRORecommended.pptx"));
  slideShow.close();

 }
}