Hallo Community
Da sich Synology ja relativ bedeckt hält, was die Datenverschlüsselung von Hyper Backup angeht, habe ich mich mal daran gemacht und mittels Reverse Engineering ein Script gebastelt, das verschlüsselte Daten aus einem Backup extrahieren kann und somit auch zeigt, wie die Verschlüsselung funktioniert.
Das Script findet ihr unter: https://github.com/mistersandman/hyperbackup_decrypt
Für alle die sich nicht mit dem Script beschäftigen wollen, hier die wichtigsten Details:
1. Synology verwendet ein Standard-Schema bei der Verschlüsselung: Die Daten sind mit AES verschlüsselt und die Parameter für AES sind wiederum mit RSA verschlüsselt.
2. Die Files und Filenamen werden mit AES256 im CBC Modus verschlüsselt. AES-CBC benötigt einen Schlüssel und einen Initialisierungsvektor.
3. Die verschlüsselten Filenamen und einige Metadaten sind in einer SQLite Datenbank abgespeichert. Die Filenamen müssen zuerst via Base64 dekodiert werden und werden danach mit AES256-CBC entschlüsselt. Als Schlüssel für AES dient hierzu der SHA256-Hash vom privaten RSA Schlüssel zusammen mit dem unikey Wert aus dem _Syno_TaskConfig File (ein von Hyper Backup generierter unique identifier für das Backup). Als Initialisierungsvektor für AES dient der MD5-Hash vom unikey Wert zusammen mit einem festen Saltwert (kkE7sRZRvnbVlJFofhD7WCXumXBGyzki)
4. Um in der Ordnerstruktur zu navigieren, haben alle Einträge in der Filedatenbank eine ID und eine Parent ID. Die ID besteht aus den ersten 4 bytes des MD5-Hashes vom verschlüsselten Pfad des Elternordners und dem vollen MD5-Hash vom verschlüsselten Pfad des Eintrags, wobei sich der verschlüsselte Pfad aus den verschlüsselten Ordnernamen verbunden durch / (slash) zusammensetzt. Die ID des Wurzelordners besteht aus den ersten 4 bytes des MD5-Hashes von "." und dem vollen MD5-Hash von "." (also 5058f1af5058f1af8388633f609cadb75a75dc9d).
5. Die eigentlichen Daten sind in Chunks unterteilt, welche in verschiedenen Buckets abgespeichert werden. Die Chunks sind mit AES256-CBC verschlüsselt. Der Schlüssel und der Initialisierungsvektor sind mit RSA2048 verschlüsselt in einer Datenbank gespeichert und können mit dem privaten RSA Schlüssel entschlüsselt werden. Die entschlüsselten Chunks können zusätzlich noch mit lz4 komprimiert sein.
6. Der öffentliche RSA Schlüssel ist unter Config/public.pem{.n} gespeichert. Damit werden die AES256-CBC Parameter für die Chunk-Entschlüsselung verschlüsselt. Diese Parameter können mit dem privaten RSA Schlüssel (der zum Download angeboten wird, nachdem man einen Backup Task in Hyper Backup eingerichtet hat) wieder entschlüsselt werden. Die Integrität der Parameter kann mit einem MD5-Hash des verschlüsselten AES-Schlüssels zusammen mit einem festen Saltwert (8Llx6OSaDPzbwCkjG8eYc64GZGMIlMXm) und dem verschlüsselten Initialisierungsvektor überprüft werden. Dieser MD5-Hash ist ebenfalls in der Datenbank gespeichert.
7. Der private RSA Schlüssel kann mittels dem festgelegten Passwort aus dem File Config/encKeys{.n} wiederhergestellt werden. Er liegt mit AES256-CBC verschlüsselt in dieser Datei. Der AES Schlüssel für die Entschlüsselung ist der SHA256-Hash von einem festen Saltwert zusammen mit dem Passwort (5mNgudh053SUoMrZxoKG8GUWyj6kEtGO). Der Initialisierungsvektor ist der MD5-Hash vom unikey Wert zusammen mit einem festen Saltwert (CIpfMargmxetgFtkBmG3KqEiQ6qfqZgF)
8. Um das Passwort zu verifizieren, wird zweimal ein SHA256-Hash auf einen fixen Saltwert (5mNgudh053SUoMrZxoKG8GUWyj6kEtGO) zusammen mit dem Passwort angewendet und mit einem Wert verglichen, der auch in Config/encKeys{.n} gespeichert ist.
9. Die Filestruktur (das Mapping von Filenamen zu den zugehörigen Chunks) ist relativ komplex, vermutlich um die Speichereffizienz von mehreren Backupversionen zu optimieren. Die Backupversionierung habe ich (noch) nicht durchschaut. Ich glaube, dass mein Script lediglich die jeweils neuste Version extrahiert. In der Datenbank der Filenamen findet sich ein Offsetwert. In der Datei Config/virtual_file.index/0.idx{.n} findet sich an diesem Offset Informationen zum File. Unter anderem findet man dort die ID des file chunk indexes und einen Offset für diesen Index. Im entsprechenden file chunk index unter Config/file_chunk{ID}.index/0.idx{.n} am Offset stehen dann weitere Offsets für den chunk pool index Pool/chunk_index/0.idx{.n}. Im chunk pool index an den entsprechenden Offsets stehen dann schlussendlich die bucket ID und der bucket index offset für den chunk. Der bucket index befindet sich in Pool/0/0/{ID}.index{.n}. An den bucket index offsets stehen der chunk offset im bucket file, die Grösse des Chunks und für die Dekomprimierung noch die originale Grösse der Daten. Der verschlüsselte Chunk kann dann aus Pool/0/0/{ID}.bucket{.n} extrahiert, entschlüsselt und dekomprimiert werden. Die Integrität eines Chunks kann mittels des MD5-Hash über den entschlüsselten und dekomprimierten Chunkinhalt überprüft werden. Dieser Hashwert ist ebenfalls in den bucket index Einträgen gespeichert.
Es gibt noch einige Stellen im Script in denen Daten gelesen werden, deren Bedeutung sich mir noch nicht erschlossen hat. Vielleicht hat ja jemand von euch noch Spass daran, da noch weiter zu forschen. Ebenfalls offen ist noch, wie die Versionierung funktioniert, da ich meinen Fokus nur auf die Verschlüsselung gelegt habe. Ein weiterer Spezialfall ist das Backup der DSM-Konfiguration. Dazu findet sich zwar einen Eintrag in Config/@Share/@AppConfig zum file config.dss, welcher aber einen negativen Eintrag als Offset für die virtuelle Filetable hat. Ich vermute diese Datei wird verschlüsselt im File Pool/file_pool/1.file{.n} gespeichert, aber ich habe dessen Format noch nicht entziffern können.
Gruss mrsandman
Da sich Synology ja relativ bedeckt hält, was die Datenverschlüsselung von Hyper Backup angeht, habe ich mich mal daran gemacht und mittels Reverse Engineering ein Script gebastelt, das verschlüsselte Daten aus einem Backup extrahieren kann und somit auch zeigt, wie die Verschlüsselung funktioniert.
Das Script findet ihr unter: https://github.com/mistersandman/hyperbackup_decrypt
Für alle die sich nicht mit dem Script beschäftigen wollen, hier die wichtigsten Details:
1. Synology verwendet ein Standard-Schema bei der Verschlüsselung: Die Daten sind mit AES verschlüsselt und die Parameter für AES sind wiederum mit RSA verschlüsselt.
2. Die Files und Filenamen werden mit AES256 im CBC Modus verschlüsselt. AES-CBC benötigt einen Schlüssel und einen Initialisierungsvektor.
3. Die verschlüsselten Filenamen und einige Metadaten sind in einer SQLite Datenbank abgespeichert. Die Filenamen müssen zuerst via Base64 dekodiert werden und werden danach mit AES256-CBC entschlüsselt. Als Schlüssel für AES dient hierzu der SHA256-Hash vom privaten RSA Schlüssel zusammen mit dem unikey Wert aus dem _Syno_TaskConfig File (ein von Hyper Backup generierter unique identifier für das Backup). Als Initialisierungsvektor für AES dient der MD5-Hash vom unikey Wert zusammen mit einem festen Saltwert (kkE7sRZRvnbVlJFofhD7WCXumXBGyzki)
4. Um in der Ordnerstruktur zu navigieren, haben alle Einträge in der Filedatenbank eine ID und eine Parent ID. Die ID besteht aus den ersten 4 bytes des MD5-Hashes vom verschlüsselten Pfad des Elternordners und dem vollen MD5-Hash vom verschlüsselten Pfad des Eintrags, wobei sich der verschlüsselte Pfad aus den verschlüsselten Ordnernamen verbunden durch / (slash) zusammensetzt. Die ID des Wurzelordners besteht aus den ersten 4 bytes des MD5-Hashes von "." und dem vollen MD5-Hash von "." (also 5058f1af5058f1af8388633f609cadb75a75dc9d).
5. Die eigentlichen Daten sind in Chunks unterteilt, welche in verschiedenen Buckets abgespeichert werden. Die Chunks sind mit AES256-CBC verschlüsselt. Der Schlüssel und der Initialisierungsvektor sind mit RSA2048 verschlüsselt in einer Datenbank gespeichert und können mit dem privaten RSA Schlüssel entschlüsselt werden. Die entschlüsselten Chunks können zusätzlich noch mit lz4 komprimiert sein.
6. Der öffentliche RSA Schlüssel ist unter Config/public.pem{.n} gespeichert. Damit werden die AES256-CBC Parameter für die Chunk-Entschlüsselung verschlüsselt. Diese Parameter können mit dem privaten RSA Schlüssel (der zum Download angeboten wird, nachdem man einen Backup Task in Hyper Backup eingerichtet hat) wieder entschlüsselt werden. Die Integrität der Parameter kann mit einem MD5-Hash des verschlüsselten AES-Schlüssels zusammen mit einem festen Saltwert (8Llx6OSaDPzbwCkjG8eYc64GZGMIlMXm) und dem verschlüsselten Initialisierungsvektor überprüft werden. Dieser MD5-Hash ist ebenfalls in der Datenbank gespeichert.
7. Der private RSA Schlüssel kann mittels dem festgelegten Passwort aus dem File Config/encKeys{.n} wiederhergestellt werden. Er liegt mit AES256-CBC verschlüsselt in dieser Datei. Der AES Schlüssel für die Entschlüsselung ist der SHA256-Hash von einem festen Saltwert zusammen mit dem Passwort (5mNgudh053SUoMrZxoKG8GUWyj6kEtGO). Der Initialisierungsvektor ist der MD5-Hash vom unikey Wert zusammen mit einem festen Saltwert (CIpfMargmxetgFtkBmG3KqEiQ6qfqZgF)
8. Um das Passwort zu verifizieren, wird zweimal ein SHA256-Hash auf einen fixen Saltwert (5mNgudh053SUoMrZxoKG8GUWyj6kEtGO) zusammen mit dem Passwort angewendet und mit einem Wert verglichen, der auch in Config/encKeys{.n} gespeichert ist.
9. Die Filestruktur (das Mapping von Filenamen zu den zugehörigen Chunks) ist relativ komplex, vermutlich um die Speichereffizienz von mehreren Backupversionen zu optimieren. Die Backupversionierung habe ich (noch) nicht durchschaut. Ich glaube, dass mein Script lediglich die jeweils neuste Version extrahiert. In der Datenbank der Filenamen findet sich ein Offsetwert. In der Datei Config/virtual_file.index/0.idx{.n} findet sich an diesem Offset Informationen zum File. Unter anderem findet man dort die ID des file chunk indexes und einen Offset für diesen Index. Im entsprechenden file chunk index unter Config/file_chunk{ID}.index/0.idx{.n} am Offset stehen dann weitere Offsets für den chunk pool index Pool/chunk_index/0.idx{.n}. Im chunk pool index an den entsprechenden Offsets stehen dann schlussendlich die bucket ID und der bucket index offset für den chunk. Der bucket index befindet sich in Pool/0/0/{ID}.index{.n}. An den bucket index offsets stehen der chunk offset im bucket file, die Grösse des Chunks und für die Dekomprimierung noch die originale Grösse der Daten. Der verschlüsselte Chunk kann dann aus Pool/0/0/{ID}.bucket{.n} extrahiert, entschlüsselt und dekomprimiert werden. Die Integrität eines Chunks kann mittels des MD5-Hash über den entschlüsselten und dekomprimierten Chunkinhalt überprüft werden. Dieser Hashwert ist ebenfalls in den bucket index Einträgen gespeichert.
Es gibt noch einige Stellen im Script in denen Daten gelesen werden, deren Bedeutung sich mir noch nicht erschlossen hat. Vielleicht hat ja jemand von euch noch Spass daran, da noch weiter zu forschen. Ebenfalls offen ist noch, wie die Versionierung funktioniert, da ich meinen Fokus nur auf die Verschlüsselung gelegt habe. Ein weiterer Spezialfall ist das Backup der DSM-Konfiguration. Dazu findet sich zwar einen Eintrag in Config/@Share/@AppConfig zum file config.dss, welcher aber einen negativen Eintrag als Offset für die virtuelle Filetable hat. Ich vermute diese Datei wird verschlüsselt im File Pool/file_pool/1.file{.n} gespeichert, aber ich habe dessen Format noch nicht entziffern können.
Gruss mrsandman
Zuletzt bearbeitet: