HotXLS verifiziert und signiert Excel-Digitalsignaturen in Delphi neu, ohne dass Excel installiert ist. Die Unit lxSignatureOpcIntegrity prüft ECMA-376 Part 2 Paket-Signaturen direkt aus den Workbook-Bytes: RSA-SHA256 über das kanonische SignedInfo, jeden SHA-256-Reference-Digest und die exakten Parts, die jede Signatur auswählt. Zertifikatsketten-Vertrauen ist ein separater, expliziter Schritt, und Editieren oder Neu-Signieren veröffentlicht nur Output, der erneut verifiziert
Der übliche Auslöser ist ein Dokumenten-Workflow, der aus dem Desktop hinausgewachsen ist. Ein Controller signiert eine Budget-Arbeitsmappe in Excel, ein Delphi-Dienst muss später die Signatur bestätigen, bevor er die Zahlen importiert, und ein dritter Schritt muss ein Sheet korrigieren und unter einem Service-Zertifikat erneut signieren. Excel dafür auf einem Server zu automatisieren ist fragil und unsupported, und die Signatur als Ja/Nein-Flag zu behandeln versteckt die beiden Fragen, die tatsächlich zählen: Zu welchen Bytes hat sich der Unterzeichner committed, und vertrauen Sie der Person, die diesen Schlüssel hält
Was deckt eine OPC-Paket-Signatur tatsächlich ab?
Eine OPC-Paket-Signatur deckt nur die Parts ab, die ihr Manifest auflistet, nicht die ganze XLSX-Datei. ECMA-376 Part 2, §13 (Digital Signatures), platziert einen Signature-Origin-Part im Paket, typischerweise /_xmlsignatures/origin.sigs, mit Relationships zu einem oder mehreren Signature-Parts wie /_xmlsignatures/sig1.xml. Jeder Signature-Part ist eine W3C-XML-DSig-Signature, deren SignedInfo auf ein internes idPackageObject verweist, und dieses Object trägt ein Manifest aus References auf einzelne Package-Parts, jeder mit eigenem Digest
HotXLS unterstützt drei Arten, wie eine Reference einen Part binden kann, und sie sind nicht austauschbar. Eine rohe Reference hasht die gespeicherten Part-Bytes. Eine Canonical-XML-Reference hasht die Form Canonical XML (without comments), sodass Attributreihenfolge und die Schreibweise von Namespace-Deklarationen keine Rolle mehr spielen. Eine Relationship-Reference wendet zuerst die OPC-RelationshipTransform an und kanonisiert dann, und diese Transform behält nur die Relationships, die ihre SourceId- oder SourceType-Selektoren treffen. Ein Selektor authentifiziert exakt die Relationships, die er auswählt: Der Rest dieses .rels-Parts darf sich frei ändern, während die Signatur gültig bleibt
Dieser letzte Punkt ist der Grund, warum HotXLS Coverage meldet statt eines einzelnen grünen Häkchens. TXLSSignatureOpcIntegrityResult.Coverage ist ein Array aus TXLSSignatureOpcCoverage-Records, einer pro authentifizierter Manifest-Reference, jeweils mit ReferenceUri, PartName, ContentType, Transforms, RelationshipSourceIds und RelationshipSourceTypes. Wenn Ihr Import sheet3.xml liest und kein Deskriptor es nennt, sagt die Signatur nichts über die Zahlen aus, die Sie gleich laden
Signaturen aus einem unveränderlichen ZIP-Snapshot verifizieren
HotXLS verifiziert ein Paket aus einem einzigen unveränderlichen Byte-Snapshot, nie aus der Datei auf der Platte oder einem lebenden TWorkbook. XlsVerifySignatureOpcPackage und seine Multi-Signature-Geschwister nehmen ein TBytes-Array, lesen nur dieses Array und holen niemals externe Ziele, Zertifikate, lokale Dateien oder Netzwerkressourcen. Der Punkt: Die Bytes, die Sie verifiziert haben, sind die Bytes, die Sie danach parsen — wird die Datei später neu gelesen, kann ein anderer Prozess sie zwischenzeitlich ersetzt haben
Die Kanonisierung ist der Ort, an dem selbst gebaute Verifizierer meist scheitern. SignedInfo und das Package-Object müssen im Namespace-Kontext der vollständigen Signature-Hülle kanonisiert werden. Ein isoliertes Fragment, aus dem Dokument herausgeschnitten, erbt andere Namespace-Deklarationen als der Verifizierer an Ort und Stelle sieht, also unterscheiden sich seine C14N-Bytes, der Digest unterscheidet sich, und eine vollkommen gültige Signatur scheitert. HotXLS verwendet den vollen Hüllen-Kontext auf der verifizierenden wie auf der ausstellenden Seite
Für Pakete, die mehr als eine Signatur tragen können, verifiziert XlsVerifyAllSignaturesOpcPackage jede angehängte Signatur und behält die Einzelergebnisse. Der Aggregatstatus ist xsoiIntegrityVerified nur, wenn jede zugelassene Signatur verifiziert; sonst ist er der erste Fehlschlag in Inventarreihenfolge, und jeder Report sagt weiterhin, welcher Signature-Part scheiterte
uses
System.SysUtils, System.IOUtils, lxSignatureOpcIntegrity;
function AllSignaturesIntact(const FileName: string): Boolean;
var
Bytes: TBytes;
Reports: TXLSSignatureOpcVerifications;
Status: TXLSSignatureOpcIntegrityStatus;
I, J: Integer;
begin
Bytes := TFile.ReadAllBytes(FileName); // ein Snapshot, verifiziert und geparst
Status := XlsVerifyAllSignaturesOpcPackage(Bytes, Reports);
for I := 0 to High(Reports) do
begin
Writeln(String(Reports[I].SignaturePart), ': ', Ord(Reports[I].Status));
if Reports[I].Status = xsoiIntegrityVerified then
for J := 0 to High(Reports[I].Integrity.Coverage) do
Writeln(' covers ', String(Reports[I].Integrity.Coverage[J].PartName));
end;
Result := Status = xsoiIntegrityVerified; // xsoiUnsigned ist kein Bestehen
end;
Die Statuswerte sind konkret genug, um darauf zu handeln. xsoiDigestMismatch bedeutet, dass ein gewählter Part sich nach dem Signieren geändert hat, xsoiInvalidSignature, dass der RSA-Wert sich gegen SignedInfo mit dem eingebetteten Zertifikatsschlüssel nicht verifiziert, xsoiPartUnavailable, dass ein gewählter Part fehlt, und xsoiUnsupportedProfile oder xsoiResourceLimit, dass der Verifizierer sich geweigert hat, statt zu raten. Die darunterliegende ZIP-Schicht wendet dasselbe Fail-closed-Lesen an, das im Artikel zur ZIP-EOCD-Validierung für nicht vertrauenswürdige XLSX-Dateien beschrieben ist, und die Part-Suche folgt den Regeln der XLSX-OPC-Relationship-Auflösung
Warum macht das Editieren einer signierten Arbeitsmappe die Signatur ungültig?
Das Editieren einer signierten Arbeitsmappe macht die Signatur ungültig, sobald ein geändertes Byte in eine signierte Auswahl fällt, denn der gespeicherte SHA-256-Digest für diese Reference passt nicht mehr. Ein Spreadsheet-Writer, der die Arbeitsmappe lädt und erneut speichert, fasst fast immer signierte Parts an: Er serialisiert XML neu, nummeriert Shared Strings um, schreibt workbook.xml und das Content-Type-Manifest neu. Selbst wenn die Zellwerte identisch sind, sind die Bytes es nicht, und die nächste Verifikation liefert xsoiDigestMismatch
HotXLS lässt Sie die Frage stellen, bevor Sie sich auf eine Bearbeitung festlegen. XlsEditVerifiedOpcPackage ersetzt bestehende Parts in einem verifizierten Paket und verifiziert dann den Kandidaten. Mit xsoePreserve liefert es xsoePreserved nur, wenn die Signatur weiterhin verifiziert — was passiert, wenn die editierten Parts außerhalb jeder signierten Auswahl liegen —, und sonst xsoeWouldInvalidate. Mit xsoeInvalidateChanged entfernt es die Signaturbindung aus den Root-Relationships und [Content_Types].xml und gibt ein explizit unsigniertes Paket als xsoeInvalidated zurück, statt Ihnen eine Datei zu reichen, die noch signiert aussieht
function EditKeepsSignature(const Signed, NewSheetXml: TBytes;
out Edited: TBytes): Boolean;
var
Edits: TXLSSignatureOpcEdits;
Details: TXLSSignatureOpcEditResult;
begin
SetLength(Edits, 1);
Edits[0].PartName := '/xl/worksheets/sheet2.xml';
Edits[0].Data := NewSheetXml;
case XlsEditVerifiedOpcPackage(Signed, Edits, xsoePreserve, Edited, Details) of
xsoePreserved: Result := True; // sheet2 lag außerhalb der signierten Auswahl
xsoeWouldInvalidate: Result := False; // ein signierter Digest deckt sheet2 ab
else
raise Exception.CreateFmt('Edit refused, original integrity %d',
[Ord(Details.OriginalIntegrity)]);
end;
end;
Integrität gegenüber Vertrauen: Was beweist eine verifizierte Signatur?
Eine verifizierte Signatur beweist, dass die ausgewählten Parts unverändert sind, seit jemand mit dem privaten Schlüssel sie signiert hat — und nichts darüber, wer dieser jemand ist. HotXLS hält die beiden Schichten absichtlich auseinander: Jedes TXLSSignatureOpcIntegrityResult trägt Trust = xsstUntrusted, eingeschlossen erfolgreicher Prüfungen, und ClaimedSignatureTime ist vom Unterzeichner gelieferte Angabe, kein vertrauenswürdiger Zeitstempel. Jeder kann ein selbstsigniertes Zertifikat erzeugen, eine Arbeitsmappe signieren und die Integritätsprüfung bestehen
Vertrauen ist ein separater Aufruf mit einer Policy im Besitz des Aufrufers. XlsDefaultSignatureOpcTrustPolicy liefert eine Fail-closed-Policy ohne Anker, ohne Verifikationszeit, mit der Document-Signing-EKU 1.3.6.1.4.1.311.10.3.12 und xsrvRequireCached-Revocation. XlsVerifySignatureOpcPackageWithTrust fährt zuerst den vollständigen Integritätsverifizierer und evaluiert dann das Zertifikat, das ihn tatsächlich bestanden hat; ein Aufrufer kann kein anderes Leaf-Zertifikat unterschieben
uses
System.SysUtils, System.DateUtils, lxSignatureOpcIntegrity;
function SignedUnderCorporateCA(const Bytes, RootDer, IssuingCaDer: TBytes): Boolean;
var
Policy: TXLSSignatureOpcTrustPolicy;
Trust: TXLSSignatureOpcTrustResult;
begin
Policy := XlsDefaultSignatureOpcTrustPolicy;
SetLength(Policy.TrustAnchors, 1);
Policy.TrustAnchors[0] := RootDer;
SetLength(Policy.Intermediates, 1);
Policy.Intermediates[0] := IssuingCaDer;
Policy.VerificationTimeUtc := TTimeZone.Local.ToUniversalTime(Now);
Result := XlsVerifySignatureOpcPackageWithTrust(Bytes, Policy, Trust)
= xsotTrustedSelection;
if not Result then
Writeln(Format('trust %d, integrity %d, policy error %.8x, chain errors %.8x',
[Ord(Trust.Status), Ord(Trust.IntegrityStatus),
Trust.NativePolicyError, Trust.NativeChainErrors]));
end;
Das Windows-Backend hat zwei Fallen, die leicht übersehen werden, wenn man WinCrypt selbst aufruft. Dass CertVerifyCertificateChainPolicy True zurückgibt, bedeutet nur, dass der Aufruf ausgeführt wurde; die Annahme verlangt weiterhin ein null dwError im Policy-Status, null aggregierte Chain-Fehler, eine explizite digitalSignature-Key-Usage am Leaf und die geforderte EKU. HotXLS stellte fest, dass ein Zertifikat ohne Key-Usage die gewöhnliche Base-Policy bestehen kann, also prüft es DigitalSignatureUsage und RequiredEkuPresent selbst. Die zweite Falle: Eine exklusive Root-Store mit hRestrictedOther in der Chain-Engine-Konfiguration zu kombinieren liefert E_INVALIDARG auf dem aktuellen Backend, also verwendet HotXLS die exklusive Caller-Root-Store und autorisiert danach jedes Element der zurückgegebenen primären Kette gegen die von Ihnen gelieferten DER-Bytes. Ein Issuer, der zufällig in einem lokalen System-Store sitzt, wird dadurch nie vertrauenswürdig
xsotTrustedSelection beschreibt weiterhin nur diese Signatur und ihre gemeldete Coverage. Unsignierte Zellen und nicht gewählte Relationships dürfen sich ändern, während die gewählte Signatur vertrauenswürdig bleibt — deshalb liefert das Trust-Ergebnis auch PackageSha256 und CertificateSha256 für Ihr Audit-Log
Wie signiert oder gegensigniert man eine Arbeitsmappe ohne Excel neu?
Auf Byte-Array-Ebene signiert HotXLS über explizite, autorisierte Transaktionen neu, und die signierten Save-Pfade des Workbooks wiederverwenden dieselben verifizierten Pipelines. XlsReplaceVerifiedSignatureOpcPackage tauscht eine verifizierte Signatur gegen eine neue, XlsEditAndReplaceSelectedSignatureOpcPackage wendet Part-Edits an und ersetzt eine gewählte Signatur in derselben Transaktion, und XlsAppendVerifiedSignatureOpcPackage ergänzt eine Signatur, während es jedes verifizierte Original Byte für Byte hält. Die Replace-Aufrufe nehmen eine TXLSSignatureOpcReplaceAuthorization, der Append-Aufruf verifiziert jedes Original zuerst, und alle nehmen eine TXLSSignatureOpcIssueOptions-Auswahl und einen Signer-Callback; die Bibliothek berührt Ihren privaten Schlüssel nie
Der Autorisierungs-Record bindet die Operation an das exakte Original, das Sie freigegeben haben: ExpectedPackageSha256, ExpectedSignaturePart und ExpectedCertificateSha256, alle erst verglichen, nachdem die alte Signatur vollständig verifiziert ist. Jeder editierte Part muss von der neuen Auswahl abgedeckt sein, jede nicht gewählte Signatur muss sich weiterhin gegen das gestaffelte Paket verifizieren, bevor der Signer gerufen wird, und der Aufruf liefert sonst xspsWouldInvalidate
uses
System.SysUtils, System.DateUtils, lxHash, lxSignatureOpcIntegrity;
type
TWorkbookResigner = class
private
function SignDigest(const Request: TXLSSignatureOpcSelectedReplaceRequest;
var Signature: TBytes): TXLSSignatureOpcSignerStatus;
public
function Resign(const Approved, NewSheetXml, NewCertDer: TBytes;
const OldCertSha256: WideString; var Output: TBytes): Boolean;
end;
function TWorkbookResigner.SignDigest(
const Request: TXLSSignatureOpcSelectedReplaceRequest;
var Signature: TBytes): TXLSSignatureOpcSignerStatus;
begin
// Request.NewSignature.Digest ist der 32-Byte-SHA-256, den die Bibliothek berechnete;
// RsaSignSha256Pkcs1 steht für Ihren eigenen CNG-, HSM- oder Smartcard-Wrapper und
// muss die volle Big-Endian-RSASSA-PKCS1-v1_5-Signatur zurückgeben
if RsaSignSha256Pkcs1(Request.NewSignature.Digest, Signature) then
Result := xsosSigned
else
Result := xsosFailed;
end;
function TWorkbookResigner.Resign(const Approved, NewSheetXml, NewCertDer: TBytes;
const OldCertSha256: WideString; var Output: TBytes): Boolean;
var
Auth: TXLSSignatureOpcReplaceAuthorization;
Options: TXLSSignatureOpcIssueOptions;
Edits: TXLSSignatureOpcEdits;
Details: TXLSSignatureOpcSelectedReplaceResult;
begin
Auth.ExpectedPackageSha256 := XLSComputeSHA256Hex(Approved); // oder der bei der Freigabe gespeicherte Hash
Auth.ExpectedSignaturePart := '/_xmlsignatures/sig1.xml';
Auth.ExpectedCertificateSha256 := OldCertSha256;
SetLength(Edits, 1);
Edits[0].PartName := '/xl/worksheets/sheet1.xml';
Edits[0].Data := NewSheetXml;
SetLength(Options.References, 2);
Options.References[0].PartName := '/xl/workbook.xml';
Options.References[0].Mode := xsimCanonicalXml;
Options.References[1].PartName := '/xl/worksheets/sheet1.xml';
Options.References[1].Mode := xsimCanonicalXml;
Options.ClaimedSignatureTimeUtc := FormatDateTime('yyyy"-"mm"-"dd"T"hh":"nn":"ss"Z"',
TTimeZone.Local.ToUniversalTime(Now));
Result := XlsEditAndReplaceSelectedSignatureOpcPackage(Approved, NewCertDer,
Auth, Options, Edits, SignDigest, Output, Details) = xspsReplaced;
end;
Zwei Details in diesem Code sind absichtlich so. Output und Details müssen getrennte Variablen sein; ein Feld von Details als Output zu übergeben ist explizit nicht unterstützt, denn die Bibliothek kann kein Paket veröffentlichen und einen Zertifikatsreport im selben Speicher halten. Und die alte Coverage wird nicht geerbt: Die neue Signatur authentifiziert exakt die References, die Sie auflisten — entscheiden Sie also erneut, auf welche Parts ein nachgelagerter Konsument sich stützen wird
Gegensignieren verwendet denselben Append-Aufruf. Seit HotXLS 2.384.296 darf TXLSSignatureOpcIssueOptions.References, übergeben an XlsAppendVerifiedSignatureOpcPackage, Parts aus dem verifizierten Original-Signatur-Inventar nennen, sodass ein Prüfer über einen bestehenden Signature-Part signieren, mehrere Counter-Signatures ketten oder Signature-Parts und Content-Parts in einer Auswahl mischen kann. Jede angehängte Signatur verzeichnet nur ihre direkten Auswahlen. Die Qualifikation für dieses Release lief 222 Boundary-Checks und 122 unabhängige Checks mit dem .NET-WindowsBase-Packaging-Verifizierer über 7 Ausgabepakete, mit Bestätigung von genau einer neuen Signatur sowie byte-identischen Original-Signaturen und Zertifikaten
Fallstricke und Grenzen, die man kennen sollte
Der lehrreichste Bug kam von einem Zeichen, das vollkommen legal ist. OPC-Reference-URIs tragen den Content-Type als Query, etwa /xl/worksheets/sheet1.xml?ContentType=application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml. Ein früher HotXLS-Issuer prozent-kodierte das literale + als %2B. HotXLS' eigene RSA-, C14N- und Reference-Prüfungen akzeptierten das Ergebnis, aber WindowsBase meldete InvalidSignature; eine kontrollierte Probe, die nur diese Schreibweise änderte, ging von InvalidSignature zu Success. Der Issuer behält jetzt unterstützte literale ASCII-Query-Interpunktion bei und escaped weiterhin XML-Attribute, und die Lehre für jeden Signer: gegen einen unabhängigen nativen Verifizierer testen, nicht nur gegen den eigenen
- Das Trust-Backend ist Windows-only; andere Ziele liefern
xsotTrustUnavailable, nachdem die Integrität bestanden ist - Der Verifizierer akzeptiert Pakete bis 64 MiB, 16 MiB pro Part und höchstens 32 Signature-Parts unter einem Origin
- Das Ausstellen akzeptiert bis zu 256 References und 4096 kombinierte Selektoren pro Signatur
- Das unterstützte Profil ist RSA-SHA256, SHA-256-Digests und Canonical XML without comments; andere Algorithmen, zusätzliche Application-Objects und externe References liefern
xsoiUnsupportedProfile xsrvRequireCachednutzt rein gecachte Revocation-Daten; fehlender Status weist ab, undxsrvNotCheckedgibt keine Revocation-Zusage- Eine Arbeitsmappe zu signieren, die Excel ebenfalls akzeptieren muss, stößt weiterhin auf die Paketregeln aus warum Excel ein gültiges XLSX repariert; ein duplizierter Override oder eine duplizierte Relationship-Id bricht die Datei, bevor irgendeine Signatur geprüft wird
FAQ
Bedeutet eine HotXLS-Integritätsprüfung, dass der Unterzeichner vertrauenswürdig ist?
Nein. XlsVerifySignatureOpcPackage beweist, dass die gewählten Parts unter dem eingebetteten Zertifikatsschlüssel zu ihren Digests passen, und meldet stets xsstUntrusted. Verwenden Sie XlsVerifySignatureOpcPackageWithTrust mit Ihren eigenen Ankern und einer UTC-Verifikationszeit, um die Zertifikatskette zu bewerten
Warum zeigt Excel meine Arbeitsmappen-Signatur nach einem Delphi-Edit als ungültig?
Ein gespeichertes Workbook schreibt signierte XML-Parts neu, also ändert sich der SHA-256-Digest mindestens einer Reference. Verwenden Sie XlsEditVerifiedOpcPackage mit xsoePreserve, um eine Bearbeitung zuerst zu testen, oder XlsEditAndReplaceSelectedSignatureOpcPackage, um in einer verifizierten Transaktion zu editieren und neu zu signieren
Kann HotXLS mit einem Schlüssel auf Smartcard oder HSM signieren?
Ja, denn Signieren ist ein Caller-Callback. Die Bibliothek übergibt das kanonische SignedInfo und seinen 32-Byte-SHA-256-Digest, Ihr Code liefert die RSASSA-PKCS1-v1_5-Signatur zurück, und der private Schlüssel verlässt nie den Provider, den Sie kontrollieren
OPC-Signaturverifikation, Trust-Bewertung, Gegensignieren und verifiziertes Neu-Signieren sind Teil von HotXLS, der nativen Excel-Spreadsheet-Komponente für Delphi und C++Builder, die XLS, XLSX, XLSB und ODS ohne Excel oder COM-Automatisierung liest und schreibt