מאמר טכני

הצפנת פלט XLSX עם AES ב-Delphi: מה ש-SaveAsEncrypted של HotXLS כותב

ל-Excel יש שני דברים ששניהם נקראים "סיסמה", ורק אחד מהם הוא הצפנה. סיסמת הפתיחה מצפינה צופן אמיתי: בלעדיה אי-אפשר לקרוא את הקובץ כלל. סיסמאות ההגנה של גיליון העבודה וחוברת העבודה אינן עושות דבר מהסוג הזה. הן מגדירות דגל שעורך משתף-פעולה מסכים לכבד, וחוברת עבודה הנושאת אך ורק את הדגל הזה היא zip קריא רגיל עם הנתונים יושבים בטקסט גלוי. בחרו את הלא נכון ותשלחו שכר העובד שנראה נעול ב-Excel ונקרא בכל עורך טקסט

ההוכחה לוקחת עשר שניות. שנו את שם .xlsx מוגן ל-.zip, פתחו אותו בכל כלי ארכיון, והסתכלו ב-xl/worksheets/sheet1.xml. אם ערכי התאים שם בטקסט UTF-8 פשוט, הקובץ אינו מוצפן, ולא משנה כמה הנחיות סיסמה Excel מעלה כשמישהו מנסה לערוך תא. הפער הזה שורד שנים בתוך צוותים המניחים שהגנת גיליון היא סודיות, והוא בדרך כלל צף ביום שבו סקירת אבטחה מריצה בדיוק את שינוי-השם הזה

HotXLS היא ספריית גיליונות אלקטרוניים מקורית ל-Delphi ול-C++Builder, והיא שומרת את שתי התכונות בצידי הקו ההוא הנגדיים. הגנת גיליון עבודה וחוברת עבודה הן הגבלות עריכה הנתמכות בגיבוב ישן חלש בכוונה. SaveAsEncrypted מפיק חבילה מוצפנת ב-AES ששום דבר פחות מהסיסמה לא יפתח. הסעיפים שלהלן מכסים מה אותה קריאה כותבת, את אי-הסימטריה שעליכם לתכנן סביבה (HotXLS כותבת קבצים מוצפנים אך אינה יכולה לקרוא אותם בחזרה), וכיצד מסלול ה-XLS הישן שונה

מדוע הגנת גיליון אינה הצפנה

שיטות ה-Protect על גיליונות ו-ProtectWorkbook על חוברת העבודה שומרות גיבוב 4-ספרות-הקס של הסיסמה. זהו האלגוריתם הישן ש-OOXML ו-BIFF שניהם ירשו מ-Excel של שנות ה-90, ותיעוד הפורמט לעולם אינו טוען שהוא עושה יותר מלעצור עריכות מקריות. החבילה נשארת zip קריא רגיל: נתוני תאים, נוסחאות ומחרוזות משותפות כולם ב-XML של טקסט גלוי. ברירת המחדל מחמירה זאת, לא משפרת. כל תא מתחיל ב-Locked=True, ולכן קריאה ל-Protect מבלי לבטל תחילה נעילת טווח קלט מקפיאה את כל הגיליון מפני עריכה בעוד היא משאירה כל ערך גלוי לעין

שום דבר מכך אינו הופך את ההגנה לחסרת-תועלת. הכוונת משתמשים לטווחים הניתנים לעריכה וייצוב פריסה להדפסה הם עבודות אמיתיות, המכוסות במאמר שלנו על הגנת גיליון עבודה והגדרת עמוד. אך אלה עבודות שמישות. ברגע שהדרישה היא סודיות, ה-API היחיד שעונה עליה הוא SaveAsEncrypted

מה SaveAsEncrypted באמת כותב

המימוש עוקב אחר הצפנת תקן ECMA-376, המפורטת ב-[MS-OFFCRYPTO] סעיף 2.3.4. הסיסמה רצה דרך 50,000 איטרציות של SHA-1 כדי לגזור מפתח AES-128. בלוק מאמת, מוצפן ב-AES-128 במצב ECB, מאפשר לצרכן לאשר את הסיסמה לפני שהוא מפענח דבר, וכל חבילת חוברת העבודה מוצפנת אז ב-AES-128 במצב CBC. מה שנוחת על הדיסק אינו zip כלל. זהו קובץ מורכב OLE המחזיק זרמי EncryptionInfo, EncryptedPackage ו-DataSpaces, ללא ספריית xl/ שכלי ארכיון יוכל לרשום, ולכן מבחן שינוי-השם כעת אינו מעלה דבר קריא. Excel 2007 ואילך פותח אותו עם הסיסמה לבדה, ו-LibreOffice הנוכחי קורא הצפנת תקן גם כן

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  rc: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Payroll');
    Sheet.Cells[1, 1].Value := 'Employee';
    Sheet.Cells[1, 2].Value := 'Net pay';
    Sheet.Cells[2, 1].Value := 'A. Garcia';
    Sheet.Cells[2, 2].Value := 4815.16;

    rc := Book.SaveAsEncrypted('payroll-2026-06.xlsx', PasswordFromVault);
    if rc <> 1 then
      raise Exception.CreateFmt('Encrypted save failed (rc=%d)', [rc]);
  finally
    Book.Free;
  end;
