מאמר טכני

ערפול ה-XOR של XLS ב-HotXLS: גזירת המפתח ו-XorRor

HotXLS כותב חוברות עבודה מעורפלות-XOR של Excel 5.0/95 (BIFF5) ש-Excel 16 פותח רק כששלושה פרטים תואמים את [MS-OFFCRYPTO] בדיוק: מפתח ה-FILEPASS חייב להיות CreateXorKey_Method1(password), מערך ה-XOR בן 16 הבתים חייב להיבנות עם XorRor (הזזה ימינה בביט אחד), וכל בית חייב להשתמש ב-XorArrayIndex = (stream offset + record length) mod 16. HotXLS השיג את האינדקס נכון ב-v2.384.47 ואת המפתח וההזזה נכונים ב-v2.384.54. לפני כן, כל קובץ BIFF5 מוגן סיסמה שהפיק נפתח חלק ב-HotXLS ונכשל ב-Excel

המשפט האחרון הזה הוא הסיפור כולו במיניאטורה. קורא וכותב שחולקים את אותה טעות מסכימים אחד עם השני בצורה מושלמת, ולכן בדיקות round-trip נשארות ירוקות בזמן שהצרכן היחיד שחשוב אומר לא. Excel 16 אמר לא פעמיים, עם שתי הודעות שונות, וכל הודעה הצביעה על שכבה אחרת של הסכימה. המאמר הזה עובר על השכבות האלה בסדר שבו Excel בודק אותן, עם פירוט ברמת הבתים שישמש אתכם בין אם אתם קוראים ל-HotXLS ובין אם כותבים קורא BIFF משלכם

מה אובפוסקציית ה-XOR של BIFF באמת שומרת בקובץ?

ערפול ה-XOR של BIFF שומרת בקובץ רק שתי מילים בנות 16 ביט, וכל השאר מחושב מחדש מהסיסמה. רשומת ה-FILEPASS ($002F) יושבת מיד אחרי ה-BOF של workbook globals, ובקובץ BIFF5 הגוף שלה הוא בדיוק 4 בתים: מפתח ה-XOR ואחריו מאמת הסיסמה (verifier). אין salt, אין מזהה אלגוריתם ואין בלוב verifier מוצפן מהסוג שסכמות ה-RC4 וה-AES נושאות

משתי המילים האלה קורא בונה שלושה דברים:

  • את ה-verifier, hash בן 16 ביט של בייטי הסיסמה מ-XOR עם $CE4B. ההשוואה מול המילה השמורה היא בדיקת הסיסמה, והיחידה
  • את מפתח ה-XOR, ערך בן 16 ביט מ-CreateXorKey_Method1 ב-[MS-OFFCRYPTO] §2.3.7.2, שמונע על ידי שתי טבלאות קבועות (InitialCode, 15 מילים, ו-XorMatrix, 105 מילים)
  • את מערך ה-XOR, 16 בתים שנבנים מבייטי הסיסמה בתוספת pad קבוע בן 16 בתים, כל אחד מ-XOR עם בייט המפתח הנמוך (מיקומים זוגיים) או בייט המפתח הגבוה (מיקומים אי-זוגיים), ואז הזזה ימינה בביט אחד

כותרות הרשומות נשארות בטקסט רגיל, וכך גם כמה רשומות שלמות שהסכימה פוטרת, בהן BOF, FILEPASS ו-INTERFACEHDR. כל גוף רשומה אחר עובר טרנספורמציה בייט-בייט: הזזה שמאלה ב-5 ביטים ואז XOR עם כניסה אחת של המערך בן 16 הבתים. הפענוח, ש-[MS-OFFCRYPTO] §2.3.7.3 מאיית בתור DecryptData_Method1, הוא תמונת הראי: קודם XOR ואז הזזה ימינה ב-5

ב-HotXLS לא נוגעים באף אחד מאלה ישירות. מגדירים סיסמה, בוחרים פורמט, ו-SaveAs פולט את ה-FILEPASS ומטרנספורם את ה-stream:

uses
  SysUtils, lxHandle;

procedure SaveLegacyProtectedBook(const FileName: string);
var
  Wb: IXLSWorkbook;
