POI:设置密码以防止更改
POI: Set password to protect from changes
我想使用 Apache POI 对 OOXML 文件启用密码保护。
通过 Office 程序,在保存文件时(pptx
、xlsx
、...)我能够 select Tools > Options
并且我有提示为打开 and/or 更改文件设置密码。
现在我通过 google 搜索了几个小时并阅读了一些 API 页面以找到 POI 方法,但找不到任何东西。
知道这是否已实施或微软的专长,因为他们根本不在乎他们自己的标准化?
编辑:
由于下面的第一条评论指向 Office 2003 文档,我可能会明确指出:我在谈论 XSS* 功能。我想保护 2007 年以来的 OOXML 格式。我在不同的 API 上查找类似的功能,但找不到那些。 HSSWorkBook#writeProtect... 我知道。
尽管 Excel
在保存文件时一步完成此操作,但这是两个步骤。
起初 ReadOnlyRecommended
设置在 /xl/workbook.xml
中,看起来像:
<workbook>
...
<fileSharing readOnlyRecommended="true" userName="user" reservationPassword="DC45"/>
...
可以使用 org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFileSharing which you can get/set in CTWorkbook
which you can get from XSSFWorkbook.getCTWorkbook 设置 fileSharing
元素。
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();
}
}
我想使用 Apache POI 对 OOXML 文件启用密码保护。
通过 Office 程序,在保存文件时(pptx
、xlsx
、...)我能够 select Tools > Options
并且我有提示为打开 and/or 更改文件设置密码。
现在我通过 google 搜索了几个小时并阅读了一些 API 页面以找到 POI 方法,但找不到任何东西。
知道这是否已实施或微软的专长,因为他们根本不在乎他们自己的标准化?
编辑: 由于下面的第一条评论指向 Office 2003 文档,我可能会明确指出:我在谈论 XSS* 功能。我想保护 2007 年以来的 OOXML 格式。我在不同的 API 上查找类似的功能,但找不到那些。 HSSWorkBook#writeProtect... 我知道。
尽管 Excel
在保存文件时一步完成此操作,但这是两个步骤。
起初 ReadOnlyRecommended
设置在 /xl/workbook.xml
中,看起来像:
<workbook>
...
<fileSharing readOnlyRecommended="true" userName="user" reservationPassword="DC45"/>
...
可以使用 org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFileSharing which you can get/set in CTWorkbook
which you can get from XSSFWorkbook.getCTWorkbook 设置 fileSharing
元素。
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();
}
}