Artikel Teknis

Baca File Excel Terenkripsi Agile di Delphi dengan HotXLS

HotXLS membaca file Excel terenkripsi Agile, yaitu perlindungan kata sandi yang diterapkan secara bawaan oleh Excel 2010 dan setiap versi yang lebih baru, melalui panggilan tunggal: TXLSXWorkbook.OpenEncrypted. Komponen mengurai deskriptor enkripsi XML, menurunkan kunci dari kata sandi dengan rantai hash spin-count SHA-512, memverifikasi kata sandi terhadap verifikator terenkripsi, lalu mendekripsi paket dalam segmen AES-CBC 4096 bita. Tidak melibatkan instalasi Excel, COM, atau DLL kripto eksternal

Artikel ini membahas sisi pembacaan dari enkripsi Agile secara khusus. Dua masalah terkait memiliki artikelnya sendiri: beroperasi bersama dengan skema RC4 dan XOR usang di dalam file BIFF .xls lama dibahas dalam artikel interop ECB dan RC4, and menghasilkan buku kerja (workbook) yang dilindungi kata sandi dengan Enkripsi Standar ECMA-376 dibahas dalam artikel keluaran XLSX yang dilindungi AES. Di sini file tersebut sudah ada, orang lain yang mengenkripsinya, dan tugas Anda adalah membukanya

Skenario yang memaksa masalah ini sudah biasa bagi siapa saja yang menjalankan alur pemrosesan dokumen. Layanan impor sisi server menerima unggahan buku kerja; tidak ada Excel di mesin tersebut dan tidak akan pernah ada; dan suatu pagi pelanggan mengunggah file .xlsx yang tampak biasa yang ditolak oleh pembaca ZIP karena itu bukan file ZIP sama sekali. Pelanggan menyimpannya dengan kata sandi. Sejak saat itu, pemuat (loader) Anda harus memahami [MS-OFFCRYPTO] atau ia akan mengembalikan file tersebut ke pengguna yang, dari sudut pandang mereka, tidak melakukan sesuatu yang aneh

Apa itu enkripsi Agile dalam file Excel?

Enkripsi Agile adalah skema perlindungan kata sandi yang didefinisikan dalam [MS-OFFCRYPTO] §2.3.4.10 hingga §2.3.4.15, dan merupakan apa yang ditulis oleh Excel 2010 dan versi setelahnya setiap kali buku kerja disimpan dengan kata sandi. File terenkripsi bukan lagi paket ZIP. Ini adalah wadah OLE Compound File Binary (CFB) yang menampung dua aliran: EncryptionInfo, yang menjelaskan bagaimana enkripsi dilakukan, dan EncryptedPackage, yang merupakan file ZIP .xlsx asli yang dienkripsi sebagai blob buram. Tanda tangan CFB (D0 CF 11 E0 A1 B1 1A E1) adalah keajaiban yang sama yang dibawa oleh file BIFF .xls usang, itulah sebabnya file yang diganti namanya atau dienkripsi tidak dapat diklasifikasikan berdasarkan ekstensi saja

Yang membedakan Agile dari pendahulunya adalah bahwa EncryptionInfo bersifat mendeskripsikan diri sendiri. Setelah awalan versi 8 bita, dengan versi utama dan minor keduanya bernilai 4, alirannya adalah deskriptor XML UTF-8. Elemen keyData mendeklarasikan cipher (AES), mode perantaian (ChainingModeCBC), hash (SHA512), panjang kunci dalam bit, ukuran blok, dan salt Base64. Elemen kata sandi keyEncryptor membawa salt miliknya sendiri, spinCount, dan tiga payload Base64: encryptedVerifierHashInput, encryptedVerifierHashValue, dan encryptedKeyValue. Excel menulis AES-256 dengan spin count 100.000, tetapi deskriptor diizinkan mendeklarasikan AES-128 atau AES-192, dan HotXLS menghormati apa pun yang dinyatakan oleh keyBits alih-alih mengasumsikan 256

Satu titik masuk untuk buku kerja plaintext, Standar, and Agile

TXLSXWorkbook.OpenEncrypted menangani ketiga status yang dapat ditemui oleh pemanggil, yaitu ZIP biasa, terenkripsi Standar, dan terenkripsi Agile, sehingga penangan unggahan tidak perlu mengklasifikasikan file sebelum memuatnya. Metode ini pertama-tama menguji file tersebut: jika tidak ada tanda tangan CFB, ia akan beralih ke jalur Open normal dan kata sandi diabaikan begitu saja. Jika file tersebut adalah wadah CFB, ia mencoba Enkripsi Standar ECMA-376 terlebih dahulu dan, ketika tanda tangan versi EncryptionInfo adalah Agile 4.4, ia mengirimkannya ke alur Agile. Nilai kembaliannya adalah 1 jika sukses, kontrak yang sama seperti Open