begin
  Wb := TXLSWorkbook.Create;
  Wb.Sheets.Add.Name := 'Ledger';
  Wb.Sheets[1].Range['A1', 'A1'].Value := 'Account';
  Wb.Sheets[1].Range['B1', 'B1'].Value := 1250.75;

  // BIFF5 תומך רק בערפול XOR; גם xletAuto היה בוחר בו
  Wb.EncryptionType := xletXor;
  // משאירים ASCII ולכל היותר 15 תווים (ראו להלן)
  Wb.EncryptionPassword := 'secret';

  if Wb.SaveAs(FileName, xlExcel5) <> 1 then
    raise Exception.Create('BIFF5 save failed');
end;

מדוע Excel אומר שהסיסמה שגויה כשה-verifier תואם?

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

כותב ה-HotXLS לפני v2.384.54 מילא את מילת המפתח בשני בייטים אקראיים. על הנייר זה נראה לא מזיק, כי ה-verifier הוא בדיקת הסיסמה המתועדת והמערך נבנה מהמפתח שהקובץ מצהיר, מה שלא יהיה. HotXLS עצמו קרא את הקבצים האלה בלי בעיה, כי הקורא שלו לקח את המפתח מה-FILEPASS כנתון. Excel 16, שקיבל את אותו קובץ ואת הסיסמה הנכונה, ענה שהסיסמה אינה נכונה. מאז v2.384.54 המפתח נגזר, ולכן ה-FILEPASS עבור הסיסמה secret מחזיק תמיד מפתח $014D ו-verifier $DAA7, ערכים שאומתו בהצלבה מול מימוש עצמאי של המפרט

דיאגרמת HotXLS של רשומת ה-FILEPASS של BIFF5 XOR ש-Excel 16 בודק בפתיחה: הגוף הרגיל בן ארבעת הבתים מחזיק את מפתח ה-XOR ואת מאמת הסיסמה, Excel גוזר את המפתח מהסיסמה שהוקלדה עם CreateXorKey_Method1 ודוחה מפתח אקראי עם שגיאת סיסמה גם כשה-verifier תואם; HotXLS שומר את המפתח הנגזר 014D עבור secret
גוף ה-FILEPASS הוא רק שתי מילים, אבל Excel גוזר את המפתח מחדש מהסיסמה שלכם ומשווה; מפתח שמולא אקראית נכשל בבדיקה גם עם verifier נכון, וזו הסיבה ש-HotXLS גוזר אותו מאז v2.384.54

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

// שרטוט עיקרון של [MS-OFFCRYPTO] 2.3.7.2 CreateXorKey_Method1
// ו-CreateXorArray_Method1 (המחשה בלבד, לא API של HotXLS)
type
  TXorArray = array [0..15] of Byte;

function DemoCreateXorKey(const Password: AnsiString): Word;
const
  InitialCode: array [0..14] of Word = ($E1F0, $1D0F, $CC9C, $84C0, $110C,
    $0E10, $F1CE, $313E, $1872, $E139, $D40F, $84F9, $280C, $A96A, $4EC3);
  XorMatrix: array [0..104] of Word = (
    $AEFC, $4DD9, $9BB2, $2745, $4E8A, $9D14, $2A09,
    $7B61, $F6C2, $FDA5, $EB6B, $C6F7, $9DCF, $2BBF,
    $4563, $8AC6, $05AD, $0B5A, $16B4, $2D68, $5AD0,
    $0375, $06EA, $0DD4, $1BA8, $3750, $6EA0, $DD40,
    $D849, $A0B3, $5147, $A28E, $553D, $AA7A, $44D5,
    $6F45, $DE8A, $AD35, $4A4B, $9496, $390D, $721A,
    $EB23, $C667, $9CEF, $29FF, $53FE, $A7FC, $5FD9,
    $47D3, $8FA6, $0F6D, $1EDA, $3DB4, $7B68, $F6D0,
    $B861, $60E3, $C1C6, $93AD, $377B, $6EF6, $DDEC,
    $45A0, $8B40, $06A1, $0D42, $1A84, $3508, $6A10,
    $AA51, $4483, $8906, $022D, $045A, $08B4, $1168,
    $76B4, $ED68, $CAF1, $85C3, $1BA7, $374E, $6E9C,
    $3730, $6E60, $DCC0, $A9A1, $4363, $86C6, $1DAD,
    $3331, $6662, $CCC4, $89A9, $0373, $06E6, $0DCC,
    $1021, $2042, $4084, $8108, $1231, $2462, $48C4);
var
  Len, I, Bit, Element: Integer;
  Ch: Byte;
