مقال تقني

التحقق من تواقيع Excel الرقمية وإعادة التوقيع في Delphi

تتحقق HotXLS من تواقيع Excel الرقمية وتعيد توقيعها داخل Delphi دون تثبيت Excel. تفحص وحدة lxSignatureOpcIntegrity تواقيع الحزمة في ECMA-376 Part 2 مباشرة من بايتات المصنف: ‏RSA-SHA256 على SignedInfo القانوني، وكل بصمة SHA-256 للـ Reference، والأجزاء التامة التي ينقيها كل توقيع. وثقة سلسلة الشهادات خطوة منفصلة صريحة، ولا ينشر التحرير أو إعادة التوقيع إلا مخرجات تتحقق من جديد

المحفز المعتاد سير عمل مستندات تجاوز سطح المكتب. يوقع متحكم مصنف ميزانية في Excel، وعلى خدمة Delphi لاحقاً تأكيد التوقيع قبل استيراد الأعداد، وتحتاج خطوة ثالثة تصحيح ورقة واحدة وإعادة التوقيع بشهادة خدمة. أتمتة Excel على خادم لذلك هشة وغير مدعومة، ومعاملة التوقيع بوصفه علماً نعم/لا يخفي السؤالين المهمين فعلاً: أي بايتات التزم بها الموقّع، وهل تثق بالشخص الذي يحمل ذلك المفتاح

ماذا يغطي توقيع حزمة OPC فعلاً؟

توقيع حزمة OPC يغطي فقط الأجزاء التي يسردها Manifest الخاص به، لا ملف XLSX كله. يضع ECMA-376 Part 2 §13 (Digital Signatures) جزء أصل التوقيع في الحزمة، عادة /_xmlsignatures/origin.sigs، بعلاقات إلى جزء توقيع أو أكثر مثل /_xmlsignatures/sig1.xml. كل جزء توقيع هو Signature من W3C XML-DSig تشير SignedInfo فيه إلى idPackageObject داخلي، ويحمل ذلك Object بيان Manifest من References إلى أجزاء الحزمة الفردية، لكل منها بصمتها

تدعم HotXLS ثلاث طرق يربط بها Reference جزءاً، وهي ليست قابلة للتبادل. Reference خام يجزئ بايتات الجزء المخزنة. و Reference XML قانوني يجزئ صيغة Canonical XML ‏(دون تعليقات)، فلم يعد ترتيب السمات وتهريب تعريف النطاق مهمين. و Reference علاقات يطبق أولاً OPC RelationshipTransform ثم يقنن، ويحتفظ ذلك التحويل بالعلاقات المطابقة لمختاراته SourceId أو SourceType فقط. مُختار يصادق على العلاقات التي ينقيها بالضبط: يمكن لبقية جزء .rels ذاك أن يتغير بحرية بينما يبقى التوقيع صالحاً

تشريح التوقيع الرقمي OPC في HotXLS لـ Delphi: يقود جزء origin.sigs إلى أجزاء تواقيع XML-DSig يربط بيان Manifest فيها كل جزء حزمة بمرجع خام أو XML قانوني أو RelationshipTransform
أنماط Reference الثلاثة ليست قابلة للتبادل: الخام يجزئ البايتات المخزنة، و XML القانوني يتجاهل تهريب السمات، ومختارات العلاقات تترك مدخلات .rels غير المنتقاة حرة التغير

تلك النقطة الأخيرة سبب إبلاغ HotXLS عن التغطية بدل علامة صح واحدة.‏ TXLSSignatureOpcIntegrityResult.Coverage مصفوفة من سجلات TXLSSignatureOpcCoverage، واحدة لكل Manifest Reference مصادق عليه، بـ ReferenceUri و PartName و ContentType و Transforms و RelationshipSourceIds و RelationshipSourceTypes. وإن قرأ استيرادك sheet3.xml ولم يسمه وصفٌ، فالتوقيع لا يقول شيئاً عن الأعداد التي أنت على وشك تحميلها

