Al desarrollar un proyecto, se hizo necesario eliminar los archivos creados por la aplicación durante su ejecución. Pero se requería que los archivos no se eliminaran al final de la sesión, el trabajo de la PC, sino a petición del usuario.
Y parece que no hay problema. La biblioteca estándar de Java tiene un método delete () en la clase java.io.File para eliminar un archivo.
File file = new File("path/to/file"); if (file.delete()) { System.out.println(file.getName() + " deleted"); } else { System.out.println(file.getName() + " not deleted"); }
El método delete () en la clase java.io.File llama bajo el capó una función nativa para eliminar un archivo, dependiendo del sistema operativo. Y los sistemas operativos modernos no eliminan inmediatamente el archivo al eliminar un archivo, sino que solo eliminan el nombre del archivo. El contenido del archivo permanece y la memoria ocupada por el archivo ya eliminado puede reutilizarse en el futuro. Pero de todos modos, durante algún tiempo parece que el archivo ya eliminado está disponible.
Y si nos fijamos en Internet, hay muchos programas para recuperar archivos borrados, por ejemplo,
Recuva .
Pero quiero que los archivos se eliminen sin la posibilidad de restaurarlos en el futuro. Comenzar a buscar en Internet, resulta que eliminar el archivo sin restaurar (sobrescribir) no es una tarea muy trivial. Y al implementar dicha tarea, es necesario tener en cuenta las características de trabajar con archivos en un sistema operativo particular. Y en este caso, debe usar una API nativa escrita manualmente o algún tipo de biblioteca nativa.
Dado que la aplicación se desarrolló en Ubuntu, este sistema operativo ofrece muchas soluciones listas para usar en forma de utilidades de línea de comandos. Por ejemplo, la utilidad de eliminación segura, que le permite eliminar archivos sin recuperación utilizando diferentes enfoques.
$ srm -vz private/*
Las aplicaciones deben verificar si la utilidad está instalada y mostrar un error si no la encuentra. Pero en el caso de usar la aplicación para otro sistema operativo, debe usar una utilidad similar.
Lo cual no es muy conveniente y quiero alejarme de estos problemas. Si observa el código fuente de la utilidad de eliminación segura, le permite trabajar en diferentes sistemas operativos. Está escrito en C99 y utiliza diferentes magias de preprocesamiento y una API específica de la plataforma. La depuración de dicho código nativo en caso de error es muy difícil y esa es la tarea.
Si comprende cómo funciona la utilidad de eliminación segura, se pueden distinguir los siguientes pasos.
- Primero, verifica si el archivo existe y si los derechos son correctos.
- dependiendo del algoritmo especificado, sobrescribe el contenido del archivo.
- reduce el tamaño del archivo a cero bytes.
- cambia el nombre del archivo a una secuencia aleatoria de caracteres.
- elimina el archivo
Secure-Delete permite que diferentes algoritmos sobrescriban el contenido de un archivo:
- Algoritmo simple: se sobrescribe con 1 pasada de 0x00 bytes.
- Algoritmo DOE: se sobrescribe con 3 pases aleatorios, aleatorios, "DoE".
- Algoritmo RCMP: se sobrescribe con 3 pasadas 0x00, 0xFF, "RCMP".
- Algoritmo OPENBSD: se sobrescribe con 3 pases 0xFF, 0x00, 0xFF bytes.
- Algoritmo DOD: sobrescribe 7 pases.
- Algoritmo de Gutmann: sobrescribe 35 pases.
Me gustaría que el código sea independiente de la plataforma y funcione bajo diferentes sistemas operativos. Si observa C ++ moderno, se pueden realizar todos los pasos que hace la eliminación segura para sobrescribir archivos.
Para verificar si existe un archivo y si tiene los derechos correctos, puede usar std :: filesystem, que se agregó en C ++ 17.
Para versiones anteriores del estándar, se puede utilizar boost :: filesystem.
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; } }
Para sobrescribir el contenido del archivo, según el algoritmo elegido, puede dejar la implementación como en Secure-Delete.
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; }
Un búfer de cierto tamaño se llena con un determinado conjunto de datos, según el algoritmo, y escribe este búfer en un archivo hasta que llega al final.
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; }
Luego reduzca el tamaño del archivo a cero bytes, utilizando la función std :: filesystem :: resize_file () para esto.
try { fs::resize_file(file, 0); } catch (fs::filesystem_error& e) { env->ThrowNew(exception_class, "truncate file fail"); }
El siguiente paso es cambiar el nombre del archivo a una secuencia aleatoria de caracteres, usando std :: random () y std :: filesystem :: file :: replace_filename () para esto.
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;
Y en la etapa final, solo necesita eliminar el archivo usando std :: filesystem :: remove () para esto.
try { fs::remove(copy_file); } catch (fs::filesystem_error& e) { env->ThrowNew(exception_class, "can't remove file"); }
Bueno, para usar en Java necesitas declarar métodos nativos.
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; }
La implementación nativa usa solo la biblioteca estándar de C ++, lo que facilita el puerto a otras plataformas. Y lo más importante, no hay macroprocesador de preprocesador, que no es tan fácil de depurar en caso de errores.
El estándar C ++ 17 ya es compatible con todos los compiladores populares: MVSC, Clang, GCC.
El código fuente completo se puede ver en github:
code .