OpenXML 使用密码保护电子表格工作簿
OpenXML Protect Spreadsheet Workbook with Password
我正在尝试使用 OpenXML SDK 创建受保护的电子表格文档。但是,生成的 WorkbookHashValue
不正确,因此工作簿无法解除保护。
var password = Encoding.UTF8.GetBytes("123");
var salt = new byte[16];
new RNGCryptoServiceProvider().GetNonZeroBytes(salt);
var spinCount = 100000U;
using (var document = SpreadsheetDocument.Create("text.xlsx", SpreadsheetDocumentType.Workbook))
{
var workbookPart = document.AddWorkbookPart();
var workbook = new Workbook();
WorkbookProtection workbookProtection = new WorkbookProtection()
{
LockStructure = true,
WorkbookAlgorithmName = "SHA-512",
WorkbookHashValue = Convert.ToBase64String(GetPasswordHash(password, salt, spinCount)),
WorkbookSaltValue = Convert.ToBase64String(salt),
WorkbookSpinCount = spinCount
};
var sheets = new Sheets();
var sheet = new Sheet
{
Name = "Sheet 1",
SheetId = 1U,
Id = "rId1"
};
sheets.Append(sheet);
workbook.Append(workbookProtection);
workbook.Append(sheets);
workbookPart.Workbook = workbook;
var worksheetPart = workbookPart.AddNewPart<WorksheetPart>("rId1");
var worksheet = new Worksheet();
var sheetData = new SheetData();
worksheet.Append(sheetData);
worksheetPart.Worksheet = worksheet;
}
private byte[] GetPasswordHash(byte[] password, byte[] salt, uint spinCount)
{
using (var sha512 = SHA512.Create())
{
var buffer = new byte[salt.Length + password.Length];
Array.Copy(salt, buffer, salt.Length);
Array.Copy(password, 0, buffer, salt.Length, password.Length);
byte[] hash = sha512.ComputeHash(buffer);
buffer = new byte[hash.Length + 4];
for (var i = 0U; i < spinCount; i++)
{
Array.Copy(hash, buffer, hash.Length);
Array.Copy(BitConverter.GetBytes(i), 0, buffer, hash.Length, 4);
hash = sha512.ComputeHash(buffer);
}
return hash;
}
}
使用 salt VAQd0dyl7U67APquHio1lQ==
的密码 123
的正确哈希值应该是 2ZwXmW83qax0iUfzSkbhwAOVSDHAm6S/v9irWWTzdoFDgzO2Kc82P3Z9BAwbWqFLzN4rKaL0APOMzQ5tA7TBDw==
,但是上面的代码生成了 z5ebojaXN/sD4ps9yurRCpSTDp+kSuTz+HN2PyKmGuicNgszAPKxfsE+kTgOEbGhT/VqSbwTd++oyAJxJh0L3A==
.
我尝试对比了Apache POI的源代码,没有发现任何错误
public static byte[] hashPassword(String password, HashAlgorithm hashAlgorithm, byte[] salt, int spinCount, boolean iteratorFirst) {
// If no password was given, use the default
if (password == null) {
password = Decryptor.DEFAULT_PASSWORD;
}
MessageDigest hashAlg = getMessageDigest(hashAlgorithm);
hashAlg.update(salt);
byte[] hash = hashAlg.digest(StringUtil.getToUnicodeLE(password));
byte[] iterator = new byte[LittleEndianConsts.INT_SIZE];
byte[] first = (iteratorFirst ? iterator : hash);
byte[] second = (iteratorFirst ? hash : iterator);
try {
for (int i = 0; i < spinCount; i++) {
LittleEndian.putInt(iterator, 0, i);
hashAlg.reset();
hashAlg.update(first);
hashAlg.update(second);
hashAlg.digest(hash, 0, hash.length); // don't create hash buffer everytime new
}
} catch (DigestException e) {
throw new EncryptedDocumentException("error in password hashing");
}
return hash;
}
(iteratorFirst
对于工作簿保护是错误的)
用于获取密码字节的编码应该是UTF16LE
Encoding.Unicode.GetBytes("123");
我正在尝试使用 OpenXML SDK 创建受保护的电子表格文档。但是,生成的 WorkbookHashValue
不正确,因此工作簿无法解除保护。
var password = Encoding.UTF8.GetBytes("123");
var salt = new byte[16];
new RNGCryptoServiceProvider().GetNonZeroBytes(salt);
var spinCount = 100000U;
using (var document = SpreadsheetDocument.Create("text.xlsx", SpreadsheetDocumentType.Workbook))
{
var workbookPart = document.AddWorkbookPart();
var workbook = new Workbook();
WorkbookProtection workbookProtection = new WorkbookProtection()
{
LockStructure = true,
WorkbookAlgorithmName = "SHA-512",
WorkbookHashValue = Convert.ToBase64String(GetPasswordHash(password, salt, spinCount)),
WorkbookSaltValue = Convert.ToBase64String(salt),
WorkbookSpinCount = spinCount
};
var sheets = new Sheets();
var sheet = new Sheet
{
Name = "Sheet 1",
SheetId = 1U,
Id = "rId1"
};
sheets.Append(sheet);
workbook.Append(workbookProtection);
workbook.Append(sheets);
workbookPart.Workbook = workbook;
var worksheetPart = workbookPart.AddNewPart<WorksheetPart>("rId1");
var worksheet = new Worksheet();
var sheetData = new SheetData();
worksheet.Append(sheetData);
worksheetPart.Worksheet = worksheet;
}
private byte[] GetPasswordHash(byte[] password, byte[] salt, uint spinCount)
{
using (var sha512 = SHA512.Create())
{
var buffer = new byte[salt.Length + password.Length];
Array.Copy(salt, buffer, salt.Length);
Array.Copy(password, 0, buffer, salt.Length, password.Length);
byte[] hash = sha512.ComputeHash(buffer);
buffer = new byte[hash.Length + 4];
for (var i = 0U; i < spinCount; i++)
{
Array.Copy(hash, buffer, hash.Length);
Array.Copy(BitConverter.GetBytes(i), 0, buffer, hash.Length, 4);
hash = sha512.ComputeHash(buffer);
}
return hash;
}
}
使用 salt VAQd0dyl7U67APquHio1lQ==
的密码 123
的正确哈希值应该是 2ZwXmW83qax0iUfzSkbhwAOVSDHAm6S/v9irWWTzdoFDgzO2Kc82P3Z9BAwbWqFLzN4rKaL0APOMzQ5tA7TBDw==
,但是上面的代码生成了 z5ebojaXN/sD4ps9yurRCpSTDp+kSuTz+HN2PyKmGuicNgszAPKxfsE+kTgOEbGhT/VqSbwTd++oyAJxJh0L3A==
.
我尝试对比了Apache POI的源代码,没有发现任何错误
public static byte[] hashPassword(String password, HashAlgorithm hashAlgorithm, byte[] salt, int spinCount, boolean iteratorFirst) {
// If no password was given, use the default
if (password == null) {
password = Decryptor.DEFAULT_PASSWORD;
}
MessageDigest hashAlg = getMessageDigest(hashAlgorithm);
hashAlg.update(salt);
byte[] hash = hashAlg.digest(StringUtil.getToUnicodeLE(password));
byte[] iterator = new byte[LittleEndianConsts.INT_SIZE];
byte[] first = (iteratorFirst ? iterator : hash);
byte[] second = (iteratorFirst ? hash : iterator);
try {
for (int i = 0; i < spinCount; i++) {
LittleEndian.putInt(iterator, 0, i);
hashAlg.reset();
hashAlg.update(first);
hashAlg.update(second);
hashAlg.digest(hash, 0, hash.length); // don't create hash buffer everytime new
}
} catch (DigestException e) {
throw new EncryptedDocumentException("error in password hashing");
}
return hash;
}
(iteratorFirst
对于工作簿保护是错误的)
用于获取密码字节的编码应该是UTF16LE
Encoding.Unicode.GetBytes("123");