Το HotXLS διαβάζει αρχεία Excel με κρυπτογράφηση Agile, την προστασία με κωδικό πρόσβασης που εφαρμόζει από προεπιλογή το Excel 2010 και κάθε μεταγενέστερη έκδοση, μέσω μιας μόνο κλήσης: της TXLSXWorkbook.OpenEncrypted. Το στοιχείο αναλύει τον περιγραφέα κρυπτογράφησης XML, παράγει κλειδιά από τον κωδικό πρόσβασης με μια αλυσίδα κατακερματισμού SHA-512 spin-count, επαληθεύει τον κωδικό πρόσβασης έναντι του κρυπτογραφημένου επαληθευτή, και στη συνέχεια αποκρυπτογραφεί το πακέτο σε τμήματα AES-CBC των 4096 byte. Δεν εμπλέκεται εγκατάσταση του Excel, COM, ούτε εξωτερικό DLL κρυπτογράφησης
Αυτό το άρθρο καλύπτει ειδικά την πλευρά της ανάγνωσης της κρυπτογράφησης Agile. Δύο γειτονικά προβλήματα έχουν τα δικά τους άρθρα: η διαλειτουργικότητα με τα παλαιά σχήματα RC4 και XOR μέσα σε παλιά αρχεία BIFF .xls καλύπτεται στο άρθρο για τη διαλειτουργικότητα ECB και RC4, και η παραγωγή προστατευμένων με κωδικό πρόσβασης βιβλίων εργασίας με την Τυπική Κρυπτογράφηση (Standard Encryption) ECMA-376 καλύπτει το άρθρο για την προστατευμένη με AES έξοδο XLSX. Εδώ το αρχείο υπάρχει ήδη, κάποιος άλλος το κρυπτογράφησε, και η δουλειά σας είναι να το ανοίξετε
Το σενάριο που επιβάλλει το ζήτημα είναι οικείο σε οποιονδήποτε εκτελεί μια ροή επεξεργασίας εγγράφων. Μια υπηρεσία εισαγωγής στην πλευρά του διακομιστή δέχεται μεταφορτώσεις βιβλίων εργασίας· δεν υπάρχει Excel στο μηχάνημα ούτε θα υπάρξει ποτέ· και ένα πρωί ένας πελάτης μεταφορτώνει ένα απολύτως συνηθισμένο .xlsx το οποίο ο αναγνώστης ZIP απορρίπτει επειδή δεν είναι καθόλου αρχείο ZIP. Ο πελάτης το αποθήκευσε με κωδικό πρόσβασης. Από εκείνη τη στιγμή, ο φορτωτής σας είτε κατανοεί το [MS-OFFCRYPTO] είτε επιστρέφει το αρχείο σε έναν χρήστη ο οποίος, από τη δική του σκοπιά, δεν έκανε τίποτα ασυνήθιστο
Tι είναι η κρυπτογράφηση Agile σε ένα αρχείο Excel;
Η κρυπτογράφηση Agile είναι το σχήμα προστασίας με κωδικό πρόσβασης που ορίζεται στο [MS-OFFCRYPTO] §2.3.4.10 έως §2.3.4.15, και είναι αυτό που γράφει το Excel 2010 και οι μεταγενέστερες εκδόσεις κάθε φορά που ένα βιβλίο εργασίας αποθηκεύεται με κωδικό πρόσβασης. Το κρυπτογραφημένο αρχείο δεν είναι πλέον πακέτο ZIP. Είναι ένα κοντέινερ OLE Compound File Binary (CFB) που περιέχει δύο ροές (streams): την EncryptionInfo, η οποία περιγράφει πώς εκτελέστηκε η κρυπτογράφηση, και την EncryptedPackage, η οποία είναι το πραγματικό ZIP .xlsx κρυπτογραφημένο ως αδιαφανές blob. Η υπογραφή CFB (D0 CF 11 E0 A1 B1 1A E1) είναι το ίδιο μαγικό αναγνωριστικό που φέρουν τα παλαιά αρχεία BIFF .xls, γι' αυτό και ένα μετονομασμένο ή κρυπτογραφημένο αρχείο δεν μπορεί να ταξινομηθεί μόνο από την επέκτασή του
Αυτό που διακρίνει την Agile από τους προκατόχους της είναι ότι η EncryptionInfo είναι αυτοπερισταλτική (self-describing). Μετά από ένα πρόθεμα έκδοσης 8 byte, με κύρια και δευτερεύουσα έκδοση αμφότερες στο 4, η ροή είναι ένας XML περιγραφέας UTF-8. Ένα στοιχείο keyData δηλώνει τον κρυπτογραφικό αλγόριθμο (AES), τη λειτουργία αλυσίδας (ChainingModeCBC), τον κατακερματισμό (SHA512), το μήκος κλειδιού σε bit, το μέγεθος μπλοκ και ένα αλάτι (salt) Base64. Ένα στοιχείο κωδικού πρόσβασης keyEncryptor μεταφέρει το δικό του αλάτι, το spinCount και τρία ωφέλιμα φορτία Base64: τα encryptedVerifierHashInput, encryptedVerifierHashValue και encryptedKeyValue. Το Excel γράφει AES-256 με spin count 100.000, αλλά ο περιγραφέας επιτρέπεται να δηλώσει AES-128 ή AES-192, και το HotXLS σέβεται ό,τι αναφέρει το keyBits αντί να υποθέτει 256
Ένα σημείο εισόδου για βιβλία εργασίας απλού κειμένου, Standard και Agile
Η TXLSXWorkbook.OpenEncrypted χειρίζεται και τις τρεις καταστάσεις που μπορεί να συναντήσει ένας καλών: απλό ZIP, κρυπτογραφημένο με Standard και κρυπτογραφημένο με Agile, οπότε οι διαχειριστές μεταφόρτωσης δεν χρειάζεται να ταξινομούν τα αρχεία πριν τα φορτώσουν. Η μέθοδος αρχικά ανιχνεύει το αρχείο: εάν δεν υπάρχει υπογραφή CFB, παραπέμπει στην κανονική διαδρομή Open και ο κωδικός πρόσβασης απλά αγνοείται. Εάν το αρχείο είναι κοντέινερ CFB, δοκιμάζει πρώτα την Τυπική Κρυπτογράφηση ECMA-376 και, όταν η υπογραφή έκδοσης της EncryptionInfo είναι η Agile 4.4, μεταβαίνει στη ροή εργασίας Agile. Η τιμή επιστροφής είναι 1 σε περίπτωση επιτυχίας, το ίδιο συμβόλαιο με την Open
var
Wb: TXLSXWorkbook;
begin
Wb := TXLSXWorkbook.Create;
try
// Works for plain .xlsx, Standard-encrypted and
// Agile-encrypted files alike
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 πάνω στο αλάτι του κωδικού πρόσβασης συνενωμένο με τα bytes UTF-16LE του κωδικού πρόσβασης, και στη συνέχεια η σύνοψη κατακερματίζεται εκ νέου spinCount φορές, με κάθε γύρο να προτάσσει τον 32-bit little-endian μετρητή επανάληψης στην προηγούμενη σύνοψη. Με την προεπιλεγμένη τιμή spin count 100.000 του Excel, αυτό σημαίνει εκατό χιλιάδες σειριακές κλήσεις SHA-512 ανά προσπάθεια κωδικού πρόσβασης, και αυτός είναι ολόκληρος ο σκοπός. Το spin count είναι ένας επιβραδυντής brute-force επιθέσεων: κοστίζει σε έναν νόμιμο καλούντα μερικά χιλιοστά του δευτερολέπτου μία φορά, και κοστίζει σε έναν επιτιθέμενο λεξικού τα ίδια χιλιοστά του δευτερολέπτου για κάθε μεμονωμένη προσπάθεια
// [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); // iteration counter, little-endian
Move(Result[0], buf[4], 64); // previous digest
Result := XlsSHA512(buf);
end;
end;
Ο κατακερματισμός spin hash εξακολουθεί να μην είναι κλειδί. Τρία διαφορετικά κλειδιά παράγονται από αυτόν κατακερματίζοντάς τον άλλη μία φορά με προσαρτημένο ένα σταθερό κλειδί μπλοκ 8 byte, μία σταθερά ανά σκοπό: FE A7 D2 76 3B 4B 9E 79 για την αποκρυπτογράφηση της εισόδου του επαληθευτή, D7 AA 0F 6D 30 61 34 4E για τον κατακερματισμό του επαληθευτή, και 14 6E 0B E7 AB AC D0 D6 για το ξετύλιγμα του πραγματικού κλειδιού πακέτου. Κάθε αποτέλεσμα SHA-512 περικόπτεται στο δηλωμένο μήκος κλειδιού και, σύμφωνα με το [MS-OFFCRYPTO], γεμίζει με bytes 0x36 στη θεωρητική περίπτωση όπου ο κατακερματισμός είναι μικρότερος από το κλειδί. Ο ίδιος κανόνας γεμίσματος με 0x36 ισχύει όταν το αλάτι του κωδικού πρόσβασης επεκτείνεται στο μέγεθος του μπλοκ για χρήση ως διάνυσμα αρχικοποίησης (IV) CBC
Επαλήθευση κωδικού πρόσβασης και η παγίδα περικοπής saltSize
Το HotXLS επαληθεύει τον κωδικό πρόσβασης πριν αγγίξει το πακέτο, χρησιμοποιώντας το ζεύγος επαληθευτών από τον περιγραφέα. Αποκρυπτογραφεί το encryptedVerifierHashInput με το πρώτο παραγόμενο κλειδί, κατακερματίζει το αποτέλεσμα με SHA-512, αποκρυπτογραφεί το encryptedVerifierHashValue με το δεύτερο παραγόμενο κλειδί, και συγκρίνει τις δύο συνόψεις byte προς byte. Μια ασυμφωνία σημαίνει ότι ο κωδικός πρόσβασης είναι λάθος, κάτι που αναφέρεται ως διακριτό αποτέλεσμα και όχι ως κατεστραμμένο βιβλίο εργασίας, και κρίσιμα σημαίνει ότι το σώμα του πακέτου δεν αποκρυπτογραφείται ποτέ με λάθος κλειδί, οπότε δεν υπάρχει σενάριο όπου ένας λάθος κωδικός πρόσβασης παράγει εύλογα αλλά κατεστραμμένα δεδομένα
Υπάρχει μια λεπτομέρεια των προδιαγραφών εδώ που είναι εύκολο να γίνει λάθος. Το [MS-OFFCRYPTO] §2.3.4.13 ορίζει τον επαληθευτή ως bytes saltSize τυχαίων δεδομένων, όπου saltSize είναι το μήκος του αλατιού του κρυπτογραφητή κλειδιού, όχι το μέγεθος μπλοκ του κρυπτογράφου. Επειδή το κρυπτογραφημένο κείμενο AES-CBC είναι ευθυγραμμισμένο με το μπλοκ, η αποκρυπτογραφημένη είσοδος επαληθευτή επιστρέφει γεμισμένη σε πολλαπλάσιο των 16 byte, και πρέπει να περικοπεί πίσω στο saltSize πριν από τον κατακερματισμό. Το Excel γράφει πάντα saltSize ίσο με blockSize, αμφότερα 16, οπότε μια υλοποίηση που παρακάμπτει την περικοπή περνά κάθε δοκιμή έναντι της πραγματικής εξόδου του Excel και στη συνέχεια αποτυγχάνει στο πρώτο αρχείο από έναν παραγωγό που επέλεξε διαφορετικό μήκος αλατιού. Το HotXLS περικόπτει στο μήκος του αλατιού επειδή αυτό λέει στην πραγματικότητα η προδιαγραφή, και το γεγονός ότι οι δύο τιμές συμφωνούν στην πράξη είναι σύμπτωση, όχι συμβόλαιο
Πώς αποκρυπτογραφείται το EncryptedPackage;
Η ροή EncryptedPackage ξεκινά με ένα μέγεθος απλού κειμένου 8 byte little-endian, ακολουθούμενο από το κρυπτογραφημένο κείμενο σε τμήματα των 4096 byte, και το HotXLS το αποκρυπτογραφεί τμήμα προς τμήμα με ένα νέο IV ανά τμήμα. Το ίδιο το κλειδί του πακέτου δεν προέρχεται από τον κωδικό πρόσβασης: είναι ένα τυχαίο ενδιάμεσο κλειδί που ο εγγραφέας κρυπτογράφησε στο encryptedKeyValue, και το HotXLS το ξετυλίγει με το τρίτο παραγόμενο κλειδί, περικόπτοντας στο μήκος κλειδιού που δηλώνει το keyData. Το IV κάθε τμήματος είναι SHA-512 πάνω στο αλάτι keyData συνενωμένο με τον 32-bit little-endian δείκτη τμήματος, περικομμένο στο μέγεθος του μπλοκ. Αυτή η κατασκευή σημαίνει ότι οποιοδήποτε τμήμα 4096 byte μπορεί να αποκρυπτογραφηθεί ανεξάρτητα, γεγονός που καθιστά τη μορφή φιλική προς την τυχαία πρόσβαση θεωρητικά, αν και το HotXLS αποκρυπτογραφεί ολόκληρο το πακέτο στη μνήμη και παραδίδει τα προκύπτοντα bytes ZIP στον κανονικό του φορτωτή XLSX
Το δηλωμένο μέγεθος απλού κειμένου κάνει την τελευταία δουλειά. Η έξοδος AES-CBC είναι ευθυγραμμισμένη με το μπλοκ, οπότε το τελευταίο τμήμα φέρει έως και 15 bytes γεμίσματος που δεν αποτελούν μέρος του εγγράφου· ο αποκρυπτογραφημένος buffer περικόπτεται στο πρόθεμα μεγέθους, και το αποτέλεσμα είναι ακριβώς το ZIP .xlsx που κρυπτογράφησε το Excel. Το HotXLS επικυρώνει το πρόθεμα έναντι του πραγματικού μήκους της ροής πριν από την αποκρυπτογράφηση, οπότε μια κομμένη μεταφόρτωση ή ένα παραποιημένο πεδίο μεγέθους αποτυγχάνει καθαρά αντί για υπερχείλιση
Αναφορά σφαλμάτων και ειλικρινή όρια
Οι καταστάσεις αποτυχίας διαχωρίζονται σκόπιμα. Ένας λανθασμένος κωδικός πρόσβασης εγείρει μια εξαίρεση με ρητό μήνυμα λάθους κωδικού, που προκαλείται από την ασυμφωνία του επαληθευτή, ώστε η διεπαφή χρήστη να μπορεί να προτρέψει τον χρήστη να ξαναπροσπαθήσει. Ένα κοντέινερ CFB του οποίου ο περιγραφέας δηλώνει αλγορίθμους εκτός του υποστηριζόμενου συνόλου, οτιδήποτε άλλο εκτός από AES με αλυσίδα CBC και κατακερματισμό SHA-512 σε περιγραφέα Agile, ή ένα κοντέινερ που δεν είναι ούτε Standard ούτε 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
// Raised for both a wrong password and an unsupported
// scheme; E.Message states which, so log it verbatim and
// only offer a password retry for the wrong-password case
RejectUpload(FileName, E.Message);
end;
end;
Τα όρια αξίζει να αναφερθούν με σαφήνεια. Το HotXLS διαβάζει περιγραφείς Agile που δηλώνουν AES σε λειτουργία CBC με SHA-512, γεγονός που καλύπτει ό,τι γράφουν στην πράξη το Excel 2010 έως το Excel 365, και στα τρία μεγέθη κλειδιών. Οι περιγραφείς που δηλώνουν άλλους κρυπτογραφικούς αλγορίθμους ή αλγορίθμους κατακερματισμού απορρίπτονται αντί να γίνονται υποθέσεις, και οι κρυπτογραφητές κλειδιών που βασίζονται σε πιστοποιητικά δεν συμβουλεύονται, παρά μόνο ο κρυπτογραφητής κλειδιού κωδικού πρόσβασης. Στην πλευρά της εγγραφής, το HotXLS παράγει επί του παρόντος Τυπική Κρυπτογράφηση (Standard Encryption) αντί για Agile, μια διάκριση που έχει σημασία εάν τα κατάντη εργαλεία επιθεωρούν το σχήμα· οι λεπτομέρειες βρίσκονται στο άρθρο για τη συγγραφή προστατευμένης με AES εξόδου XLSX
Οι προστατευμένες με κωδικό πρόσβασης μεταφορτώσεις παύουν να αποτελούν ειδική περίπτωση μόλις ο φορτωτής αντιμετωπίσει την κρυπτογράφηση ως μέρος της μορφής αρχείου και όχι ως εξαίρεση σε αυτήν. Το σημείο εισόδου OpenEncrypted, η παραγωγή SHA-512 spin-count και η κατακερματισμένη ροή εργασίας AES-CBC που περιγράφονται εδώ αποστέλλονται ως μέρος του HotXLS Delphi Excel Component, μαζί με την υπόλοιπη εγγενή μηχανή ανάγνωσης και εγγραφής XLS και XLSX για Delphi και C++Builder