package org.multibit.file;
import org.multibit.utils.OSUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.security.SecureRandom;
/**
* <p>Utilties to provide the following to applications:</p>
* <ul>
* <li>Access to secure file operations (delete, create with access restrictions etc)</li>
* </ul>
* <p>Uses Java new I/O and Guava Files where possible</p>
*
* @since 0.0.1
*/
public class SecureFiles {
private static final Logger log = LoggerFactory.getLogger(SecureFiles.class);
private static SecureRandom secureRandom = new SecureRandom();
private static boolean initialised = false;
// Nonsense bytes to fill up deleted files - these have no meaning.
static final byte[] NONSENSE_BYTES = new byte[]{(byte) 0xF0, (byte) 0xA6, (byte) 0x55, (byte) 0xAA, (byte) 0x33,
(byte) 0x77, (byte) 0x33, (byte) 0x37, (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78, (byte) 0xC2, (byte) 0xB3,
(byte) 0xA4, (byte) 0x9A, (byte) 0x30, (byte) 0x7F, (byte) 0xE5, (byte) 0x5A, (byte) 0x23, (byte) 0x47, (byte) 0x13,
(byte) 0x17, (byte) 0x15, (byte) 0x32, (byte) 0x5C, (byte) 0x77, (byte) 0xC9, (byte) 0x73, (byte) 0x04, (byte) 0x2D,
(byte) 0x40, (byte) 0x0F, (byte) 0xA5, (byte) 0xA6, (byte) 0x43, (byte) 0x77, (byte) 0x33, (byte) 0x3B, (byte) 0x62,
(byte) 0x34, (byte) 0xB6, (byte) 0x72, (byte) 0x32, (byte) 0xB3, (byte) 0xA4, (byte) 0x4B, (byte) 0x80, (byte) 0x7F,
(byte) 0xC5, (byte) 0x43, (byte) 0x23, (byte) 0x47, (byte) 0x13, (byte) 0xB7, (byte) 0xA5, (byte) 0x32, (byte) 0xDC,
(byte) 0x79, (byte) 0x19, (byte) 0xB1, (byte) 0x03, (byte) 0x9D};
static final int BULKING_UP_FACTOR = 16;
static final byte[] SECURE_DELETE_FILL_BYTES = new byte[NONSENSE_BYTES.length * BULKING_UP_FACTOR];
private static void initialise() {
// Make some SECURE_DELETE_FILL_BYTES bytes = x BULKING_UP_FACTOR the
// NONSENSE just to save write time.
for (int i = 0; i < BULKING_UP_FACTOR; i++) {
System.arraycopy(
NONSENSE_BYTES, 0,
SECURE_DELETE_FILL_BYTES, NONSENSE_BYTES.length * i,
NONSENSE_BYTES.length);
}
initialised = true;
}
/**
* Utilities have private constructor
*/
private SecureFiles() {
}
/**
* Delete a file with an overwrite of all of the data.
* <p/>
* Set bit patterns are used rather than random numbers to avoid a
* futex_wait_queue_me error on Linux systems (related to /dev/random usage)
*
* @param file The file to secure delete
* @throws java.io.IOException if the operation fails for any reason
*/
public static synchronized void secureDelete(File file) throws IOException {
if (!initialised) {
initialise();
}
log.trace("Start of secureDelete");
if (OSUtils.isWindows()) {
// Use slow secure delete
slowSecureDelete(file);
} else {
fastSecureDelete(file);
}
log.trace("End of secureDelete");
}
/**
* Delete a file with an overwrite of all of the data.
* <p/>
* Set bit patterns are used rather than random numbers to avoid a
* futex_wait_queue_me error on Linux systems (related to /dev/random usage)
*
* @param file The file to secure delete
*
* @throws java.io.IOException if the operation fails for any reason
*/
public static synchronized void slowSecureDelete(File file) throws IOException {
if (file != null && file.exists()) {
try (RandomAccessFile raf = new RandomAccessFile(file, "rws")) {
// Prep for file delete as this can be fiddly on Windows
// Make sure it is writable and any references to it are garbage
// collected and finalized.
if (!file.setWritable(true)) {
throw new IOException("Could not write to file " + file.getAbsolutePath());
}
System.gc();
long length = file.length();
raf.seek(0);
raf.getFilePointer();
int pos = 0;
while (pos < length) {
raf.write(SECURE_DELETE_FILL_BYTES);
pos += SECURE_DELETE_FILL_BYTES.length;
}
}
boolean deleteSuccess = file.delete();
log.trace("Result of delete of file '" + file.getAbsolutePath() + "' was " + deleteSuccess);
}
}
/**
* An alternative secure delete algorithm from http://www.cafeaulait.org/books/javaio2/ioexamples/14/SecureDelete.java
*
* @param file the file to secure delete
*/
private static void fastSecureDelete(File file) throws IOException {
if (file != null && file.exists()) {
RandomAccessFile raf = null;
FileChannel channel = null;
MappedByteBuffer buffer;
try {
raf = new RandomAccessFile(file, "rw");
channel = raf.getChannel();
buffer = channel.map(
FileChannel.MapMode.READ_WRITE,
0,
raf.length()
);
// Overwrite with random data; one byte at a time
byte[] data = new byte[1];
while (buffer.hasRemaining()) {
secureRandom.nextBytes(data);
buffer.put(data[0]);
}
// Ensure we push this out to the file system
buffer.force();
channel.close();
} finally {
buffer = null;
if (channel != null) {
channel.close();
channel = null;
}
if (raf != null) {
raf.close();
raf = null;
}
}
// Delete file
// Use JDK7 NIO Files to delete the file since it offers the following benefits:
// * best chance at an atomic operation
// * relies on native code
// * works on Windows
boolean deleteSuccess = Files.deleteIfExists(file.toPath());
log.trace("Result of initial delete was {} for:\n'{}'", deleteSuccess, file.getAbsolutePath());
if (OSUtils.isWindows()) {
// Work around an issue on Windows whereby files are not deleted
File canonical = file.getCanonicalFile();
if (canonical.exists() && !canonical.delete())
log.debug("Failed to delete canonical file {}", file.getCanonicalPath());
}
}
}
}