begin
  Result := 0;
  Len := Length(Password);
  if Len > 15 then
    Len := 15;                       // המפתח רואה רק 15 בתים
  if Len = 0 then
    Exit;
  Result := InitialCode[Len - 1];
  Element := $68;                    // כניסת ה-XorMatrix האחרונה
  for I := Len downto 1 do
  begin
    Ch := Ord(Password[I]);
    for Bit := 1 to 7 do
    begin
      if (Ch and $40) <> 0 then
        Result := Result xor XorMatrix[Element];
      Ch := Byte(Ch shl 1);
      Dec(Element);
    end;
  end;
end;

function XorRor(B, KeyByte: Byte): Byte;
begin
  B := B xor KeyByte;
  Result := Byte((B shr 1) or (B shl 7));   // הזזה ימינה בביט אחד
end;

procedure DemoCreateXorArray(const Password: AnsiString; out Arr: TXorArray);
const
  PadArray: TXorArray = ($BB, $FF, $FF, $BA, $FF, $FF, $B9, $80,
    $00, $BE, $0F, $00, $BF, $0F, $00, $00);
var
  Key: Word;
  Len, I: Integer;
begin
  Key := DemoCreateXorKey(Password);
  Len := Length(Password);
  if Len > 16 then
    Len := 16;
  for I := 0 to Len - 1 do
    Arr[I] := Ord(Password[I + 1]);
  for I := Len to 15 do
    Arr[I] := PadArray[I - Len];
  for I := 0 to 15 do
    if Odd(I) then
      Arr[I] := XorRor(Arr[I], Byte(Key shr 8))
    else
      Arr[I] := XorRor(Arr[I], Byte(Key and $FF));
end;

עבור secret זה מפיק את המערך 1F 32 17 B9 14 BA 7B 7F 59 DD 59 7F 7A C0 A6 DF, שהוא fixture נוח אם בודקים קורא משלכם

דיאגרמת HotXLS של בניית מערך ה-XOR עבור ערפול BIFF5: שישה עשר בתים שנזרעו מהסיסמה ומ-pad קבוע עוברים XOR עם בייט המפתח הנמוך במיקומים זוגיים ועם בייט המפתח הגבוה במיקומים אי-זוגיים, ואז הזזה ימינה בביט אחד עם XorRor, ומפיקים את ה-fixture 1F 32 17 B9 עבור הסיסמה secret
בייטי המערך מגיעים מהסיסמה, מה-pad ומשני בייטי המפתח, עם הזזה אחת בסוף; הזזה שמאלה 2 עבדה רק כשטרנספורמציית הבייט רצה בסדר ההפוך, ו-Excel הולך אחרי סדר המפרט

מדוע מפתח נכון עדיין מפיק קובץ פגום?

מפתח נכון עדיין מפיק קובץ פגום כשאת מערך ה-XOR מזיזים לכיוון הלא נכון: [MS-OFFCRYPTO] מגדיר את שלב המערך בתור XorRor, הזזה ימינה בביט אחד, ומערך שמוזז שמאלה בשניים מפענח כל גוף רשומה לרעש. תיקון המפתח דילג את Excel 16 מעבר לשאילתת הסיסמה וישר אל שגיאה אחרת, דיווח שלקובץ יש בעיה ושאי אפשר לפותח אותו

הקוד הישן של HotXLS הזיז כל בייט מערך שמאלה ב-2 ביטים, צורה שמסתובבת בכמה מימושי BIFF. מכיוון ש-HotXLS השתמש באותה הזזה בשני הצדדים, הקורא שלו עצמו מעולם לא שם לב. Excel 16 כבר לא יכול לשמור קבצי Excel 5.0/95, והוא לא מציע XOR בשמירת BIFF8, ולכן לא הייתה דגימת Excel טבעית להשוות מולה. הראיות היו צריכות להגיע מהכיוון השני: כותבים stream אחד של BIFF5 בטקסט רגיל, מקודדים אותו מחדש בשמונה דרכים, ונותנים ל-Excel 16 לפתוח כל וריאציה. שמונה הווריאציות הצליבו שלוש בחירות בלתי תלויות:

בחירהאפשרות Aאפשרות B
הזזת המערךXorRor (הזזה ימינה 1)הזזה שמאלה 2
אינדקס המערך(offset + record length) mod 16offset mod 16
סדר טרנספורמציית הבייטהזזה שמאלה 5 ואז XORXOR ואז הזזה שמאלה 5