var
  Wb: TXLSXWorkbook;
begin
  Wb := TXLSXWorkbook.Create;
  try
    // Bekerja untuk file .xlsx biasa, terenkripsi Standar
    // maupun terenkripsi Agile secara sama
    if Wb.OpenEncrypted('upload.xlsx', 'customer-password') = 1 then
      Writeln(VarToWideStr(Wb.Sheets[1].Cells[1, 1].Value));
  finally
    Wb.Free;
  end;
end;

Kejadian fallback untuk masukan yang tidak terenkripsi lebih penting daripada penampilannya. Importer batch yang selalu memanggil OpenEncrypted tidak memerlukan percabangan di tempat pemanggilan: file yang tidak pernah dilindungi dimuat persis seperti sebelumnya, dan file yang tiba dalam keadaan terenkripsi didekripsi di tempat lalu dimasukkan ke pemuat ZIP biasa sebagai aliran dalam memori. Hanya ada satu jalur kode untuk diuji, bukan tiga

Bagaimana kata sandi menjadi kunci AES?

Enkripsi Agile tidak pernah menggunakan kata sandi secara langsung. HotXLS pertama-tama menghitung hash iterasi: digest awal adalah SHA-512 atas salt kata sandi yang digabungkan dengan bita UTF-16LE dari kata sandi, lalu digest tersebut di-hash ulang sebanyak spinCount kali, dengan setiap putaran menambahkan penghitung iterasi little-endian 32-bit ke digest sebelumnya. Dengan spin count bawaan Excel sebesar 100.000, itu berarti seratus ribu panggilan serial SHA-512 untuk setiap upaya kata sandi, dan itulah intinya. Spin count adalah pembatas kekuatan kasar (brute-force throttle): ini membebani pemanggil yang sah beberapa milidetik sekali saja, dan membebani penyerang kamus (dictionary attacker) beberapa milidetik yang same untuk setiap tebakan

// hash kata sandi teriterasi [MS-OFFCRYPTO]:
//   H(0) = SHA-512(salt + UTF-16LE(password))
//   H(n) = SHA-512(LE32(n - 1) + H(n - 1)), diulang sebanyak spinCount kali
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);            // penghitung iterasi, little-endian
    Move(Result[0], buf[4], 64);   // digest sebelumnya
    Result := XlsSHA512(buf);
  end;
end;

Hash hasil perputaran tersebut masih belum menjadi kunci. Tiga kunci berbeda diturunkan darinya dengan menghitung hash sekali lagi dengan kunci blok 8 bita tetap yang ditambahkan, satu konstanta per tujuan: FE A7 D2 76 3B 4B 9E 79 untuk mendekripsi masukan verifikator, D7 AA 0F 6D 30 61 34 4E untuk hash verifikator, dan 14 6E 0B E7 AB AC D0 D6 untuk membuka kunci paket sebenarnya. Setiap hasil SHA-512 dipotong ke panjang kunci yang dideklarasikan, dan, menurut [MS-OFFCRYPTO], diisi dengan bita 0x36 dalam kasus teoretis di mana hash lebih pendek dari kunci. Aturan pengisian 0x36 yang same berlaku saat salt kata sandi diperpanjang ke ukuran blok untuk digunakan sebagai vektor inisialisasi CBC

Verifikasi kata sandi dan jebakan pemotongan saltSize

HotXLS memverifikasi kata sandi sebelum menyentuh paket, menggunakan pasangan verifikator dari deskriptor. Ini mendekripsi encryptedVerifierHashInput dengan kunci turunan pertama, menghitung hash hasilnya dengan SHA-512, mendekripsi encryptedVerifierHashValue dengan kunci turunan kedua, dan membandingkan kedua digest bita demi bita. Ketidakcocokan berarti kata sandi salah, yang dilaporkan sebagai hasil yang berbeda alih-alih buku kerja yang rusak, dan yang terpenting itu berarti isi paket tidak pernah didekripsi dengan kunci yang salah, sehingga tidak ada skenario di mana kata sandi yang salah menghasilkan data korup yang tampak masuk akal