التحقق من التواقيع من لقطة ZIP غير قابلة للتغيير

تتحقق HotXLS من الحزمة من لقطة بايتات واحدة غير قابلة للتغيير، لا من الملف على القرص ولا من TWorkbook حي. تأخذ XlsVerifySignatureOpcPackage وأشقاؤها متعددو التواقيع مصفوفة TBytes، ويقرؤون تلك المصفوفة فقط، ولا يجلبون أبداً أهدافاً خارجية أو شهادات أو ملفات محلية أو موارد شبكة. والمغزى أن البايتات التي تحققت منها هي البايتات التي تحللها بعد ذلك: فإن أعيدت قراءة الملف لاحقاً فربما استبدله عملية أخرى في الأثناء

التقنين هو حيث تخطئ المدققات المكتوبة داخلياً عادة. يجب تقنين SignedInfo و Object الحزمة في سياق نطاقات المغلف Signature التام. فقاعة معزولة مقطوعة من المستند ترث تعريفات نطاقات مختلفة عمّا تراه المدققة في موضعها، فتختلف بايتات C14N وتختلف البصمة ويفشل توقيع سليم تماماً. تستخدم HotXLS سياق المغلف التام على جانبَي التحقق والإصدار

للحزم التي قد تحمل أكثر من توقيع، تتحقق XlsVerifyAllSignaturesOpcPackage من كل توقيع ملحق وتحفظ النتائج الفردية. الحالة الكلية xsoiIntegrityVerified فقط عندما يتحقق كل توقيع مقبول؛ وإلا فهي أول فشل بترتيب الجرد، وكل تقرير ما يزال يخبرك أي جزء توقيع فشل

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);  // لقطة واحدة، تم التحقق منها وحُللت
  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 ليست نجاحاً
end;

قيم الحالة محددة بما يكفي للتصرف عليها.‏ xsoiDigestMismatch تعني أن جزءاً منتقى تغير بعد التوقيع، و xsoiInvalidSignature تعني أن قيمة RSA لا تتحقق مقابل SignedInfo بمفتاح الشهادة المضمّنة، و xsoiPartUnavailable تعني أن جزءاً منتقى مفقود، و xsoiUnsupportedProfile أو xsoiResourceLimit تعنيان أن المدققة رفضت بدل أن تخمّن. تُطبق طبقة ZIP تحتها القراءة نفسها بالإغلاق عند الفشل الموصوفة في مقالة التحقق من EOCD لملفات ZIP في ملفات XLSX غير الموثوقة، ويلتزم بحث الأجزاء بقواعد حل علاقات OPC في XLSX

لماذا يُبطل تحرير مصنف موقع التوقيع؟

يبطل تحرير مصنف موقع التوقيع بمجرد أن يقع بايت متغير داخل انتقاء موقّع، لأن بصمة SHA-256 المخزنة لذلك Reference لم تعد تطابق. كاتب جداول يحمّل المصنف ويحفظه من جديد يلمس الأجزاء الموقعة تقريباً دوماً: يعيد تسلسل XML، ويعيد ترقيم السلاسل المشتركة، ويعيد كتابة workbook.xml وبيان أنواع المحتوى. وحتى حين تكون قيم الخلايا مطابقة فإن البايتات ليست، وتعيد المحاولة التالية للتحقق xsoiDigestMismatch

تتيح HotXLS طرح السؤال قبل الالتزام بتحرير. يستبدل XlsEditVerifiedOpcPackage أجزاء قائمة في حزمة متحقق منها ثم يتحقق من المرشح. مع xsoePreserve يعيد xsoePreserved فقط عندما يظل التوقيع متحققاً، وهو ما يحدث عندما تقع الأجزاء المحررة خارج كل انتقاء موقعّ، و xsoeWouldInvalidate خلاف ذلك. ومع xsoeInvalidateChanged يزيل ربط التوقيع من علاقات الجذر و [Content_Types].xml ويعيد حزمة غير موقعة صراحةً بوصفها xsoeInvalidated، بدل أن يسلّمك ملفاً ما يزال يبدو موقعاً

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 خارج الانتقاء الموقع
    xsoeWouldInvalidate: Result := False;  // بصمة موقعة تغطي sheet2
  else
    raise Exception.CreateFmt('Edit refused, original integrity %d',
      [Ord(Details.OriginalIntegrity)]);
  end;