Excel 16 פתח בדיוק שתיים מתוך השמונה: XorRor עם קודם-הזזה-ואז-XOR ועם אינדקס אורך-הרשומה, ועוד וריאציה אחת שרק נראית שונה. הזזה-שמאלה-2 עם XOR-ואז-הזזה ועם אותו אינדקס היא אותה פונקציה בתחפושת. הזזה מתפלגת מעל XOR, ולכן rol5(p xor rol2(b)) שווה ל-rol5(p) xor rol7(b), ועל ערך בן 8 ביטים הזזה שמאלה ב-7 היא הזזה ימינה ב-1. בקיצור, rol5 ∘ rol2 = ror1, וזו הסיבה שהמערך עם הזזה-שמאלה-2 נראה סביר בבידוד: הוא נכון רק יחד עם סדר הטרנספורמציה ההפוך. כשמזווגים אותו עם סדר המפרט, הוא משחית כל בייט מטרונספורם

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

איך מחשבים את ה-XorArrayIndex עבור כל בית?

ה-XorArrayIndex עבור בית הוא ה-offset שלו ב-workbook stream בתוספת האורך של כל נתוני הרשומה שאליה הוא שייך, mod 16. האינדקס לכן מתאפס מחדש לערך התלוי ברשומה בכל רשומה ומתקדם באחד לכל בית בתוכה. הפסאודוקוד של המפרט קורא לקלטים FileOffset ו-Data.Length, שקל לפרש בטעות כ-offset של תחילת הרשומה בלבד, והקריאה המוטעית הזאת היא בדיוק מה ש-HotXLS שילק עד v2.384.47

שלושה פרטים מכריעים אם האינדקסים שלכם מתיישרים עם Excel:

  • כותרת הרשומה בת 4 הבתים מעולם לא מטרונספורמת, אבל היא עדיין תופסת מיקומי stream, ולכן בית הגוף הראשון של רשומה יושב ב-header offset + 4
  • האיבר של האורך הוא אורך נתוני הרשומה המלא, לא מספר הבתים שבאמת הטרונספורמו
  • BOUNDSHEET חלקו רגיל: ארבעת הבתים הראשונים שלו, lbPlyPos, ה-offset ב-stream של BOF הגיליון, נשארים קריאים כדי שפרסר יוכל לאתר גיליונות. ארבעת הבתים האלה מדולגים על ידי הטרנספורמציה אבל עדיין נספרים גם ל-offset וגם לאורך הרשומה
דיאגרמת HotXLS של כלל ה-XorArrayIndex עבור ערפול BIFF5 XOR: כל ביית גוף משתמש ב-offset ה-stream בתוספת אורך נתוני הרשומה המלא modulo 16, הכותרת הרגילה בת ארבעת הבתים והקידומת הרגילה lbPlyPos של BOUNDSHEET עדיין נספרים ל-offset, והתעלמות מאיבר אורך הרשומה הייתה הפגם ש-HotXLS תיקן ב-v2.384.47
אינדקס המערך מתאפס פעם ברשומה, לא פעם ב-stream: הכותרת וכל קידומת רגילה תופסים מיקומים, אורך נתוני הרשומה מזין את ה-modulo, ושני חצאי ה-HotXLS הישן הסכימו על הנוסחה השגויה

ביחד, הטרנספורמציה לכל רשומה היא כמה שורות. שוב, זה שרטוט של הכלל, לא משהו שצריך לקרוא לו:

function Rol8(B: Byte; N: Integer): Byte;
begin
  Result := Byte((B shl N) or (B shr (8 - N)));
end;

// מערפל גוף רשומה אחד במקום. BodyPos הוא ה-offset ב-stream של
// Body[0], כלומר ה-offset של כותרת הרשומה + 4. PlainPrefix הוא 4 עבור
// BOUNDSHEET, האורך המלא עבור BOF / FILEPASS, ו-0 לרוב הרשומות
procedure DemoObfuscateRecord(var Body: array of Byte; RecordLength: Word;
  PlainPrefix: Integer; BodyPos: LongWord; const Arr: TXorArray);
var
  I: Integer;
begin
  for I := PlainPrefix to RecordLength - 1 do
    Body[I] := Rol8(Body[I], 5) xor
      Arr[(BodyPos + LongWord(I) + RecordLength) mod 16];
end;

