HotXLS สามารถอ่านไฟล์ Excel ที่เข้ารหัสแบบ Agile ซึ่งเป็นการป้องกันด้วยรหัสผ่านที่ Excel 2010 และเวอร์ชันถัดมาใช้งานเป็นค่าเริ่มต้น ผ่านการเรียกใช้ฟังก์ชันเพียงครั้งเดียว: TXLSXWorkbook.OpenEncrypted คอมโพเนนต์จะทำหน้าที่วิเคราะห์คำอธิบายการเข้ารหัส XML, สร้างคีย์จากรหัสผ่านด้วยห่วงโซ่การแฮชแบบวนซ้ำ (spin-count) ด้วย SHA-512, ตรวจสอบรหัสผ่านเทียบกับตัวตรวจสอบที่เข้ารหัสไว้ และถอดรหัสแพ็กเกจข้อมูลเป็นเซกเมนต์ AES-CBC ขนาด 4096 ไบต์ โดยไม่จำเป็นต้องติดตั้ง Excel, ไม่ใช้ COM และไม่ต้องพึ่งพา DLLs การเข้ารหัสภายนอกใดๆ
บทความนี้จะเน้นไปที่ส่วนของการอ่านการเข้ารหัสแบบ Agile โดยเฉพาะ ส่วนปัญหาใกล้เคียงกันอีกสองประการได้แยกอธิบายไว้ในบทความอื่น: การทำงานร่วมกับโครงสร้างแบบดั้งเดิมอย่าง RC4 และ XOR ภายในไฟล์ .xls แบบ BIFF รุ่นเก่าจะอธิบายไว้ในบทความการทำงานร่วมกันของ ECB และ RC4 และการสร้างสมุดงานที่ป้องกันด้วยรหัสผ่านด้วยการเข้ารหัสมาตรฐาน ECMA-376 จะอธิบายไว้ในบทความเอาต์พุต XLSX ที่ป้องกันด้วย AES ในที่นี้ ไฟล์มีอยู่แล้ว มีคนอื่นเข้ารหัสไว้ และงานของคุณคือการเปิดมันขึ้นมา
สถานการณ์ที่บังคับให้เกิดปัญหานี้เป็นเรื่องคุ้นเคยสำหรับผู้ที่ดูแลระบบประมวลผลเอกสาร บริการนำเข้าข้อมูลฝั่งเซิร์ฟเวอร์จะรับการอัปโหลดสมุดงาน โดยที่ไม่มีโปรแกรม Excel ติดตั้งอยู่ในเครื่องคอมพิวเตอร์นั้นและจะไม่มีวันมี และเช้าวันหนึ่งลูกค้าอัปโหลดไฟล์ .xlsx ธรรมดาที่ตัวอ่าน ZIP ปฏิเสธการทำงานเนื่องจากมันไม่ใช่ไฟล์ ZIP เลย ลูกค้าบันทึกไฟล์นั้นพร้อมรหัสผ่าน ตั้งแต่วินาทีนั้น ตัวโหลดไฟล์ของคุณต้องเข้าใจโครงสร้าง [MS-OFFCRYPTO] มิฉะนั้นมันจะส่งไฟล์กลับไปยังผู้ใช้ ซึ่งในมุมมองของพวกเขานั้นไม่ได้ทำอะไรที่แปลกไปจากเดิมเลย
การเข้ารหัสแบบ Agile ในไฟล์ Excel คืออะไร?
การเข้ารหัสแบบ Agile คือรูปแบบการป้องกันด้วยรหัสผ่านที่กำหนดไว้ใน [MS-OFFCRYPTO] §2.3.4.10 ถึง §2.3.4.15 และเป็นสิ่งที่ Excel 2010 ขึ้นไปเขียนบันทึกทุกครั้งเมื่อสมุดงานถูกบันทึกพร้อมรหัสผ่าน ไฟล์ที่เข้ารหัสแล้วจะไม่ใช่แพ็กเกจ ZIP อีกต่อไป แต่มันคือคอนเทนเนอร์ OLE Compound File Binary (CFB) ที่เก็บสองสตรีมข้อมูลย่อย: ได้แก่ EncryptionInfo ซึ่งอธิบายวิธีการเข้ารหัส และ EncryptedPackage ซึ่งเป็นไฟล์ ZIP .xlsx จริงที่ถูกเข้ารหัสเป็นบล็อบทึบแสง ค่าลายเซ็นของ CFB (D0 CF 11 E0 A1 B1 1A E1) เป็นรหัสเดียวกับที่ไฟล์ .xls แบบ BIFF รุ่นเก่ามีอยู่ ซึ่งเป็นเหตุผลว่าทำไมไฟล์ที่เปลี่ยนชื่อหรือเข้ารหัสจึงไม่สามารถจัดประเภทได้ด้วยนามสกุลไฟล์เพียงอย่างเดียว
สิ่งที่แยกการเข้ารหัสแบบ Agile ออกจากรุ่นก่อนคือ EncryptionInfo จะมีคำอธิบายในตัวเอง หลังจากส่วนนำหน้าเวอร์ชันขนาด 8 ไบต์ซึ่งเวอร์ชันหลักและย่อยเป็น 4 สตรีมข้อมูลจะเป็น XML ในรูปแบบ UTF-8 อิลีเมนต์ keyData จะประกาศอัลกอริทึมการเข้ารหัส (AES) โหมดการเชื่อมต่อบล็อก (ChainingModeCBC) การแฮช (SHA512) ความยาวคีย์ในหน่วยบิต ขนาดบล็อก และเกลือ (salt) แบบ Base64 ส่วนอิลีเมนต์รหัสผ่าน keyEncryptor จะเก็บเกลือของตัวเอง, ค่า spinCount และข้อมูลเพย์โหลด Base64 สามรายการ: ได้แก่ encryptedVerifierHashInput, encryptedVerifierHashValue และ encryptedKeyValue โปรแกรม Excel จะเขียนในรูปแบบ AES-256 ร่วมกับการวนซ้ำ 100,000 ครั้ง แต่คำอธิบายสามารถระบุในรูปแบบ AES-128 หรือ AES-192 ได้ และ HotXLS จะปฏิบัติตามค่า keyBits ที่ประกาศไว้โดยไม่ทึกทักว่าเป็น 256 เสมอไป
จุดเข้าใช้งานเดียวสำหรับสมุดงานข้อความธรรมดา, แบบมาตรฐาน และแบบ Agile
ฟังก์ชัน TXLSXWorkbook.OpenEncrypted จะรองรับสถานะทั้งสามแบบที่ผู้เรียกใช้สามารถพบเจอ ได้แก่ ZIP ข้อความธรรมดา, การเข้ารหัสแบบมาตรฐาน และการเข้ารหัสแบบ Agile ดังนั้นระบบรับอัปโหลดจึงไม่จำเป็นต้องจัดประเภทไฟล์ก่อนโหลด ขั้นแรกเมธอดจะตรวจสอบโครงสร้างไฟล์: หากไม่มีลายเซ็น CFB มันจะส่งต่อไปยังเส้นทาง Open ปกติและละเว้นรหัสผ่านไป หากไฟล์เป็นคอนเทนเนอร์ CFB มันจะทดลองเข้ารหัสมาตรฐาน ECMA-376 ก่อน และเมื่อลายเซ็นเวอร์ชัน EncryptionInfo เป็น Agile 4.4 มันจะส่งต่อไปยังกระบวนการ Agile โดยตรง ค่าส่งกลับคือ 1 เมื่อสำเร็จ ซึ่งเป็นข้อตกลงเดียวกับฟังก์ชัน Open
var
Wb: TXLSXWorkbook;
begin
Wb := TXLSXWorkbook.Create;
try
// ทำงานได้กับทั้งไฟล์ .xlsx ธรรมดา, แบบเข้ารหัสมาตรฐาน
// และแบบเข้ารหัส Agile
if Wb.OpenEncrypted('upload.xlsx', 'customer-password') = 1 then
Writeln(VarToWideStr(Wb.Sheets[1].Cells[1, 1].Value));
finally
Wb.Free;
end;
end;
ระบบการตรวจหาไฟล์ที่ไม่เข้ารหัสมีความสำคัญมากกว่าที่เห็นภายนอก ตัวนำเข้าข้อมูลแบบแบทช์ที่เรียกใช้ OpenEncrypted เสมอไม่จำเป็นต้องเขียนโค้ดแยกสาขาการทำงานที่จุดเรียกใช้: ไฟล์ที่ไม่เคยมีการป้องกันจะโหลดได้ปกติเหมือนเดิมทุกประการ และไฟล์ที่ส่งมาแบบเข้ารหัสจะถูกถอดรหัสในตำแหน่งเดิมแล้วส่งต่อให้กับตัวโหลด ZIP ทั่วไปเป็นสตรีมข้อมูลในหน่วยความจำ ซึ่งช่วยให้มีเส้นทางการทดสอบเพียงเส้นทางเดียวแทนที่จะเป็นสามเส้นทาง
รหัสผ่านกลายเป็นคีย์ AES ได้อย่างไร?
การเข้ารหัสแบบ Agile จะไม่นำรหัสผ่านไปใช้งานตรงๆ HotXLS จะเริ่มคำนวณแฮชแบบวนซ้ำก่อน: แฮชเริ่มต้นคือ SHA-512 ของเกลือรหัสผ่านเชื่อมต่อกับข้อมูลไบต์ UTF-16LE ของรหัสผ่าน จากนั้นนำแฮชนั้นมาแฮชซ้ำตามจำนวน spinCount ครั้ง โดยแต่ละรอบจะใส่ตัวนับการวนซ้ำแบบ little-endian ขนาด 32 บิตไว้หน้าแฮชก่อนหน้า ด้วยค่า spin count เริ่มต้นของ Excel ที่ 100,000 ครั้ง นั่นคือการเรียกใช้ SHA-512 ต่อเนื่องกันหนึ่งแสนครั้งต่อหนึ่งความพยายามป้อนรหัสผ่าน ซึ่งนี่คือประเด็นสำคัญ ค่า spin count ทำหน้าที่เป็นตัวหน่วงเวลาการเดารหัสผ่าน (brute-force throttle): มันจะทำให้ผู้เรียกใช้ที่ถูกต้องเสียเวลาเพียงไม่กี่มิลลิวินาทีในครั้งเดียว แต่จะทำให้ผู้บุกรุกที่ใช้พจนานุกรมเดารหัสผ่านต้องเสียเวลาไม่กี่มิลลิวินาทีนั้นในทุกๆ ครั้งที่เดาคำ
// [MS-OFFCRYPTO] iterated password hash:
// H(0) = SHA-512(salt + UTF-16LE(password))
// H(n) = SHA-512(LE32(n - 1) + H(n - 1)), repeated spinCount times
function AgilePasswordHash(const Password: WideString;
const Salt: TBytes; SpinCount: Integer): TBytes;
var
buf: TBytes;
i: Integer;
begin
Result := XlsSHA512(Concat(Salt, Utf16LEBytes(Password)));
SetLength(buf, 4 + 64);
for i := 0 to SpinCount - 1 do
begin
PutLE32(buf, 0, i); // ตัวนับการวนซ้ำ, little-endian
Move(Result[0], buf[4], 64); // ค่าแฮชรอบก่อนหน้า
Result := XlsSHA512(buf);
end;
end;
แฮชที่ผ่านการวนซ้ำแล้วก็ยังไม่สามารถนำมาใช้เป็นคีย์ได้ คีย์ที่แยกจากกันสามตัวจะถูกแยกออกมาโดยการแฮชแฮชนั้นอีกครั้งพร้อมแนบคีย์บล็อกขนาด 8 ไบต์ที่กำหนดค่าคงที่ไว้สำหรับแต่ละวัตถุประสงค์: ได้แก่ FE A7 D2 76 3B 4B 9E 79 สำหรับถอดรหัส verifier input, D7 AA 0F 6D 30 61 34 4E สำหรับ verifier hash และ 14 6E 0B E7 AB AC D0 D6 สำหรับการแกะคีย์ของตัวแพ็กเกจจริง ผลลัพธ์ SHA-512แต่ละตัวจะถูกตัดทอนให้เหลือตามความยาวคีย์ที่ประกาศไว้ และตามมาตรฐาน [MS-OFFCRYPTO] จะเติมด้วยไบต์ 0x36 ในกรณีเชิงทฤษฎีที่แฮชสั้นกว่าความยาวคีย์ กฎการเติมไบต์ 0x36 แบบเดียวกันนี้จะถูกใช้เมื่อเกลือรหัสผ่านถูกขยายขนาดให้เท่ากับขนาดบล็อกเพื่อใช้เป็นเวกเตอร์เริ่มต้น (initialization vector) ของ CBC
การตรวจสอบรหัสผ่านกับกับดักการตัดทอนขนาด saltSize
HotXLS จะทำการตรวจสอบความถูกต้องของรหัสผ่านก่อนที่จะเข้าถึงตัวแพ็กเกจ โดยใช้คู่ของตัวตรวจสอบจากคำอธิบาย มันจะถอดรหัส encryptedVerifierHashInput ด้วยคีย์ตัวแรกที่แยกได้ นำผลลัพธ์ไปแฮชด้วย SHA-512 ถอดรหัส encryptedVerifierHashValue ด้วยคีย์ตัวที่สองที่แยกได้ และนำแฮชทั้งสองตัวมาเปรียบเทียบกันไบต์ต่อไบต์ ผลต่างที่เกิดขึ้นหมายถึงรหัสผ่านไม่ถูกต้อง ซึ่งจะรายงานผลลัพธ์แยกเฉพาะแทนที่จะเป็นการรายงานว่าสมุดงานชำรุด และจุดสำคัญคือเนื้อหาของแพ็กเกจจะไม่ถูกถอดรหัสด้วยคีย์ที่ไม่ถูกต้อง จึงไม่มีเหตุการณ์ที่รหัสผ่านผิดจะทำให้ได้ข้อมูลที่ดูเหมือนปกติแต่ชำรุดภายใน
มีรายละเอียดข้อกำหนดอย่างหนึ่งตรงจุดนี้ที่ง่ายต่อการเขียนโค้ดผิดพลาด [MS-OFFCRYPTO] §2.3.4.13 กำหนดค่าตัวตรวจสอบไว้เป็นข้อมูลสุ่มขนาด saltSize ไบต์ โดยที่ saltSize คือความยาวของเกลือของตัวเข้ารหัสคีย์ ไม่ใช่ขนาดบล็อกของการเข้ารหัส เนื่องจากข้อความเข้ารหัส AES-CBC จะถูกจัดแนวบล็อก ตัวข้อมูลเข้าของตัวตรวจสอบที่ถอดรหัสแล้วจะส่งกลับมาแบบเติมข้อมูลให้หารด้วย 16 ลงตัว มันจึงต้องถูกตัดทอนกลับไปเป็นขนาด saltSize ก่อนคำนวณแฮช โปรแกรม Excel จะเขียนค่า saltSize ให้เท่ากับ blockSize คือ 16 เสมอ การเขียนโปรแกรมที่ข้ามขั้นตอนตัดทอนนี้จะสามารถผ่านการทดสอบกับผลลัพธ์จริงของ Excel ได้ตามปกติ แต่จะมาล้มเหลวกับไฟล์ตัวแรกที่มาจากผู้ผลิตรายอื่นที่เลือกใช้ความยาวเกลือที่ต่างออกไป HotXLS จะตัดทอนตามความยาวเกลือเนื่องจากนั่นคือสิ่งที่ข้อกำหนดระบุไว้ และการที่ค่าทั้งสองตัวตรงกันในการใช้งานจริงเป็นเพียงเรื่องบังเอิญ ไม่ใช่ข้อตกลงบังคับ
EncryptedPackage ถูกถอดรหัสอย่างไร?
สตรีมข้อมูล EncryptedPackage จะเริ่มต้นด้วยขนาดข้อความธรรมดาแบบ little-endian ขนาด 8 ไบต์ ตามด้วยข้อความเข้ารหัสที่แบ่งเป็นเซกเมนต์ขนาด 4096 ไบต์ และ HotXLS จะถอดรหัสทีละเซกเมนต์ด้วยค่า IV ใหม่เฉพาะตัวสำหรับแต่ละเซกเมนต์ คีย์ของตัวแพ็กเกจไม่ได้สร้างขึ้นจากรหัสผ่านโดยตรง: แต่มันคือคีย์กลางที่สุ่มขึ้นมาซึ่งผู้เขียนได้เข้ารหัสไว้ใน encryptedKeyValue และ HotXLS จะแกะมันด้วยคีย์ตัวที่สามที่แยกได้ พร้อมตัดทอนขนาดตามความยาวคีย์ที่ประกาศใน keyData ค่า IV ของแต่ละเซกเมนต์คือ SHA-512 ของเกลือ keyData เชื่อมกับดัชนีเซกเมนต์แบบ little-endian ขนาด 32 บิต และตัดทอนขนาดตามขนาดบล็อก โครงสร้างนี้หมายความว่าเซกเมนต์ขนาด 4096 ไบต์ใดๆ สามารถถูกถอดรหัสแยกเป็นอิสระต่อกันได้ ซึ่งช่วยให้รูปแบบนี้เอื้อต่อการเข้าถึงแบบสุ่มในทางทฤษฎี แม้ว่าในทางปฏิบัติ HotXLS จะใช้วิธีถอดรหัสข้อมูลทั้งแพ็กเกจลงในหน่วยความจำแล้วส่งต่อไบต์ของไฟล์ ZIP ผลลัพธ์ให้กับตัวโหลด XLSX ทั่วไปก็ตาม
ขนาดข้อความธรรมดาที่ประกาศไว้จะช่วยจัดการงานชิ้นสุดท้าย เอาต์พุตของ AES-CBC จะถูกจัดแนวบล็อก ดังนั้นเซกเมนต์สุดท้ายจะเก็บข้อมูลเติมพื้นที่ (padding) สูงสุด 15 ไบต์ซึ่งไม่ใช่เนื้อหาของเอกสาร บัฟเฟอร์ที่ถอดรหัสแล้วจะถูกตัดทอนตามคำนำหน้าขนาด และผลลัพธ์ที่ได้จะเป็นไฟล์ ZIP .xlsx ที่ Excel เข้ารหัสไว้อย่างถูกต้อง HotXLS จะตรวจสอบความถูกต้องของคำนำหน้าเทียบกับความยาวจริงของสตรีมก่อนทำการถอดรหัส เพื่อให้ไฟล์อัปเดตที่ถูกตัดขาดหรือช่องข้อมูลขนาดที่ถูกรบกวนสามารถแจ้งล้มเหลวได้อย่างถูกต้อง แทนการเกิดปัญหาข้อมูลล้นขอบเขต (overrunning)
การรายงานข้อผิดพลาดและขอบเขตที่แท้จริง
โหมดการล้มเหลวแต่ละแบบได้รับการออกแบบแยกจากกันอย่างเจตนา รหัสผ่านที่ผิดพลาดจะสร้างข้อยกเว้น (exception) พร้อมข้อความแจ้งเตือนที่ชัดเจน ขับเคลื่อนโดยผลการตรวจสอบ verifier mismatch เพื่อให้ส่วนติดต่อผู้ใช้สามารถร้องขอให้ผู้ใช้ลองใหม่อีกครั้งได้ คอนเทนเนอร์ CFB ที่ประกาศอัลกอริทึมนอกเหนือขอบเขตที่ระบบรองรับ (นอกเหนือจาก AES ร่วมกับ CBC และการแฮช SHA-512 ในคำอธิบาย Agile) หรือกรณีที่คอนเทนเนอร์ไม่ใช่ทั้งรูปแบบมาตรฐานหรือแบบ Agile จะสร้างข้อยกเว้นที่ระบุว่ารูปแบบนี้ไม่ได้รับการสนับสนุน เหตุการณ์สองกรณีนี้ต้องไม่มีวันนำมาปะปนกัน: การเสนอให้ผู้ใช้ลองรหัสผ่านใหม่กับรูปแบบที่ไม่รองรับเป็นการทำให้ผู้ใช้เสียเวลา และการรายงานรหัสผ่านผิดเป็นปัญหาโครงสร้างไฟล์จะส่งผลให้ทีมสนับสนุนวิเคราะห์ปัญหาผิดแนวทาง
function LoadUploadedWorkbook(const FileName: WideString;
const Password: WideString; Wb: TXLSXWorkbook): Boolean;
begin
Result := False;
try
Result := Wb.OpenEncrypted(FileName, Password) = 1;
except
on E: EXlsxEncryptionNotImplemented do
// เกิดขึ้นได้ทั้งกรณีรหัสผ่านผิดและรูปแบบการเข้ารหัส
// ที่ไม่รองรับ; E.Message จะระบุสาเหตุ ให้บันทึกตามจริง
// และเสนอให้ผู้ใช้ป้อนรหัสผ่านใหม่เฉพาะกรณีรหัสผ่านผิดเท่านั้น
RejectUpload(FileName, E.Message);
end;
end;
ขอบเขตการทำงานเป็นสิ่งที่คุณควรระบุไว้ให้ชัดเจน HotXLS สามารถอ่านคำอธิบาย Agile ที่ระบุรูปแบบ AES ในโหมด CBC ร่วมกับ SHA-512 ซึ่งครอบคลุมรูปแบบที่ Excel 2010 ถึง Excel 365 เขียนใช้งานจริงในขนาดคีย์ทั้งสามขนาด คำอธิบายที่ระบุระบบรหัสหรืออัลกอริทึมแฮชอื่นๆ จะถูกปฏิเสธทันทีแทนการเดาโครงสร้าง และระบบจะไม่เรียกใช้ตัวเข้ารหัสคีย์แบบอิงตามใบรับรอง (certificate-based) โดยจะพิจารณาเฉพาะตัวเข้ารหัสคีย์แบบรหัสผ่านเท่านั้น ในฝั่งการเขียนข้อมูล ในปัจจุบัน HotXLS จะสร้างข้อมูลในรูปแบบการเข้ารหัสมาตรฐาน (Standard Encryption) แทนรูปแบบ Agile ซึ่งเป็นข้อแตกต่างที่สำคัญหากเครื่องมือปลายทางมีการตรวจสอบรูปแบบการเข้ารหัส รายละเอียดสามารถศึกษาเพิ่มเติมได้ในบทความเกี่ยวกับการเขียนเอาต์พุต XLSX ที่ป้องกันด้วย AES
สมุดงานที่ป้องกันด้วยรหัสผ่านจะหมดประเด็นการเป็นกรณีพิเศษทันทีที่ตัวโหลดข้อมูลพิจารณาการเข้ารหัสเป็นส่วนหนึ่งของรูปแบบไฟล์ ไม่ใช่ข้อยกเว้นของระบบ จุดเข้าใช้งาน OpenEncrypted, กระบวนการสร้างคีย์ SHA-512 แบบวนซ้ำ และไพป์ไลน์ถอดรหัส AES-CBC ที่อธิบายไว้ในบทความนี้ จัดส่งมาเป็นส่วนหนึ่งของคอมโพเนนต์ HotXLS Delphi Excel Component ควบคู่ไปกับเอนจินการอ่านและเขียนไฟล์ XLS และ XLSX แบบเนทีฟสำหรับ Delphi และ C++Builder