end;

السلامة مقابل الثقة: ماذا يثبت توقيع متحقق؟

التوقيع المتحقق يثبت أن الأجزاء المنتقاة لم تتغير منذ أن وقّعها أحدهم مالك المفتاح الخاص، ولا يقول شيئاً عن هوية ذلك أحدهم. تفصل HotXLS الطبقتين عمداً: كل TXLSSignatureOpcIntegrityResult يحمل Trust = xsstUntrusted، بما في ذلك الفحوصات الناجحة، و ClaimedSignatureTime بيانات يقدمها الموقّع لا طابعاً زمنياً موثوقاً. يستطيع أي شخص توليد شهادة موقعة ذاتياً، وتوقيع مصنف، واجتياز فحص السلامة

تتحقق HotXLS من التواقيع الرقمية لحزم OPC في Excel داخل Delphi بطبقتين: فحوص بصمات السلامة على لقطة بايتات غير قابلة للتغيير تبلّغ دوماً عن xsstUntrusted، ثم XlsVerifySignatureOpcPackageWithTrust بسياسة يملكها المستدعي
التوقيع المتحقق يثبت أن الأجزاء المنتقاة لم تتغير، بينما تتطلب الثقة مراسيك أنت وزمن تحقق UTC و EKU لتوقيع المستندات قبل أن يعني xsotTrustedSelection شيئاً

الثقة استدعاء منفصل بسياسة يملكها المستدعي. تعيد XlsDefaultSignatureOpcTrustPolicy سياسة إغلاق عند الفشل بلا مراسي وبلا زمن تحقق، مع EKU لتوقيع المستندات 1.3.6.1.4.1.311.10.3.12 وإبطال xsrvRequireCached. تجري XlsVerifySignatureOpcPackageWithTrust أولاً مدقق السلامة التام ثم تقيّم الشهادة التي اجتازته فعلاً؛ ولا يستطيع المستدعي إحلال شهادة طرفية مختلفة

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;

لخلفية Windows فخّان يسهل فواتهما عندما تستدعي WinCrypt بنفسك. عودة CertVerifyCertificateChainPolicy بـ True تعني فقط أن الاستدعاء نُفذ؛ ولا يزال القبول يتطلّب dwError صفرياً في حالة السياسة، وأخطاء سلسلة كلية صفرية، واستخدام مفتاح digitalSignature صريحاً على الطرفية و EKU المطلوب. وجدت HotXLS أن شهادة تنقصها علامة استخدام المفتاح قد تجتاز سياسة الأساس العادية، فتفحص DigitalSignatureUsage و RequiredEkuPresent بنفسها. الفخ الثاني: دمج مخزن جذور حصري مع hRestrictedOther في إعداد محرك السلسلة يعيد E_INVALIDARG على الخلفية الحالية، فتستخدم HotXLS مخزن الجذور الحصري للمستدعي ثم تمنح كل عنصر من السلسلة الأولية المعادة التخويل مقابل بايتات DER التي زودتها به. مُصدر صادف الجلوس في مخزن نظام محلي لا يصير موثوقاً قط

تصف xsotTrustedSelection مع ذلك ذلك التوقيع وحده وتغطيته المبلَّغ عنها. الخلايا غير الموقعة والعلاقات غير المنتقاة يمكن أن تتغير بينما يبقى التوقيع المنتقى موثوقاً، ولهذا يعيد نتيجة الثقة أيضاً PackageSha256 و CertificateSha256 لسجل تدقيقك

كيف تعيد توقيع مصنف أو توقعه توقيعاً مضاداً دون Excel؟