// הקריאה היא תמונת הראי: B := Body[I] xor Arr[...];
// ואז הזזה ימינה ב-5, כלומר Rol8(B, 3)

לפני v2.384.47 הקורא של HotXLS חישב את האינדקס ממיקום ה-stream בלבד והכותב השתמש ב-offset הבייטים הרגיל. שניהם התעלמו מאורך הרשומה, ולכן שוב שני החצאים הסכימו ביניהם ועם אף אחד אחר. מפענח שנכתב באופן עצמאי קרא נכון את הפלט של v2.384.47 ואת הפלט הישן יותר קרא כשטויות, ובדיקת שמונה-הווריאציות ב-Excel 16 אישרה מאוחר יותר את הכלל מול היעד האמיתי

מה קורה עם קובצי XOR שנכתבו בגרסאות HotXLS ישנות?

HotXLS ממשיך לקרוא את קובצי ה-XOR שלו שלפני v2.384.54 על ידי בדיקת מפתח ה-FILEPASS: כשהמפתח השמור שווה למפתח הנגזר מהסיסמה, הקורא בונה את מערך ה-XorRor של המפרט, וכשהוא שונה, הקורא מתייחס לקובץ בתור קובץ HotXLS ישן ובונה מחדש את מערך ההזזה-שמאלה-2. קבצים שנכתבו על ידי Excel תמיד נושאים את המפתח הנגזר, ולכן הם תמיד לוקחים את נתיב המפרט

הבדיקה היא heuristic עם שיעור כשל מדויק. קובץ ישן שהמפתח האקראי שלו במקרה היה שווה למפתח הנגזר ייקרא עם המערך השגוי, והסיכוי לזה הוא 1 מתוך 65,536. ה-fallback מכסה רק את הזזת המערך; כלל האינדקס לא מוחלף, ולכן הקבצים שהוא מציל הם אלה שנכתבו בין v2.384.47 ל-v2.384.53. אם עדיין מחזיקים קובצי BIFF5 XOR מהחלון הזה, פותחים אותם עם ה-HotXLS הנוכחי ושומרים אותם שוב כדי לקבל קובץ ש-Excel מקבל

שני פרטי סיסמה חלים על כל קובץ, ישן או חדש:

  • אורך. CreateXorKey_Method1 קורא רק את 15 בייטי הסיסמה הראשונים, וזהו הגבול של המפרט. HotXLS מיישם את התקרה הזאת על המפתח ומשאיר את ה-verifier והמערך על הכללים הרגילים שלהם באורך מלא וב-16 בתים, בעקביות בשני הצדדים. Excel עצמו מסרב לסיסמאות ארוכות מ-15 תווים עבור הפורמט הזה, ולכן מתייחסים ל-15 בתור המקסימום האמיתי
  • מערך תווים. HotXLS ממיר את הסיסמה לבייטים דרך ה-ANSI code page המערכתי. המפרט מתאר לקיחת הבייט הנמוך של כל תו UTF-16, מה שתואם עבור ASCII. בלי דגימות Excel מוגנות בסיסמאות שאינן ASCII אין אמת מידה לשאר, ולכן דבקים בסיסמאות ASCII עבור קובצי XOR

בצד הקריאה, TXLSWorkbook.OnPassword נותן לכם לשאול את המשתמש לסיסמה כש-Open פוגש רשומת FILEPASS. האירוע הוא TXLSPasswordEvent עם var PassWord: WideString ו-var Retry: Boolean; מגדירים Retry ל-True כדי לנסות שוב, עד שלושה ניסיונות:

procedure TImportForm.WorkbookPassword(Sender: TObject;
  var PassWord: WideString; var Retry: Boolean);
var
  S: string;
begin
  S := '';
  Retry := InputQuery('Protected workbook', 'Password:', S);
  PassWord := S;
end;

procedure TImportForm.ImportLegacyFile(const FileName: string);
var
  Wb: IXLSWorkbook;
  Rc: Integer;
begin
  Wb := TXLSWorkbook.Create;
  Wb.OnPassword := WorkbookPassword;
  Rc := Wb.Open(FileName);
  // -1003: נדרשת סיסמה ולא סופקה אף אחת; -1005: סיסמה שגויה
  if Rc <> 1 then
    raise Exception.CreateFmt('Cannot open %s (code %d)', [FileName, Rc]);
  ShowMessage(VarToStr(Wb.Sheets[1].Range['A1', 'A1'].Value));
end;

