Bei der Entwicklung eines Projekts wurde es erforderlich, Dateien zu löschen, die von der Anwendung während der Ausführung erstellt wurden. Es war aber erforderlich, dass die Dateien nicht am Ende der Sitzung, sondern auf Wunsch des Benutzers von der Arbeit des PCs gelöscht wurden.
Und es scheint, dass es kein Problem gibt. Die Java-Standardbibliothek verfügt über eine delete () -Methode in der java.io.File-Klasse, um eine Datei zu löschen.
File file = new File("path/to/file"); if (file.delete()) { System.out.println(file.getName() + " deleted"); } else { System.out.println(file.getName() + " not deleted"); }
Die Methode delete () in der Klasse java.io.File ruft abhängig vom Betriebssystem eine native Funktion zum Löschen einer Datei auf. Und moderne Betriebssysteme löschen die Datei beim Löschen einer Datei nicht sofort, sondern nur den Dateinamen. Der Inhalt der Datei bleibt erhalten und der von der bereits gelöschten Datei belegte Speicher kann in Zukunft wiederverwendet werden. Trotzdem scheint es einige Zeit so, als ob die bereits gelöschte Datei verfügbar ist.
Und wenn Sie sich das Internet ansehen, gibt es viele Programme zum Wiederherstellen gelöschter Dateien, beispielsweise
Recuva .
Ich möchte jedoch, dass die Dateien gelöscht werden, ohne dass sie in Zukunft wiederhergestellt werden können. Es hat sich herausgestellt, dass das Löschen der Datei ohne Wiederherstellung (Überschreiben) keine triviale Aufgabe ist. Bei der Implementierung einer solchen Aufgabe müssen die Funktionen der Arbeit mit Dateien in einem bestimmten Betriebssystem berücksichtigt werden. In diesem Fall müssen Sie entweder eine manuell geschriebene native API oder eine Art native Bibliothek verwenden.
Seit die Anwendung in Ubuntu entwickelt wurde, bietet dieses Betriebssystem viele vorgefertigte Lösungen in Form von Befehlszeilendienstprogrammen. Zum Beispiel das Dienstprogramm zum sicheren Löschen, mit dem Sie Dateien ohne Wiederherstellung mit verschiedenen Methoden löschen können.
$ srm -vz private/*
Anwendungen sollten prüfen, ob das Dienstprogramm installiert ist, und einen Fehler anzeigen, wenn es diesen nicht findet. Wenn Sie die Anwendung jedoch für ein anderes Betriebssystem verwenden, müssen Sie ein ähnliches Dienstprogramm verwenden.
Was nicht sehr praktisch ist und ich möchte von diesen Problemen wegkommen. Wenn Sie sich den Quellcode des Dienstprogramms zum sicheren Löschen ansehen, können Sie unter verschiedenen Betriebssystemen arbeiten. Es ist in C99 geschrieben und verwendet unterschiedliche Vorverarbeitungs-Magie und eine plattformspezifische API. Das Debuggen eines solchen nativen Codes im Fehlerfall ist sehr schwierig und das ist die Aufgabe.
Wenn Sie wissen, wie das Dienstprogramm zum sicheren Löschen funktioniert, können die folgenden Schritte unterschieden werden.
- Zunächst wird überprüft, ob die Datei vorhanden ist und ob die Rechte korrekt sind.
- Überschreibt je nach angegebenem Algorithmus den Inhalt der Datei.
- reduziert die Dateigröße auf null Bytes.
- Benennt die Datei in eine zufällige Folge von Zeichen um.
- löscht die Datei.
Mit Secure-Delete können verschiedene Algorithmen den Inhalt einer Datei überschreiben:
- Einfacher Algorithmus - überschreibt mit 1 Durchgang 0x00 Bytes.
- DOE-Algorithmus - Überschreibt mit 3 Durchläufen zufällig, zufällig, "DoE".
- RCMP-Algorithmus - überschreibt mit 3 Durchgängen 0x00, 0xFF, "RCMP".
- OPENBSD-Algorithmus - überschreibt mit 3 Durchgängen 0xFF, 0x00, 0xFF Bytes.
- DOD-Algorithmus - überschreibt 7 Durchgänge.
- Gutmann-Algorithmus - Überschreibt 35 Durchgänge.
Ich möchte, dass der Code plattformunabhängig ist und unter verschiedenen Betriebssystemen funktioniert. Wenn Sie sich modernes C ++ ansehen, können Sie alle Schritte ausführen, die Secure-Delete zum Überschreiben von Dateien ausführt.
Um zu überprüfen, ob eine Datei existiert und ob sie die richtigen Rechte hat, können Sie das in C ++ 17 hinzugefügte std :: Dateisystem verwenden.
Für frühere Versionen des Standards kann das boost :: -Dateisystem verwendet werden.
namespace fs = std::filesystem; if (!fs::exists(file)) { env->ThrowNew(exception_class, "File doesn't exist"); } if (!fs::is_regular_file(file)) { env->ThrowNew(exception_class, "Path doesn't regular file or symlink"); } if (!eraser.check_permision(file, fs::status(file).permissions())) { env->ThrowNew(exception_class, "File hasn't enough permision (maybe not user)"); } bool kl::erase_content::check_permision(const fs::path& entry, fs::perms permision) { try { fs::permissions(entry, fs::perms::owner_read | fs::perms::owner_write, fs::perm_options::add); return true; } catch (fs::filesystem_error& e) { return false; } }
Um den Inhalt der Datei abhängig vom gewählten Algorithmus zu überschreiben, können Sie die Implementierung wie beim sicheren Löschen belassen.
bool kl::erase_content::overwrite() { switch (entry.get_mode()) { case kl::overwrite_mode::SIMPLE_MODE: if (!overwrite_byte(1, 0x00)) { return false; } break; case kl::overwrite_mode::DOE_MODE: if (!overwrite_random(1)) { return false; } if (!overwrite_random(2)) { return false; } if (!overwrite_bytes(3, "DoE")) { return false; } break; case kl::overwrite_mode::OPENBSD_MODE: break; case kl::overwrite_mode::RCMP_MODE: break; case kl::overwrite_mode::DOD_MODE: break; case kl::overwrite_mode::GUTMAN_MODE: break; default: std::cerr << "overwrite mode doesn't choose" << std::endl; } return true; }
Ein Puffer einer bestimmten Größe wird abhängig vom Algorithmus mit einem bestimmten Datensatz gefüllt und schreibt diesen Puffer in eine Datei, bis er das Ende erreicht.
bool kl::erase_content::overwrite_byte(const int pass, const uint8_t byte) { const auto& [file_name, file_size, buffer_size, mode] = entry; this->buffer = std::make_unique<uint8_t[]>(buffer_size); std::memset(buffer.get(), byte, buffer_size); this->file = kl::fs_util::make_open_file(file_name, "r+b"); if (!overwrite_data(pass)) { return false; } return true; } bool kl::erase_content::overwrite_data(const int pass) { const auto& [file_name, file_size, buffer_size, mode] = entry; const size_t count = file_size / buffer_size; const size_t tail = file_size % buffer_size; size_t writted = 0; if (fseek(file.get(), 0, SEEK_SET) != 0) { std::cerr << "couldn't seek in file" << std::endl; return false; } writted = write_buffer(count, tail); if (writted != file_size) { std::cerr << "couldn't write buffer in file" << std::endl; return false; } fflush(file.get()); if (fseek(file.get(), 0, SEEK_SET) != 0) { std::cerr << "couldn't seek in file" << std::endl; return false; } file.reset(); return true; }
Reduzieren Sie anschließend die Dateigröße mit der Funktion std :: filesystem :: resize_file () auf null Byte.
try { fs::resize_file(file, 0); } catch (fs::filesystem_error& e) { env->ThrowNew(exception_class, "truncate file fail"); }
Der nächste Schritt besteht darin, die Datei in eine zufällige Zeichenfolge umzubenennen. Verwenden Sie dazu std :: random () und std :: filesystem :: file :: replace_filename ().
std::string parent_path = file.parent_path(); std::string file_name = file.filename(); fs::path copy_file = file; file_name = random_text(file_name.size()); copy_file.replace_filename(fs::path(file_name)); try { fs::rename(file, copy_file); } catch (fs::filesystem_error& e) { env->ThrowNew(exception_class, "can't rename file"); } return true;
Und im letzten Schritt müssen Sie nur die Datei mit std :: filesystem :: remove () löschen.
try { fs::remove(copy_file); } catch (fs::filesystem_error& e) { env->ThrowNew(exception_class, "can't remove file"); }
Nun, für die Verwendung in Java müssen Sie native Methoden deklarieren.
public class EraseFS { static { System.loadLibrary("jefl"); } public static native boolean eraseFile(String path) throws EraseException; public static native boolean eraseFile(String path, OverwrideMode mode) throws EraseException; public static native boolean eraseFiles(String... paths) throws EraseException; public static native boolean eraseFiles(OverwrideMode mode, String... paths) throws EraseException; public static native boolean eraseDirectory(String path, boolean recursived) throws EraseException; public static native boolean eraseDirectory(String path, OverwrideMode mode, boolean recursived) throws EraseException; }
Die native Implementierung verwendet nur die Standard-C ++ - Bibliothek, wodurch die Portierung auf andere Plattformen vereinfacht wird. Und vor allem gibt es keine Präprozessor-Makromagie, die im Fehlerfall nicht so einfach zu debuggen ist.
Der Standard C ++ 17 wird bereits von allen gängigen Compilern unterstützt: MVSC, Clang, GCC.
Der vollständige Quellcode kann auf github:
code eingesehen
werden .