على مستوى مصفوفات البايتات تعيد HotXLS التوقيع عبر معاملات صريحة مُخوَّلة، وتعيد مسارات الحفظ الموقع للمصنف استخدام أنابيب التحقق نفسها. يستبدل XlsReplaceVerifiedSignatureOpcPackage توقيعاً متحققاً بآخر جديد، ويطبق XlsEditAndReplaceSelectedSignatureOpcPackage تحريرات أجزاء ويستبدل توقيعاً منتقى واحداً في المعاملة نفسها، ويضيف XlsAppendVerifiedSignatureOpcPackage توقيعاً مع إبقاء كل أصلي متحقق منه بايتاً ببايت. تأخذ دعوتا الاستبدال TXLSSignatureOpcReplaceAuthorization، وتتحقق دعوة الإلحاق من كل أصلي أولاً، وكلها تأخذ انتقاء TXLSSignatureOpcIssueOptions واستدعاء موقّع عكسي؛ ولا تمس المكتبة مفتاحك الخاص قط

يربط سجل التخويل العملية بالأصلي الذي وافقته بعينه: ‏ExpectedPackageSha256 و ExpectedSignaturePart و ExpectedCertificateSha256، ولا تُقارن جميعها إلا بعد أن يتحقق التوقيع القديم تماماً. يجب أن يغطي الانتقاء الجديد كل جزء محرر، ويجب أن يظل كل توقيع غير منتقى متحققاً مقابل الحزمة المرحّلة قبل استدعاء الموقّع، وإلا أعادت الدعوة xspsWouldInvalidate

تعيد HotXLS توقيع حزمة Excel XLSX داخل Delphi عبر XlsEditAndReplaceSelectedSignatureOpcPackage، الذي يتحقق من التوقيع القديم، ويفحص بصمات الحزمة والجزء والشهادة المتوقعة، ويستدعي موقّعاً عكسياً
يربط سجل التخويل الاستبدال بالأصلي المعتمد، ويجب أن تظل التواقيع غير المنتقاة متحققة على الحزمة المرحلة، ويبقى المفتاح الخاص داخل CNG أو HSM أو البطاقة الذكية الخاصة بك
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 هي بصمة SHA-256 ذات 32 بايتاً حسبتها المكتبة؛
  // تمثل RsaSignSha256Pkcs1 غلاف CNG أو HSM أو البطاقة الذكية الخاص بك و
  // يجب أن يعيد توقيع RSASSA-PKCS1-v1_5 التام بترتيب البايتات الكبير
  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); // أو التجزئة المخزّنة عند الموافقة
  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;

تفصيلان في ذلك الكود مقصودان. يجب أن يكون Output و Details متغيرين منفصلين؛ وتمرير حقل من Details بوصفه Output غير مدعوم صراحة، لأن المكتبة لا تستطيع نشر حزمة وحفظ تقرير شهادة في التخزين نفسه. والتغطية القديمة لا تُورَّث: التوقيع الجديد يصادق على References التي تسردها بالضبط، فقرر من جديد أي أجزاء سيعتمد عليها مستهلك لاحق

التوقيع المضاد يستخدم دعوة الإلحاق نفسها. منذ HotXLS 2.384.296 يمكن أن تسمي TXLSSignatureOpcIssueOptions.References الممررة إلى XlsAppendVerifiedSignatureOpcPackage أجزاء من جرد التواقيع الأصلية المتحقق منها، فيستطيع مراجع التوقيع فوق جزء توقيع قائم، أو تسلسل عدة تواقيع مضادة، أو خلط أجزاء تواقيع وأجزاء محتوى في انتقاء واحد. يسجل كل توقيع ملحق انتقاءاته المباشرة فقط. جري تأهيل ذلك الإصدار بـ 222 فحصاً حدّياً و122 فحصاً مستقلاً بمدقق التغليف .NET WindowsBase عبر 7 حزم مخرجات، مؤكداً توقيعاً جديداً واحداً تماماً وتواقيعاً وشهادات أصلية مطابقة بايتياً