אם כבר יודעים את הסיסמה, Open(FileName, APassWord) מדלג על האירוע לגמרי

האם ערפול XOR מאובטחת מספיק למשהו?

ערפול ה-XOR של BIFF אינה הצפנה ואינה מגינה על שום דבר מפני קורא נחוש. בדיקת הסיסמה היא verifier בן 16 ביט, המפתח בן 16 ביט, והמערך בן 16 הבתים חוזר על עצמו לכל אורך ה-stream, כך שתוכן רשומות BIFF צפוי חושף בייטי מערך בלי שום סיסמה בכלל. HotXLS כותב XOR רק כי לקובצי Excel 5.0/95 אין אפשרות אחרת, והסיבה להפיק קבצים כאלה היום היא צרכן מורשת שלא יודע לקרוא כלום חדש יותר

המנוע הקלאסי בוחר את הסכימה דרך TXLSWorkbook.EncryptionType, והשילוב עם פורמט השמירה נבדק בקפדנות:

  • xletAuto (ברירת המחדל) כותב RC4 CryptoAPI עבור xlExcel97 ו-XOR עבור xlExcel5, בהתאמה למה ש-Excel עצמו כתב עבור כל פורמט
  • xletXor תקף ל-BIFF5 בלבד; עם xlExcel97 השמירה מעלה חריגה במקום ליפול בשקט
  • xletRC4 ו-xletRC4CryptoAPI הן ל-BIFF8 בלבד, ובקשה אחריהן בשמירת BIFF5 מעלה חריגה גם כן

גם ה-RC4 מיושן, והפרטים של ההתממשקות שלו מכוסים ב-מדוע Excel דוחה חוברת עבודה מוצפנת עם סיסמה נכונה. אם הנמען יודע לקרוא XLSX, משתמשים במנוע ה-XLSX במקום: TXLSXWorkbook.SaveAsEncryptedAgile כותב Agile Encryption (hashing סיסמה SHA-512 עם spin count של 100,000 איטרציות ו-AES-256-CBC), הפורמט ש-Excel 2010 ומעלה כותבים כברירת מחדל, בזמן ש-SaveAsEncrypted כותב את ההצפנה הסטנדרטית הישנה יותר של AES-128. האיזונים בין השניים נמצאים ב-הצפנת קובצי XLSX עם AES ב-Delphi, וצד הקריאה מכוסה ב-קריאת קובצי Excel מוצפני Agile עם HotXLS

עזר זריז: ערפול XOR של BIFF ש-Excel 16 מקבל

  • FILEPASS ($002F) בא אחרי BOF ה-globals; ב-BIFF5 הגוף שלו 4 בתים: מפתח ואז verifier
  • מפתח = CreateXorKey_Method1(password) לפי [MS-OFFCRYPTO] §2.3.7.2, לעולם לא אקראי; עבור secret הוא $014D
  • מערך = בייטי סיסמה + pad, XOR עם בייט המפתח הנמוך במיקומים זוגיים ועם בייט המפתח הגבוה באי-זוגיים, ואז XorRor (הזזה ימינה 1)
  • הצפנת בייט: הזזה שמאלה 5 ואז XOR; פענוח: XOR ואז הזזה ימינה 5 (§2.3.7.3)
  • XorArrayIndex = (byte stream offset + record data length) mod 16; כותרות וקידומות רגילות נספרות ל-offset
  • BOUNDSHEET משאיר את ארבעת הבתים הראשונים שלו רגילים; BOF, FILEPASS ו-INTERFACEHDR נשארים רגילים לגמרי
  • סיסמאות: ASCII, לכל היותר 15 תווים
  • HotXLS: האינדקס תוקן ב-v2.384.47, המפתח וה-XorRor תוקנו ב-v2.384.54, קובצי XOR ישנים של HotXLS מזוהים לפי אי-התאמת מפתח
  • להגנה אמיתית משתמשים לפחות ב-BIFF8 RC4 CryptoAPI, או ב-XLSX Agile Encryption

HotXLS מטפל בהגנת סיסמה של BIFF5 ו-BIFF8, ב-XLSX Standard ו-Agile Encryption, ובקריאת callback לסיסמה בצד הקריאה, מספריית Delphi ו-C++Builder אחת, כשפרטי ה-interop שלמעלה מטופלים בשבילכם. ראו את רכיב הגיליונות HotXLS ל-Delphi למהדורות, לפלטפורמות ולהורדת ניסיון