[]
数字签名是文档真实性的证明。数字签名文档确保它是由签名者创建的,并且中途没有任何更改。
GcExcel允许用户在Excel电子表格中添加数字签名,使其更真实、更易于验证。
签名行用作数字签名的签名占位符。它们可以作为线条形状添加到工作表中,并在此基础上进行签名。
你可以使用 ISignatureSet 接口的 addSignatureLine 方法在工作表中添加签名行。你还可以使用 ISignatureSetup 接口的各种方法来添加签名信息和说明。当工作簿再次打开或作为Excel文件发送给签名者时,签名者可以看到签名行以及请求签名的通知。
请参阅以下示例代码在工作表中添加签名行。
Workbook workbook = new Workbook();
ISignature signature = workbook.getSignatures().addSignatureLine(workbook.getActiveSheet(), 100.0, 50.0);
// Add Signature lines
ISignatureSetup setup = signature.getSetup();
setup.setShowSignDate(false);
setup.setAllowComments(false);
setup.setSigningInstructions("Please check the content before signing.");
setup.setSuggestedSigner("Shinzo Nagama");
setup.setSuggestedSignerEmail("shinzo.nagama@ea.com");
setup.setSuggestedSignerLine2("Commander (Balanced)");
// Save to an excel file
workbook.save("AddSignatureLines.xlsx");
下图显示了Excel中的签名行:
你可以使用以下任一方法将签名行复制到另一个工作表区域或另一个工作表:
复制签名行 - 使用 IShape 接口的 duplicate 方法
复制签名行的单元格范围 - 使用 IRange 接口的 copy 方法
复制包含签名行的工作表 - 使用 IWorksheet 接口的 copy 方法
请参阅以下示例代码,将签名行复制到另一个区域和另一个工作表。
// Copy signature line with Range.Copy
IRange srcRange = activeSheet.getRange("A1:I15");
IRange destRange = activeSheet.getRange("A16:I30");
srcRange.copy(destRange);
// Copy signature line with Shape.Duplicate
signature.getSignatureLineShape().duplicate();
// Copy signature line with Worksheet.Copy
activeSheet.copy();
您可以使用以下任一方法删除签名行:
删除签名行 - 使用 ISignature 接口的 delete 方法
删除与签名行相关的形状 - 使用 IShape 接口的 delete 方法
请参阅以下代码示例删除工作表中的签名行。
// Create a new signature line and delete with Signature.Delete
ISignature signatureForTest = newSignatureLine.call();
signatureForTest.delete();
// Create a new signature line and delete with Shape.Delete
signatureForTest = newSignatureLine.call();
IShape signatureLineShape = signatureForTest.getSignatureLineShape();
signatureLineShape.delete();
请参阅以下示例代码,将签名行移动到另一个区域或工作表。
// Move signature line
IShape signatureLineShape = signatureShinzo.getSignatureLineShape();
signatureLineShape.setTop(signatureLineShape.getTop() + 100);
signatureLineShape.setLeft(signatureLineShape.getLeft() + 50);
请参阅下面的示例代码获取工作表中的签名行列表。
// Add first signature line
ISignature signatureShinzo = signatures.addSignatureLine(activeSheet, 100.0, 50.0);
ISignatureSetup setup1 = signatureShinzo.getSetup();
setup1.setShowSignDate(false);
setup1.setAllowComments(false);
setup1.setSigningInstructions("Please check the content before signing.");
setup1.setSuggestedSigner("Shinzo Nagama");
setup1.setSuggestedSignerEmail("shinzo.nagama@ea.com");
setup1.setSuggestedSignerLine2("Commander (Balanced)");
ISignature signatureKenji = signatures.addSignatureLine(activeSheet, 100.0, 350.0);
ISignatureSetup setup2 = signatureKenji.getSetup();
setup2.setShowSignDate(true);
setup2.setAllowComments(true);
setup2.setSigningInstructions("Please check the content before signing!");
setup2.setSuggestedSigner("Kenji Tenzai");
setup2.setSuggestedSignerEmail("kenji.tenzai@ea.com");
setup2.setSuggestedSignerLine2("Commander (Mecha)");
// List signatures with indexes
for (int i = 0; i < signatures.getCount(); i++) {
ISignature signature = signatures.get(i);
// change SuggestedSigner
if (i == 0)
signature.getSetup().setSuggestedSigner("Shinzo Nagama 123");
// change SuggestedSignerLine2
if (i == 1)
signature.getSetup().setSuggestedSignerLine2("Commander (Mecha 1234)");
}
ISignature 接口中的 getSignatureLineShape 方法可以在签名行作为形状时使用。其成员及其行为如下表所示:
SignatureLineShape 成员 | Get or Call 行为 | Set 行为 |
---|---|---|
Adjustments | 支持 | #N/A |
Adjustments.Count | 支持 | #N/A |
Adjustments.Item | 不支持 | 不支持 |
Adjustments.GetEnumerator | 不支持 | #N/A |
AutoShapeType | 支持 | 不支持 |
BottomRightCell | 支持 | #N/A |
Chart | 不支持 | #N/A |
Connector | 支持 | #N/A |
ConnectorFormat | 不支持 | #N/A |
Fill | 不支持 | #N/A |
GroupItems | 不支持 | #N/A |
HasChart | 支持 | #N/A |
Hyperlink | 不支持 | #N/A |
IsPrintable | 支持 | 支持 |
Line | 不支持 | #N/A |
Locked | 支持 | 支持 |
Name | 支持 | 支持 |
Parent | 支持 | #N/A |
ParentGroup | 不支持 | #N/A |
PictureFormat | 支持 | #N/A |
PictureFormat.ColorType | 支持 | 支持 |
PictureFormat.Brightness | 支持 | 支持 |
PictureFormat.Contrast | 支持 | 支持 |
PictureFormat.Crop | 不支持 | #N/A |
PictureFormat.CropLeft, CropTop, CropRight and CropBottom | 不支持 | 不支持 |
Placement | 支持 | 支持 |
Rotation | 支持 | 不支持 |
TextFrame | 不支持 | #N/A |
ThreeD | 不支持 | #N/A |
Title | 不支持 | 不支持 |
TopLeftCell | 支持 | #N/A |
Left, Top, Right and Bottom | 支持 | 支持 |
Type | 支持 | 支持 |
Transparency | 不支持 | 不支持 |
Ungroup | 不支持 | #N/A |
Visible | 支持 | 支持 |
ZOrderPosition | 支持 | 支持 |
签名行也可以导出为PDF文档。请参阅导出签名行。
数字签名可以通过使用签名证书对签名行进行签名并添加到Excel电子表格中,签名证书可以证明签名者的身份。请按照文档(Generate_Certificate.docx)中提到的步骤生成证书文件(.pfx)。
ISignature 接口的 sign 方法可用于添加数字签名。为了提交签名,工作簿应该用 xlsx 或 xlsm 扩展名保存。包含数字签名的工作簿被“标记为终版”以阻止编辑。
请参阅以下示例代码在工作表中添加数字签名。
// Create a new workbook
Workbook workbook = new Workbook();
ISignature signature = workbook.getSignatures().addSignatureLine(workbook.getActiveSheet(), 100.0, 50.0);
ISignatureSetup setup = signature.getSetup();
setup.setShowSignDate(true);
setup.setAllowComments(true);
setup.setSigningInstructions("");
setup.setSuggestedSigner("");
setup.setSuggestedSignerEmail("example@microsoft.com");
setup.setSuggestedSignerLine2("");
SignatureDetails details = new SignatureDetails();
details.setAddress1("");
details.setAddress2("
下图显示Excel中的数字签名:
你也可以使用 ISignatureSet 接口的 addNonVisibleSignature 方法向工作簿中添加不可见的数字签名。不可见的数字签名不会出现在任何工作表中。然而,可以通过单击Excel中的“查看签名”对话框来查看签名。
请参阅以下示例代码在工作簿中添加不可见的签名。
// Create a new workbook
Workbook workbook = new Workbook();
ISignature signature = workbook.getSignatures().addNonVisibleSignature();
SignatureDetails details = new SignatureDetails();
details.setAddress1("");
details.setAddress2("
数字签名工作簿将变为只读。在GcExcel中再次打开时,必须在关闭前保留其数字签名。
如果打开了数字签名工作簿并对其进行了任何修改,则应对其进行会签。否则,将工作簿另存为xlsx或xlsm后,现有签名将被删除。ISignature 接口的 countersign 方法可以用于对使用同一证书的签名进行会签。
请参阅以下代码示例,打开经过数字签名的工作簿,并在修改工作表后对其进行会签。
// Open a digitally signed workbook
workbook.open("signsignaturelines.xlsx");
// Modify
workbook.getWorksheets().get(0).getRange("A1").setValue("Modified");
// Countersign
workbook.getSignatures().get(0).countersign(ks, password);
// Save to an excel file
workbook.save("CounterSignSignatureLines.xlsx");
使用 XlsxOpenOptions 类中的 setDigitalSignatureOnly 方法,以“仅数字签名模式”打开数字签名工作簿。在此模式下,可以在保留现有签名的同时执行以下操作:
在已有的签名行上签名
从已签名的签名行中删除签名
添加或删除不可见的签名
请参阅以下代码示例,以“仅数字签名”模式打开数字签名工作簿,并向其中添加不可见的签名。
workbook.Open("signsignaturelines.xlsx");
// Use DigitalSignatureOnly mode, because the workbook was already signed.
// If you don't open it with digital signature only mode,
// all existing signatures will be removed after saving the workbook.
XlsxOpenOptions openOption = new XlsxOpenOptions();
openOption.setDigitalSignatureOnly(true);
// Add signature to this workbook
ISignature signature = workbook.getSignatures().addNonVisibleSignature();
signature.sign(ks, password, details);
// Commit signatures
workbook.save("AddNonVisibleSignatureToSignedWorkbook.xlsx");
GcExcel允许您使用 ISignature 接口的 getIsValid 方法验证数字签名。
请参阅以下代码示例来对工作簿中的数字签名进行验证。
// Create a new workbook
Workbook workbook = new Workbook();
workbook.open("signsignaturelines.xlsx");
ISignatureSet signatures = workbook.getSignatures();
boolean signed = false;
boolean valid = false;
X509Certificate certificate = null;
// Verify the first signature
for (ISignature signature : signatures) {
if (signature.getIsSigned()) {
// Save the result in locals. You can print them later.
signed = true;
certificate = signature.getDetails().getSignatureCertificate();
valid = signature.getIsValid();
break;
}
}
// Verify the first certificate
boolean certificateIsValid = true;
// Check expiration date and start date
try {
certificate.checkValidity();
} catch (CertificateExpiredException e) {
certificateIsValid = false;
return;
} catch (CertificateNotYetValidException e) {
certificateIsValid = false;
return;
}
GcExcel允许您使用 ISignature 接口的 delete 方法从签名行中删除数字签名。签名行会被保留,需要单独删除(方法参考前面所述)。
请参阅以下代码示例,从工作簿中已签名的签名行中删除数字签名。
// Create a new workbook
Workbook workbook = new Workbook();
// This file contains 1 signed signature line and
// a not signed signature line.
workbook.open("signsignaturelines.xlsx");
// Use DigitalSignatureOnly mode, because the workbook was already signed.
// If you don't open it with digital signature only mode,
// all existing signatures will be removed after saving the workbook.
XlsxOpenOptions openOption = new XlsxOpenOptions();
openOption.setDigitalSignatureOnly(true);
// Remove signature of signed signature line.
for (ISignature signature : workbook.getSignatures()) {
if (signature.getIsSignatureLine() && signature.getIsSigned()) {
// Remove digital signature.
// The signature line will not be removed from the SignatureSet
// in digital signature only mode.
// Because signature lines are shapes.
signature.delete();
break;
}
//commit signatures
workbook.save("DeleteDigitalSignature.xlsx");
在GcExcel Java中使用数字签名的依赖项完整列表可以从附件下载。
GcExcel_Java_Dependencies_DigitalSignature.docx
此功能中包含的签名格式已通过以下版本的测试:
目标Office版本
开发此功能时用于观察文件结构的office版本是office 365,版本号16.0.12228。
可以通过使用SignatureDetails.getApplicationVersion方法查看版本
最低Office版本
打开签名工作簿所需的最低Office版本是Office 2013。
用OpenJDK 14和oraclejdk 8测试了证书兼容性。它需要BouncyCastleProvider(在bcprov-jdk15on中)。
pfx证书导出已在windows10上进行了测试,版本号1909。
jks、jce、bks和ubr证书的生成已经在ubuntu18.04 LTS上的JDK 14 环境下进行了测试。
File extension | Signature algorithm | Private key protection algorithm | Type name | Provider | Is JDK 8 compatible | Is JDK 13+ compatible |
---|---|---|---|---|---|---|
*.pfx, *.p12 | RSA | AES-256 | PKCS12 | Not specified | Error 1 | Warning 1 |
RSA | Triple-DES | PKCS12 | Not specified | Warning 1 | Warning 1 | |
DSA, ECDsa | AES-256, Triple-DES | PKCS12 | Not specified | Error 3 | Error 3 | |
*.jks | RSA | JKS or PKCS12 | Not specified | Warning 1 | Warning 1 | |
DSA, ECDsa | JKS | Not specified | Error 3 | Error 3 | ||
*.jce | RSA | Triple-DES | JCEKS | SunJCE | TRUE | TRUE |
DSA, ECDsa | Triple-DES | JCEKS | SunJCE | Error 3 | Error 3 | |
*.bks | RSA | Triple-DES | BKS | BC | TRUE | TRUE |
DSA, ECDsa | Triple-DES | BKS | BC | Error 3 | Error 3 | |
*.ubr | RSA | PBE/SHA1/Twofish | UBER | BC | TRUE | TRUE |
DSA, ECDsa | PBE/SHA1/Twofish | UBER | BC | Error 3 | Error 3 | |
*.pem | RSA, DSA, ECDsa | Not supported | I've tried almost all possible type names | Not specified | Error 2 | Error 2 |
*Error 1 - Unable to load certificate if private key is encrypted with AES256-SHA256 mode.
*Error 2 - java.io.IOException: Invalid keystore format.
*Error 3 - If provider was not specified, then throws java.security.UnrecoverableKeyException: Get Key failed: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
*Warning 1 - Make sure the code cleanup provides newly registered providers. Otherwise, there might be an error when using this certificate format.
例如,假设为了使用BouncyCastleProvider打开BKS证书,你已经注册了BouncyCastleProvider。然后你需要在打开JKS或pfx证书之前注销它。如果你正在编写单元测试,请考虑将它们配置为同步运行。
可能的解决方案
升级至最新JDK(推荐)。
将证书格式转换为支持的格式。
使用平台支持的较弱的加密算法。例如,使用OpenSSL将AES256-SHA256降级为TripleDES-SHA1。
使用值得信任的第三方证书提供商。
自己开发一个新的证书提供程序(高级)。
只支持Microsoft Office签名行。
不支持Emf图像文件。因此,在导出签名行时,如果签名图像是emf格式的,则跳过它。当预览图像也是emf图像时,将导出占位符代替预览图像。
签名行的日期格式不遵循系统配置,但Excel会遵循。
使用X.509证书必须有密码。
使用数字签名需要Java8或更高版本。否则,在运行时打开已签名的工作簿或对工作簿签名时将引发异常。
如果您使用PKCS#12文件(*.pfx)存储带有AES-256加密的私钥,则您的应用程序或服务必须在OpenJDK或Oracle JDK 11.0.3、12.0.2或13+上运行。有关详细信息,请参阅这些错误的详细信息(JDK-8214513 和 JDK-8220734)。注意:Triple-DES 不足以保护你的私钥。参考这个。
在jdk9或更高版本上运行时,会出现一个警告"An illegal reflective access operation has occurred"。
如果证书链包含两个或更多项,则证书验证返回不正确的结果。这是因为KeyStore.getCertificateChain(String)方法无法从Windows证书存储或OpenSSL证书存储获取证书链。
使用Apache POI可能会改变类加载的顺序或者破坏组件本身的版本控制。
SLF4J打印警告"SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder""。这个警告无法删除,因为用户可能使用SLF4J写入日志。
签名或验证工作簿时,只能使用RSA。造成这种限制的原因是Java默认的秘钥存储和org.bouncycastle.jcajce.provider.asymmetric.rsa.DigestSignatureSpi。
Key Algorithm | Action | Supported |
---|---|---|
RSA | Sign/Verify | Yes |
DSA | Sign/Verify | No |
ECDsa | Sign/Verify | No |