Écrasement de fichiers Java


Lors du développement d'un projet, il est devenu nécessaire de supprimer les fichiers créés par l'application lors de son exécution. Mais il fallait que les fichiers ne soient pas supprimés à la fin de la session, le PC, mais à la demande de l'utilisateur.

Et il semble qu'il n'y ait pas de problème. La bibliothèque standard Java possède une méthode delete () dans la classe java.io.File pour supprimer un fichier.

File file = new File("path/to/file"); if (file.delete()) { System.out.println(file.getName() + " deleted"); } else { System.out.println(file.getName() + " not deleted"); } 

La méthode delete () de la classe java.io.File appelle sous le capot une fonction native pour supprimer un fichier, selon le système d'exploitation. Et les systèmes d'exploitation modernes ne suppriment pas immédiatement le fichier lors de la suppression d'un fichier, mais suppriment uniquement le nom du fichier. Le contenu du fichier reste et la mémoire occupée par le fichier déjà supprimé peut être réutilisée à l'avenir. Mais tout de même, il semble que le fichier déjà supprimé soit disponible depuis un certain temps.

Et si vous regardez Internet, il existe de nombreux programmes pour récupérer des fichiers supprimés, par exemple Recuva .

Mais je veux que les fichiers soient supprimés sans possibilité de les restaurer à l'avenir. Commencer à chercher sur Internet, il s'avère que supprimer le fichier sans restaurer (écraser) n'est pas une tâche très triviale. Et lors de la mise en œuvre d'une telle tâche, il est nécessaire de prendre en compte les fonctionnalités de travail avec des fichiers dans un système d'exploitation particulier. Et dans ce cas, vous devez utiliser une API native écrite manuellement ou une sorte de bibliothèque native.

Depuis que l'application a été développée dans Ubuntu, ce système d'exploitation fournit de nombreuses solutions prêtes à l'emploi sous la forme d'utilitaires de ligne de commande. Par exemple, l'utilitaire de suppression sécurisée, qui vous permet de supprimer des fichiers sans récupération en utilisant différentes approches.

 $ srm -vz private/* 

Les applications doivent vérifier si l'utilitaire est installé et afficher une erreur s'il ne le trouve pas. Mais dans le cas de l'utilisation de l'application pour un autre système d'exploitation, vous devez utiliser un utilitaire similaire.

Ce qui n'est pas très pratique et je veux m'éloigner de ces problèmes. Si vous regardez le code source de l'utilitaire de suppression sécurisée, il vous permet de travailler sous différents systèmes d'exploitation. Il est écrit en C99 et utilise différentes magies de prétraitement et une API spécifique à la plate-forme. Le débogage d'un tel code natif en cas d'erreur est très difficile et c'est la tâche.

Si vous comprenez comment fonctionne l'utilitaire de suppression sécurisée, les étapes suivantes peuvent être distinguées.

  • Tout d'abord, il vérifie si le fichier existe et que les droits sont corrects.
  • selon l'algorithme spécifié, écrase le contenu du fichier.
  • réduit la taille du fichier à zéro octet.
  • renomme le fichier une séquence aléatoire de caractères.
  • supprime le fichier.

La suppression sécurisée permet à différents algorithmes d'écraser le contenu d'un fichier:

  • Algorithme simple - écrase avec 1 passe 0x00 octets.
  • Algorithme DOE - écrase avec 3 passes aléatoires, aléatoires, "DoE".
  • Algorithme RCMP - écrase avec 3 passes 0x00, 0xFF, «RCMP».
  • Algorithme OPENBSD - écrase avec 3 passes 0xFF, 0x00, 0xFF octets.
  • Algorithme DOD - remplace 7 passes.
  • Algorithme Gutmann - Remplace 35 passes.

J'aimerais que le code soit indépendant de la plate-forme et fonctionne sous différents systèmes d'exploitation. Si vous regardez le C ++ moderne, toutes les étapes de la suppression sécurisée pour écraser les fichiers peuvent être effectuées.

Afin de vérifier si un fichier existe et s'il dispose des droits appropriés, vous pouvez utiliser std :: filesystem, qui a été ajouté en C ++ 17.

Pour les versions précédentes de la norme, boost :: filesystem peut être utilisé.

 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; } } 

Pour écraser le contenu du fichier, selon l'algorithme choisi, vous pouvez laisser l'implémentation comme en suppression sécurisée.

 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: /* override OPENBSD_MODE method */ break; case kl::overwrite_mode::RCMP_MODE: /* override RCMP_MODE method */ break; case kl::overwrite_mode::DOD_MODE: /* override DOD_MODE method */ break; case kl::overwrite_mode::GUTMAN_MODE: /* override GUTMAN_MODE method */ break; default: std::cerr << "overwrite mode doesn't choose" << std::endl; } return true; } 

Un tampon d'une certaine taille est rempli d'un certain ensemble de données, selon l'algorithme, et écrit ce tampon dans un fichier jusqu'à la fin.

 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; } 

Réduisez ensuite la taille du fichier à zéro octet, en utilisant la fonction std :: filesystem :: resize_file () pour cela.

 try { fs::resize_file(file, 0); } catch (fs::filesystem_error& e) { env->ThrowNew(exception_class, "truncate file fail"); } 

L'étape suivante consiste à renommer le fichier en une séquence aléatoire de caractères, en utilisant pour cela std :: random () et 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; 

Et au stade final, il vous suffit de supprimer le fichier en utilisant std :: filesystem :: remove () pour cela.

 try { fs::remove(copy_file); } catch (fs::filesystem_error& e) { env->ThrowNew(exception_class, "can't remove file"); } 

Eh bien, pour une utilisation en Java, vous devez déclarer des méthodes natives.

 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; } 

L'implémentation native utilise uniquement la bibliothèque C ++ standard, ce qui facilite le portage vers d'autres plates-formes. Et surtout, il n'y a pas de magie de macro de préprocesseur, ce qui n'est pas si facile à déboguer en cas d'erreur.

Le standard C ++ 17 est déjà pris en charge par tous les compilateurs populaires: MVSC, Clang, GCC.
Le code source complet peut être consulté sur github: code .

Source: https://habr.com/ru/post/fr480256/


All Articles