提交 f6a13699 authored 作者: Thomas Mueller's avatar Thomas Mueller

A new encrypting file system has been implemented.

上级 8b34bde2
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.unit;
import java.io.IOException;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileSystem;
import org.h2.store.fs.FileSystemWrapper;
/**
* This file system encrypts the data.
*/
public class FileSystemCrypt extends FileSystemWrapper {
private static final String PREFIX = "aes:";
private static final int HEADER_LENGTH = 4096;
static {
FileSystem.register(new FileSystemCrypt());
}
protected String getPrefix() {
return PREFIX;
}
public long length(String fileName) {
return super.length(fileName) - HEADER_LENGTH;
}
public FileObject openFileObject(String fileName, String mode) {
return null;
}
/**
* An encrypted file.
*/
static class FileObjectCrypt implements FileObject {
// private FileObject
public void close() throws IOException {
// TODO Auto-generated method stub
}
public long getFilePointer() throws IOException {
// TODO Auto-generated method stub
return 0;
}
public String getName() {
// TODO Auto-generated method stub
return null;
}
public long length() throws IOException {
// TODO Auto-generated method stub
return 0;
}
public void readFully(byte[] b, int off, int len) throws IOException {
// TODO Auto-generated method stub
}
public void releaseLock() {
// TODO Auto-generated method stub
}
public void seek(long pos) throws IOException {
// TODO Auto-generated method stub
}
public void setFileLength(long newLength) throws IOException {
// TODO Auto-generated method stub
}
public void sync() throws IOException {
// TODO Auto-generated method stub
}
public boolean tryLock() {
// TODO Auto-generated method stub
return false;
}
public void write(byte[] b, int off, int len) throws IOException {
// TODO Auto-generated method stub
}
}
}
......@@ -18,6 +18,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Random;
import org.h2.dev.fs.FileSystemCrypt;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileSystem;
import org.h2.store.fs.FileSystemMemory;
......@@ -58,10 +59,12 @@ public class TestFileSystem extends TestBase {
testUserHome();
fs.unregister();
try {
FileSystemCrypt.register();
testFileSystem("crypt:aes:x:" + getBaseDir() + "/fs");
testFileSystem("nio:" + getBaseDir() + "/fs");
testFileSystem("nioMapped:" + getBaseDir() + "/fs");
if (!config.splitFileSystem) {
testFileSystem("split:" + getBaseDir() + "/fs");
testFileSystem("nio:" + getBaseDir() + "/fs");
testFileSystem("nioMapped:" + getBaseDir() + "/fs");
testFileSystem("split:nioMapped:" + getBaseDir() + "/fs");
}
} catch (Exception e) {
......@@ -192,6 +195,7 @@ public class TestFileSystem extends TestBase {
Random random = new Random(1);
random.nextBytes(buffer);
fo.write(buffer, 0, 10000);
assertEquals(10000, fo.length());
fo.seek(20000);
assertEquals(20000, fo.getFilePointer());
try {
......@@ -205,7 +209,9 @@ public class TestFileSystem extends TestBase {
assertEquals(fsBase, fs.getParent(fo.getName()).replace('\\', '/'));
fo.tryLock();
fo.releaseLock();
assertEquals(10000, fo.length());
fo.close();
assertEquals(10000, fs.length(fsBase + "/test"));
fo = fs.openFileObject(fsBase + "/test", "r");
byte[] test = new byte[10000];
fo.readFully(test, 0, 10000);
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.dev.fs;
import java.io.EOFException;
import java.io.IOException;
import org.h2.engine.Constants;
import org.h2.security.BlockCipher;
import org.h2.security.CipherFactory;
import org.h2.security.SHA256;
import org.h2.store.fs.FileObject;
import org.h2.util.MathUtils;
import org.h2.util.StringUtils;
/**
* An encrypted file.
*/
public class FileObjectCrypt implements FileObject {
static final int HEADER_LENGTH = 4096;
// TODO header
private static final byte[] HEADER = "-- H2 crypt --\n\0".getBytes();
private static final int SALT_POS = HEADER.length;
private static final int SALT_LENGTH = 16;
private static final int HASH_ITERATIONS = Constants.ENCRYPTION_KEY_HASH_ITERATIONS;
private static final int BLOCK_SIZE = Constants.FILE_BLOCK_SIZE;
private final String name;
private final FileObject file;
private final BlockCipher cipher, cipherForInitVector;
private byte[] bufferForInitVector;
public FileObjectCrypt(String name, String algorithm, String password, FileObject file) throws IOException {
this.name = name;
this.file = file;
boolean newFile = file.length() < 2 * HEADER_LENGTH;
byte[] filePasswordHash;
if (algorithm.endsWith("-hash")) {
filePasswordHash = StringUtils.convertStringToBytes(password);
algorithm = algorithm.substring(0, algorithm.length() - "-hash".length());
} else {
filePasswordHash = SHA256.getKeyPasswordHash("file", password.toCharArray());
}
cipher = CipherFactory.getBlockCipher(algorithm);
cipherForInitVector = CipherFactory.getBlockCipher(algorithm);
int keyIterations = HASH_ITERATIONS;
byte[] salt;
if (newFile) {
salt = MathUtils.secureRandomBytes(SALT_LENGTH);
file.write(HEADER, 0, HEADER.length);
file.seek(SALT_POS);
file.write(salt, 0, salt.length);
} else {
salt = new byte[SALT_LENGTH];
file.seek(SALT_POS);
file.readFully(salt, 0, SALT_LENGTH);
}
byte[] key = SHA256.getHashWithSalt(filePasswordHash, salt);
for (int i = 0; i < keyIterations; i++) {
key = SHA256.getHash(key, true);
}
cipher.setKey(key);
bufferForInitVector = new byte[BLOCK_SIZE];
seek(0);
}
public long getFilePointer() throws IOException {
return Math.max(0, file.getFilePointer() - HEADER_LENGTH);
}
public String getName() {
return name;
}
public long length() throws IOException {
return Math.max(0, file.length() - 2 * HEADER_LENGTH);
}
public void releaseLock() {
file.releaseLock();
}
public void seek(long pos) throws IOException {
file.seek(pos + HEADER_LENGTH);
}
public void sync() throws IOException {
file.sync();
}
public boolean tryLock() {
return file.tryLock();
}
public void close() throws IOException {
file.close();
}
public void setFileLength(long newLength) throws IOException {
if (newLength < length()) {
int mod = (int) (newLength % BLOCK_SIZE);
if (mod == 0) {
file.setFileLength(newLength + HEADER_LENGTH);
} else {
file.setFileLength(newLength + HEADER_LENGTH + BLOCK_SIZE - mod);
byte[] buff = new byte[BLOCK_SIZE - mod];
long pos = getFilePointer();
seek(newLength);
write(buff, 0, buff.length);
seek(pos);
}
}
file.setFileLength(newLength + 2 * HEADER_LENGTH);
if (newLength < getFilePointer()) {
seek(newLength);
}
}
public void readFully(byte[] b, int off, int len) throws IOException {
long pos = getFilePointer();
long length = length();
if (pos + len > length) {
throw new EOFException("pos: " + pos + " len: " + len + " length: " + length);
}
int posMod = (int) (pos % BLOCK_SIZE);
int lenMod = len % BLOCK_SIZE;
if (posMod == 0 && lenMod == 0) {
readAligned(pos, b, off, len);
} else {
long p = pos - posMod;
int l = len + 2 * BLOCK_SIZE - lenMod;
seek(p);
byte[] temp = new byte[l];
try {
readAligned(p, temp, 0, l);
System.arraycopy(temp, posMod, b, off, len);
} finally {
seek(pos + len);
}
}
}
public void write(byte[] b, int off, int len) throws IOException {
long pos = getFilePointer();
int posMod = (int) (pos % BLOCK_SIZE);
int lenMod = len % BLOCK_SIZE;
if (posMod == 0 && lenMod == 0) {
byte[] temp = new byte[len];
System.arraycopy(b, off, temp, 0, len);
writeAligned(pos, temp, 0, len);
} else {
long p = pos - posMod;
int l = len + 2 * BLOCK_SIZE - lenMod;
seek(p);
byte[] temp = new byte[l];
if (file.length() < pos + l + HEADER_LENGTH) {
file.setFileLength(pos + l + HEADER_LENGTH);
}
readAligned(p, temp, 0, l);
System.arraycopy(b, off, temp, posMod, len);
seek(p);
try {
writeAligned(p, temp, 0, l);
} finally {
seek(pos + len);
}
}
pos = file.getFilePointer();
if (file.length() < pos + HEADER_LENGTH) {
file.setFileLength(pos + HEADER_LENGTH);
}
}
private void readAligned(long pos, byte[] b, int off, int len) throws IOException {
file.readFully(b, off, len);
for (int p = 0; p < len; p += BLOCK_SIZE) {
for (int i = 0; i < BLOCK_SIZE; i++) {
// currently, empty blocks are not decrypted
if (b[p + off + i] != 0) {
cipher.decrypt(b, p + off, BLOCK_SIZE);
xorInitVector(b, p + off, BLOCK_SIZE, p + pos);
break;
}
}
}
}
private void writeAligned(long pos, byte[] b, int off, int len) throws IOException {
for (int p = 0; p < len; p += BLOCK_SIZE) {
for (int i = 0; i < BLOCK_SIZE; i++) {
// currently, empty blocks are not decrypted
if (b[p + off + i] != 0) {
xorInitVector(b, p + off, BLOCK_SIZE, p + pos);
cipher.encrypt(b, p + off, BLOCK_SIZE);
break;
}
}
}
file.write(b, off, len);
}
private void xorInitVector(byte[] b, int off, int len, long p) {
byte[] iv = bufferForInitVector;
while (len > 0) {
for (int i = 0; i < BLOCK_SIZE; i += 8) {
long block = (p + i) >>> 3;
iv[i] = (byte) (block >> 56);
iv[i + 1] = (byte) (block >> 48);
iv[i + 2] = (byte) (block >> 40);
iv[i + 3] = (byte) (block >> 32);
iv[i + 4] = (byte) (block >> 24);
iv[i + 5] = (byte) (block >> 16);
iv[i + 6] = (byte) (block >> 8);
iv[i + 7] = (byte) block;
}
cipherForInitVector.encrypt(iv, 0, BLOCK_SIZE);
for (int i = 0; i < BLOCK_SIZE; i++) {
b[off + i] ^= iv[i];
}
p += BLOCK_SIZE;
off += BLOCK_SIZE;
len -= BLOCK_SIZE;
}
}
}
差异被折叠。
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.dev.fs;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.h2.message.DbException;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileObjectInputStream;
import org.h2.store.fs.FileObjectOutputStream;
import org.h2.store.fs.FileSystem;
import org.h2.store.fs.FileSystemWrapper;
import org.h2.util.IOUtils;
/**
* A file system that encrypts the contents of the files.
*/
public class FileSystemCrypt extends FileSystemWrapper {
/**
* The prefix to use for this file system.
*/
public static final String PREFIX = "crypt:";
private static final FileSystemCrypt INSTANCE = new FileSystemCrypt();
protected String getPrefix() {
return PREFIX;
}
/**
* Register the file system.
*
* @return the instance
*/
public static FileSystemCrypt register() {
FileSystem.register(INSTANCE);
return INSTANCE;
}
public long length(String fileName) {
long len = super.length(fileName);
return Math.max(0, len - 2 * FileObjectCrypt.HEADER_LENGTH);
}
public FileObject openFileObject(String fileName, String mode) throws IOException {
String[] parsed = parse(fileName);
FileObject file = IOUtils.openFileObject(parsed[2], mode);
return new FileObjectCrypt(fileName, parsed[0], parsed[1], file);
}
public OutputStream openFileOutputStream(String fileName, boolean append) {
try {
FileObject file = openFileObject(fileName, "rw");
return new FileObjectOutputStream(file, append);
} catch (IOException e) {
throw DbException.convertIOException(e, fileName);
}
}
public InputStream openFileInputStream(String fileName) {
try {
FileObject file = openFileObject(fileName, "r");
return new FileObjectInputStream(file);
} catch (IOException e) {
throw DbException.convertIOException(e, fileName);
}
}
public String getParent(String fileName) {
String[] parsed = parse(fileName);
return combine(parsed[0], parsed[1], IOUtils.getParent(parsed[2]));
}
public String[] listFiles(String directory) {
String[] parsed = parse(directory);
String[] array = IOUtils.listFiles(parsed[2]);
for (int i = 0; i < array.length; i++) {
array[i] = combine(parsed[0], parsed[1], array[i]);
}
return array;
}
public String getCanonicalPath(String fileName) {
String[] parsed = parse(fileName);
return combine(parsed[0], parsed[1], IOUtils.getCanonicalPath(parsed[2]));
}
public String unwrap(String fileName) {
return parse(fileName)[2];
}
public String createTempFile(String prefix, String suffix, boolean deleteOnExit, boolean inTempDir)
throws IOException {
String[] parsed = parse(prefix);
return combine(parsed[0], parsed[1], IOUtils.createTempFile(parsed[2], suffix, deleteOnExit, inTempDir));
}
/**
* Combine the parameters into a file name.
*
* @param algorithm the encryption algorithm
* @param password the password
* @param fileName the base file name
* @return the combined file name
*/
private String combine(String algorithm, String password, String fileName) {
return PREFIX + algorithm + ":" + password + ":" + fileName;
}
/**
* Split the file name into algorithm, password, and base file name.
*
* @param fileName the file name
* @return an array with algorithm, password, and base file name
*/
private String[] parse(String fileName) {
if (!fileName.startsWith(PREFIX)) {
DbException.throwInternalError(fileName + " doesn't start with " + PREFIX);
}
fileName = fileName.substring(PREFIX.length());
int idx = fileName.indexOf(':');
String algorithm, password;
if (idx < 0) {
DbException.throwInternalError(fileName + " doesn't contain encryption algorithm and password");
}
algorithm = fileName.substring(0, idx);
fileName = fileName.substring(idx + 1);
idx = fileName.indexOf(':');
if (idx < 0) {
DbException.throwInternalError(fileName + " doesn't contain encryption password");
}
password = fileName.substring(0, idx);
fileName = fileName.substring(idx + 1);
return new String[] { algorithm, password, fileName };
}
}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!--
Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, Version 1.0,
and under the Eclipse Public License, Version 1.0
(http://h2database.com/html/license.html).
Initial Developer: H2 Group
-->
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /><title>
Javadoc package documentation
</title></head><body style="font: 9pt/130% Tahoma, Arial, Helvetica, sans-serif; font-weight: normal;"><p>
An encrypting file system.
</p></body></html>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论