end;

התייחסו למשתנה הסיסמה באותה זהירות כמו למחרוזת חיבור. שלפו אותו מכספת או משירות סוד-מחולל ברגע האחרון, לעולם אל תרשמו אותו ביומן, ולעולם אל תכתבו אותו לתוך חוברת העבודה עצמה. בדיקת קוד-ההחזרה אינה טקס אופציונלי. שמירת הצפנה הנכשלת באמצע חייבת לבטל את המסירה, מפני שהתחליף היחיד שהקוד הקורא יכול להציע הוא עותק לא מוצפן, ואותו עותק הוא בדיוק האירוע שתכונה זו קיימת כדי למנוע

יש גם מבחן קבלה הניתן לבדיקה במכונה שעולה כמעט כלום: קראו ל-CanReadEncrypted על הקובץ שזה עתה כתבתם. הוא מחזיר true רק כשהפלט באמת מכל הצפנה, ולכן אישורו אחרי כל שמירה מוצפנת תופס את הרגרסיה החשובה ביותר, מסלול קוד שנפל בשקט בחזרה ל-SaveAs פשוט, ברגע שזה קורה ולא שבועות מאוחר יותר בתיבת הדואר של לקוח. המילה האחרונה עדיין שייכת לפתיחה ידנית ב-Excel עם הסיסמה האמיתית במהלך בדיקות שחרור

לכתיבה-בלבד בעיצוב: טיפול ב-EXlsxEncryptionNotImplemented

הנה אי-הסימטריה שצריכה לעצב את ארכיטקטורת הצינור שלכם: HotXLS מצפינה בשמירה אך אינה מפענחת בפתיחה. OpenEncrypted מעלה EXlsxEncryptionNotImplemented כשהיא מכוונת לחבילה מוצפנת ממשית; בחוברת עבודה פשוטה היא פשוט נופלת ל-Open רגיל. בדיקת הלוויין CanReadEncrypted מזהה את מכל הצפנת ה-OLE בזול, ולכן קוד קליטה יכול לנתב קבצים כאלה מבלי להפעיל את החריגה:

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.CanReadEncrypted(FileName) then
    begin
      // Encrypted container: HotXLS cannot decrypt it.
      Writeln(FileName + ': needs manual decryption in Excel first');
      Exit;
    end;
    try
      Book.OpenEncrypted(FileName, '');   // plain files fall through to Open
      Writeln(FileName + ': opened, ' + IntToStr(Book.Sheets.Count) + ' sheet(s)');
    except
      on EXlsxEncryptionNotImplemented do
        Writeln(FileName + ': encrypted - routed to manual queue');
    end;
  finally
    Book.Free;
  end;
end;

לאי-הסימטריה הזו יש קריאה ארכיטקטונית ברורה אחת: הצפינו בקצה המסירה, אחרון. שמרו את אב-הטיפוס בטקסט גלוי בתוך גבול האמון שלכם, במסד נתונים, במאגר מסמכים, או בשיתוף מבוקר-גישה, והפיקו את העותק המוצפן כצעד האחרון לפני שהקובץ עוזב את המערכת. צינור המארכב רק את הפלט המוצפן נעל את עצמו מחוץ לנתונים שלו, מפני ששום שלב מאוחר יותר של אותה מערכת אינו יכול לפתוח מחדש את הקבצים האלה. כשתהליך HotXLS במורד הזרם זקוק לחוברת העבודה שוב, מסרו לו את אב-הטיפוס בטקסט גלוי, לעולם לא את ארטיפקט המסירה

הצפנת תקן AES-128 וקו הציות של AES-256

הצפנת קובצי Office מגיעה בשני דורות. הצפנת תקן, זו ש-HotXLS כותבת, משתמשת ב-AES-128 עם גזירת מפתח SHA-1. הצפנת Agile הגיעה מאוחר יותר ועוברת ל-AES-256 עם SHA-512 ומכל מפתח שונה, מתואר-XML. שניהם נפתחים בשקיפות ב-Excel, ו-AES-128 עדיין תקין חישובית להגנת קובץ במעבר ללקוח