Ada detail spesifikasi di sini yang mudah salah. [MS-OFFCRYPTO] §2.3.4.13 mendefinisikan verifikator sebagai bita saltSize data acak, di mana saltSize adalah panjang salt penyandi kunci, bukan ukuran blok sandi. Karena teks sandi AES-CBC disejajarkan per blok, masukan verifikator yang didekripsi kembali diisi (padded) hingga kelipatan 16 bita, dan harus dipotong kembali ke saltSize sebelum di-hash. Excel selalu menulis saltSize sama dengan blockSize, keduanya 16, sehingga implementasi yang melewatkan pemotongan tersebut akan lolos setiap pengujian terhadap keluaran asli Excel dan kemudian gagal pada file pertama dari produsen yang memilih panjang salt berbeda. HotXLS memotong ke panjang salt karena itulah yang sebenarnya dinyatakan oleh spesifikasi, dan kesepakatan kedua nilai tersebut dalam praktiknya adalah kebetulan, bukan kontrak

Bagaimana EncryptedPackage didekripsi?

Aliran EncryptedPackage dimulai dengan ukuran plaintext little-endian 8 bita, diikuti oleh teks sandi dalam segmen 4096 bita, dan HotXLS mendekripsinya segmen demi segmen dengan IV baru per segmen. Kunci paket itu sendiri tidak diturunkan dari kata sandi: ini adalah kunci perantara acak yang ditulis oleh penulis ke dalam encryptedKeyValue, dan HotXLS membukanya dengan kunci turunan ketiga, dipotong ke panjang kunci yang dideklarasikan oleh keyData. IV setiap segmen adalah SHA-512 atas salt keyData yang digabungkan dengan indeks segmen little-endian 32-bit, dipotong ke ukuran blok. Konstruksi itu berarti segmen 4096 bita mana pun dapat didekripsi secara independen, yang pada prinsipnya membuat format ini ramah terhadap akses acak, meskipun HotXLS mendekripsi seluruh paket ke memori dan menyerahkan bita ZIP yang dihasilkan ke pemuat XLSX normalnya

Pelaporan kesalahan dan batasan jujur

Kegagalan-kegagalan sengaja dipisahkan. Kata sandi yang salah memicu pengecualian dengan pesan kata sandi salah yang eksplisit, didorong oleh ketidakcocokan verifikator, sehingga UI dapat meminta pengguna untuk mencoba lagi. Wadah CFB yang deskriptornya mendeklarasikan algoritma di luar set yang didukung, apa pun selain AES dengan mode CBC dan hash SHA-512 dalam deskriptor Agile, atau wadah yang bukan Standar maupun Agile, memicu pengecualian berbeda yang mengidentifikasi skema sebagai tidak didukung. Keduanya tidak boleh disamakan: mencoba kembali kata sandi terhadap skema yang tidak didukung membuang-buang waktu pengguna, dan melaporkan kata sandi yang salah sebagai kesalahan format mengarahkan tim dukungan Anda ke jalur yang salah

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
      // Dipicu baik untuk kata sandi yang salah maupun skema yang
      // tidak didukung; E.Message menyatakan yang mana, jadi catat secara
      // verbatim dan hanya tawarkan coba lagi kata sandi untuk kasus kata sandi salah
      RejectUpload(FileName, E.Message);
  end;
end;

Batasan-batasan tersebut patut dinyatakan secara jelas. HotXLS membaca deskriptor Agile yang mendeklarasikan AES dalam mode CBC dengan SHA-512, yang mencakup apa yang sebenarnya ditulis oleh Excel 2010 hingga Excel 365, dalam ketiga ukuran kunci. Deskriptor yang mendeklarasikan cipher atau algoritma hash lain ditolak alih-alih ditebak-tebak, dan penyandi kunci berbasis sertifikat tidak dikonsultasikan, hanya penyandi kunci kata sandi yang digunakan. Di sisi penulisan, HotXLS saat ini menghasilkan Enkripsi Standar alih-alih Agile, perbedaan yang penting jika perkakas hilir memeriksa skemanya; detailnya ada di artikel tentang menulis keluaran XLSX yang dilindungi AES

Unggahan yang dilindungi kata sandi tidak lagi menjadi kasus khusus setelah pemuat memperlakukan enkripsi sebagai bagian dari format file alih-alih sebagai pengecualian terhadapnya. Titik masuk OpenEncrypted, derivasi spin-count SHA-512, dan alur kerja AES-CBC bersegmen yang dijelaskan di sini dikirimkan sebagai bagian dari HotXLS Delphi Excel Component, bersama dengan sisa mesin pembacaan dan penulisan XLS dan XLSX bawaannya untuk Delphi dan C++Builder