مزالق وحدود تستحق المعرفة

أكثر خطأ مفيد جاء من محرف مشروع تماماً. تحمل URIs للـ Reference في OPC نوع المحتوى استعلاماً، مثلاً /xl/worksheets/sheet1.xml?ContentType=application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml. مُصدِّر مبكر في HotXLS رمز بالنسبة المئوية المحرف + إلى %2B. قبلت فحوصات RSA و C14N و Reference الخاصة بـ HotXLS النتيجة، لكن WindowsBase بلغت InvalidSignature؛ عينة مضبوطة غيّرت ذلك التهريب وحده فانتقلت من InvalidSignature إلى Success. يحتفظ المُصدِّر الآن بعلامات الترقيم الاستعلامية ASCII الحرفية المدعومة مع الإبقاء على تهريب سمات XML، والدرس لأي موقّع أن يختبر مقابل مدقق أصلي مستقل، لا مقابل مدققك أنت فقط

  • خلفية الثقة Windows فقط؛ وتعيد الأهداف الأخرى xsotTrustUnavailable بعد نجاح السلامة
  • يقبل المدقق حزماً حتى 64 MiB، و16 MiB للجزء، وعلى الأكثر 32 جزء توقيع تحت أصل واحد
  • يقبل الإصدار حتى 256 References و4096 مختاراً مجمعاً لكل توقيع
  • الملف التعريفي المدعوم هو RSA-SHA256 وبصمات SHA-256 و Canonical XML دون تعليقات؛ الخوارزميات الأخرى و Objects تطبيق إضافية و References خارجية تعيد xsoiUnsupportedProfile
  • يستخدم xsrvRequireCached بيانات إبطال من المخزن فقط؛ الحالة المفقودة ترفض، و xsrvNotChecked لا تمنح أي ضمان إبطال
  • توقيع مصنف يجب أن تقبله Excel أيضاً ما يزال يصطدم بقواعد الحزمة في لماذا يُصلح Excel ملف XLSX سليماً؛ فـ Override أو معرف علاقة مكرر يكسر الملف قبل فحص أي توقيع

FAQ

هل يعني فحص السلامة في HotXLS أن الموقّع موثوق؟

لا. يثبت XlsVerifySignatureOpcPackage أن الأجزاء المنتقاة تطابق بصماتها تحت مفتاح الشهادة المضمّنة ويبلّغ دوماً عن xsstUntrusted. استخدم XlsVerifySignatureOpcPackageWithTrust بمراسيك أنت وزمن تحقق UTC لتقييم سلسلة الشهادات

لماذا تعرض Excel توقيع مصنفي بوصفه غير صالح بعد تحرير في Delphi؟

المصنف المحفوظ يعيد كتابة أجزاء XML الموقعة، فتتغير بصمة SHA-256 لـ Reference واحد على الأقل. استخدم XlsEditVerifiedOpcPackage مع xsoePreserve لاختبار تحرير أولاً، أو XlsEditAndReplaceSelectedSignatureOpcPackage للتحرير وإعادة التوقيع في معاملة متحققة واحدة

هل تستطيع HotXLS التوقيع بمفتاح مخزن على بطاقة ذكية أو HSM؟

نعم، لأن التوقيع استدعاء عكسي لدى المستدعي. تمرر المكتبة SignedInfo القانوني وبصمة SHA-256 ذات 32 بايتاً له، ويعيد كودك توقيع RSASSA-PKCS1-v1_5، ولا يترك المفتاح الخاص المزود الذي تتحكم فيه أبداً

التحقق من تواقيع OPC وتقييم الثقة والتوقيع المضاد وإعادة التوقيع المتحقق كلها من HotXLS، مكون جداول البيانات الأصلي لـ Excel داخل Delphi و C++Builder، الذي يقرأ ويكتب XLS و XLSX و XLSB و ODS دون Excel أو أتمتة COM