ההבדל מפסיק להיות אקדמי ביום שבו שאלון אבטחה מבקש "הצפנת AES-256 של קבצים במנוחה". הצפנת תקן אינה עומדת בקו ההוא, ולא משנה כמה הסיסמה חזקה, ושום פרמטר של SaveAsEncrypted אינו משנה את האלגוריתם שהוא פולט. אז ציינו את הפרופיל במדויק בתיעוד האבטחה שלכם: AES-128, הצפנת תקן ECMA-376, גזירת מפתח SHA-1 ב-50,000 איטרציות. טענה ששורדת סקירה שווה יותר מאופטימית שקורסת תחת ביקורת

מסלול ה-XLS הישן: RC4 החוצה, RC4 ו-XOR בחזרה פנימה

למעטפת ה-BIFF יש צורה הפוכה. ההצפנה שלה ישנה וחלשה יותר, אך הסבב מלא: מה שהיא כותבת, היא יכולה גם לקרוא בחזרה. הגדרת EncryptionPassword לפני SaveAs מפיקה .xls מוצפן ב-RC4 דרך מנגנון ה-FilePass של BIFF, ו-Open עם פרמטר סיסמה קורא את כל שלוש הסכמות הישנות, RC4, RC4 CryptoAPI, וערפול ה-XOR העתיק:

var
  Writer, Reader: IXLSWorkbook;   // interface refs: no manual Free
begin
  Writer := TXLSWorkbook.Create;
  Writer.Sheets.Add.Cells.Item[1, 1].Value := 'Confidential';
  Writer.EncryptionPassword := 'S3cret!';
  Writer.SaveAs('confidential.xls');

  Reader := TXLSWorkbook.Create;
  if Reader.Open('confidential.xls', 'S3cret!') > 0 then
    Writeln(Reader.Sheets[1].Cells.Item[1, 1].Value);  // Entries are 1-based
end;

RC4 הוא צופן מיושן ולעולם אסור לו להגן על נתונים שחשובים היום; ערכו הנותר היחיד הוא יכולת פעולה הדדית עם מערכות שעדיין מחליפות .xls. צד הקריאה, עם זאת, מצדיק את קיומו בעבודת הגירה. קובץ ישן מוגן-סיסמה נפתח עם Open(FileName, Password), מגשר אל מודל ה-OOXML, ומאובטח מחדש דרך מסלול ה-AES, שדרוג חד-כיווני שרץ ללא Excel בשום מקום בלולאה. עבור מסירות מוצפנות בנפח גבוה, הערות קצב-ההעברה בצד-השמירה במאמר שלנו על כתיבות הזרמה לעבודות אצווה בשרת חלות על שלב בניית-התוכן המתרחש לפני ההצפנה

הצפנה והגנה אינן יריבות

נקודה נוספת ששווה ליישב, מפני שהיא עולה ברגע שמישהו קורא את האזהרה בראש הדף הזה כ"הגנה היא חסרת-ערך". היא אינה. הצפנה והגנה עונות על שאלות שונות, והן נערמות בנקיות. הצפנה מחליטה מי יכול לפתוח את הקובץ; הגנה מחליטה מה קורא שכבר בפנים רשאי לשנות. מסירת שכר עובד יכולה באופן סביר לעשות את שניהם: הצפינו את החבילה כך שרק מחזיק הסיסמה רואה אותה, אז נעלו את תאי הנוסחה כך שהנמען יוכל לסנן ולמיין אך לא לשכתב בשקט את החישובים. הטעות לעולם אינה הוספת הגנה. הטעות היא לתת לנוכחותה לעמוד במקום הצפנה כשהדרישה הייתה סודיות

לצד המשמורת אין רשת ביטחון, וזה בעיצוב. גזירת המפתח של 50,000 איטרציות קיימת כדי להפוך ניחוש ליקר, ושום דבר בתוך הקובץ אינו מפקיד את הסוד בנאמנות. סיסמה אבודה היא נתונים אבודים. חוללו, מסרו ואחסנו את הסיסמאות האלה באותה משמעת שאתם מחילים על אישורי מסד נתונים, וההצפנה תעמוד בחלקה

הצפנת קובץ אמיתית היא קריאה אחת ב-HotXLS. המשמעת חיה בכל מה שמסביב לקריאה: משמורת סיסמה, גבול הכתיבה-בלבד ששומר על HotXLS מלפתוח מחדש את הפלט שלה, וטענת אלגוריתם שתוכלו להגן עליה בביקורת. SaveAsEncrypted והסבב הישן נשלחים עם HotXLS Component, הרצים באופן מקורי בתהליכי Delphi ו-C++Builder ללא אוטומציית Excel בשום מקום במסלול