EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.store.fs]

COVERAGE SUMMARY FOR SOURCE FILE [FilePathEncrypt.java]

nameclass, %method, %block, %line, %
FilePathEncrypt.java100% (3/3)97%  (36/37)85%  (966/1137)91%  (209/230)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class FilePathEncrypt$XTS100% (1/1)86%  (6/7)63%  (198/314)71%  (40/56)
swap (byte [], int, int, int): void 0%   (0/1)0%   (0/30)0%   (0/5)
encrypt (long, int, byte [], int): void 100% (1/1)50%  (41/82)67%  (10/15)
decrypt (long, int, byte [], int): void 100% (1/1)55%  (55/100)65%  (11/17)
FilePathEncrypt$XTS (BlockCipher): void 100% (1/1)100% (6/6)100% (3/3)
initTweak (long): byte [] 100% (1/1)100% (30/30)100% (5/5)
updateTweak (byte []): void 100% (1/1)100% (46/46)100% (8/8)
xorTweak (byte [], int, byte []): void 100% (1/1)100% (20/20)100% (3/3)
     
class FilePathEncrypt100% (1/1)100% (11/11)87%  (181/208)94%  (33/35)
parse (String): String [] 100% (1/1)60%  (41/68)78%  (7/9)
FilePathEncrypt (): void 100% (1/1)100% (3/3)100% (2/2)
getPasswordBytes (char []): byte [] 100% (1/1)100% (39/39)100% (7/7)
getPrefix (): String 100% (1/1)100% (21/21)100% (2/2)
getScheme (): String 100% (1/1)100% (2/2)100% (1/1)
newInputStream (): InputStream 100% (1/1)100% (8/8)100% (1/1)
newOutputStream (boolean): OutputStream 100% (1/1)100% (8/8)100% (1/1)
open (String): FileChannel 100% (1/1)100% (25/25)100% (4/4)
register (): void 100% (1/1)100% (5/5)100% (2/2)
size (): long 100% (1/1)100% (22/22)100% (5/5)
unwrap (String): FilePath 100% (1/1)100% (7/7)100% (1/1)
     
class FilePathEncrypt$FileEncrypt100% (1/1)100% (19/19)95%  (587/615)98%  (136/139)
readFully (FileChannel, long, ByteBuffer): void 100% (1/1)80%  (16/20)83%  (5/6)
truncate (long): FileChannel 100% (1/1)80%  (49/61)92%  (11/12)
read (ByteBuffer, long): int 100% (1/1)89%  (96/108)95%  (21/22)
<static initializer> 100% (1/1)100% (7/7)100% (2/2)
FilePathEncrypt$FileEncrypt (String, byte [], FileChannel): void 100% (1/1)100% (12/12)100% (5/5)
force (boolean): void 100% (1/1)100% (5/5)100% (2/2)
implCloseChannel (): void 100% (1/1)100% (4/4)100% (2/2)
init (): void 100% (1/1)100% (91/91)100% (20/20)
position (): long 100% (1/1)100% (3/3)100% (1/1)
position (long): FileChannel 100% (1/1)100% (5/5)100% (2/2)
read (ByteBuffer): int 100% (1/1)100% (17/17)100% (4/4)
readInternal (ByteBuffer, long, int): void 100% (1/1)100% (37/37)100% (8/8)
size (): long 100% (1/1)100% (5/5)100% (2/2)
toString (): String 100% (1/1)100% (3/3)100% (1/1)
tryLock (long, long, boolean): FileLock 100% (1/1)100% (7/7)100% (1/1)
write (ByteBuffer): int 100% (1/1)100% (17/17)100% (4/4)
write (ByteBuffer, long): int 100% (1/1)100% (147/147)100% (29/29)
writeFully (FileChannel, long, ByteBuffer): void 100% (1/1)100% (18/18)100% (5/5)
writeInternal (ByteBuffer, long, int): void 100% (1/1)100% (48/48)100% (11/11)

1/*
2 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
3 * and the EPL 1.0 (http://h2database.com/html/license.html).
4 * Initial Developer: H2 Group
5 */
6package org.h2.store.fs;
7 
8import java.io.EOFException;
9import java.io.IOException;
10import java.io.InputStream;
11import java.io.OutputStream;
12import java.nio.ByteBuffer;
13import java.nio.channels.FileChannel;
14import java.nio.channels.FileLock;
15import java.util.Arrays;
16 
17import org.h2.engine.Constants;
18import org.h2.mvstore.DataUtils;
19import org.h2.security.AES;
20import org.h2.security.BlockCipher;
21import org.h2.security.SHA256;
22import org.h2.util.MathUtils;
23 
24/**
25 * An encrypted file.
26 */
27public class FilePathEncrypt extends FilePathWrapper {
28 
29    private static final String SCHEME = "encrypt";
30 
31    /**
32     * Register this file system.
33     */
34    public static void register() {
35        FilePath.register(new FilePathEncrypt());
36    }
37 
38    @Override
39    public FileChannel open(String mode) throws IOException {
40        String[] parsed = parse(name);
41        FileChannel file = FileUtils.open(parsed[1], mode);
42        byte[] passwordBytes = parsed[0].getBytes(Constants.UTF8);
43        return new FileEncrypt(name, passwordBytes, file);
44    }
45 
46    @Override
47    public String getScheme() {
48        return SCHEME;
49    }
50 
51    @Override
52    protected String getPrefix() {
53        String[] parsed = parse(name);
54        return getScheme() + ":" + parsed[0] + ":";
55    }
56 
57    @Override
58    public FilePath unwrap(String fileName) {
59        return FilePath.get(parse(fileName)[1]);
60    }
61 
62    @Override
63    public long size() {
64        long size = getBase().size() - FileEncrypt.HEADER_LENGTH;
65        size = Math.max(0, size);
66        if ((size & FileEncrypt.BLOCK_SIZE_MASK) != 0) {
67            size -= FileEncrypt.BLOCK_SIZE;
68        }
69        return size;
70    }
71 
72    @Override
73    public OutputStream newOutputStream(boolean append) throws IOException {
74        return new FileChannelOutputStream(open("rw"), append);
75    }
76 
77    @Override
78    public InputStream newInputStream() throws IOException {
79        return new FileChannelInputStream(open("r"), true);
80    }
81 
82    /**
83     * Split the file name into algorithm, password, and base file name.
84     *
85     * @param fileName the file name
86     * @return an array with algorithm, password, and base file name
87     */
88    private String[] parse(String fileName) {
89        if (!fileName.startsWith(getScheme())) {
90            throw new IllegalArgumentException(fileName +
91                    " doesn't start with " + getScheme());
92        }
93        fileName = fileName.substring(getScheme().length() + 1);
94        int idx = fileName.indexOf(':');
95        String password;
96        if (idx < 0) {
97            throw new IllegalArgumentException(fileName +
98                    " doesn't contain encryption algorithm and password");
99        }
100        password = fileName.substring(0, idx);
101        fileName = fileName.substring(idx + 1);
102        return new String[] { password, fileName };
103    }
104 
105    /**
106     * Convert a char array to a byte array, in UTF-16 format. The char array is
107     * not cleared after use (this must be done by the caller).
108     *
109     * @param passwordChars the password characters
110     * @return the byte array
111     */
112    public static byte[] getPasswordBytes(char[] passwordChars) {
113        // using UTF-16
114        int len = passwordChars.length;
115        byte[] password = new byte[len * 2];
116        for (int i = 0; i < len; i++) {
117            char c = passwordChars[i];
118            password[i + i] = (byte) (c >>> 8);
119            password[i + i + 1] = (byte) c;
120        }
121        return password;
122    }
123 
124    /**
125     * An encrypted file with a read cache.
126     */
127    public static class FileEncrypt extends FileBase {
128 
129        /**
130         * The block size.
131         */
132        static final int BLOCK_SIZE = 4096;
133 
134        /**
135         * The block size bit mask.
136         */
137        static final int BLOCK_SIZE_MASK = BLOCK_SIZE - 1;
138 
139        /**
140         * The length of the file header. Using a smaller header is possible,
141         * but would mean reads and writes are not aligned to the block size.
142         */
143        static final int HEADER_LENGTH = BLOCK_SIZE;
144 
145        private static final byte[] HEADER = "H2encrypt\n".getBytes();
146        private static final int SALT_POS = HEADER.length;
147 
148        /**
149         * The length of the salt, in bytes.
150         */
151        private static final int SALT_LENGTH = 8;
152 
153        /**
154         * The number of iterations. It is relatively low; a higher value would
155         * slow down opening files on Android too much.
156         */
157        private static final int HASH_ITERATIONS = 10;
158 
159        private final FileChannel base;
160 
161        /**
162         * The current position within the file, from a user perspective.
163         */
164        private long pos;
165 
166        /**
167         * The current file size, from a user perspective.
168         */
169        private long size;
170 
171        private final String name;
172 
173        private XTS xts;
174 
175        private byte[] encryptionKey;
176 
177        public FileEncrypt(String name, byte[] encryptionKey, FileChannel base) {
178            // don't do any read or write operations here, because they could
179            // fail if the file is locked, and we want to give the caller a
180            // chance to lock the file first
181            this.name = name;
182            this.base = base;
183            this.encryptionKey = encryptionKey;
184        }
185 
186        private void init() throws IOException {
187            if (xts != null) {
188                return;
189            }
190            this.size = base.size() - HEADER_LENGTH;
191            boolean newFile = size < 0;
192            byte[] salt;
193            if (newFile) {
194                byte[] header = Arrays.copyOf(HEADER, BLOCK_SIZE);
195                salt = MathUtils.secureRandomBytes(SALT_LENGTH);
196                System.arraycopy(salt, 0, header, SALT_POS, salt.length);
197                DataUtils.writeFully(base, 0, ByteBuffer.wrap(header));
198                size = 0;
199            } else {
200                salt = new byte[SALT_LENGTH];
201                DataUtils.readFully(base, SALT_POS, ByteBuffer.wrap(salt));
202                if ((size & BLOCK_SIZE_MASK) != 0) {
203                    size -= BLOCK_SIZE;
204                }
205            }
206            AES cipher = new AES();
207            cipher.setKey(SHA256.getPBKDF2(
208                    encryptionKey, salt, HASH_ITERATIONS, 16));
209            encryptionKey = null;
210            xts = new XTS(cipher);
211        }
212 
213        @Override
214        protected void implCloseChannel() throws IOException {
215            base.close();
216        }
217 
218        @Override
219        public FileChannel position(long newPosition) throws IOException {
220            this.pos = newPosition;
221            return this;
222        }
223 
224        @Override
225        public long position() throws IOException {
226            return pos;
227        }
228 
229        @Override
230        public int read(ByteBuffer dst) throws IOException {
231            int len = read(dst, pos);
232            if (len > 0) {
233                pos += len;
234            }
235            return len;
236        }
237 
238        @Override
239        public int read(ByteBuffer dst, long position) throws IOException {
240            int len = dst.remaining();
241            if (len == 0) {
242                return 0;
243            }
244            init();
245            len = (int) Math.min(len, size - position);
246            if (position >= size) {
247                return -1;
248            } else if (position < 0) {
249                throw new IllegalArgumentException("pos: " + position);
250            }
251            if ((position & BLOCK_SIZE_MASK) != 0 ||
252                    (len & BLOCK_SIZE_MASK) != 0) {
253                // either the position or the len is unaligned:
254                // read aligned, and then truncate
255                long p = position / BLOCK_SIZE * BLOCK_SIZE;
256                int offset = (int) (position - p);
257                int l = (len + offset + BLOCK_SIZE - 1) / BLOCK_SIZE * BLOCK_SIZE;
258                ByteBuffer temp = ByteBuffer.allocate(l);
259                readInternal(temp, p, l);
260                temp.flip();
261                temp.limit(offset + len);
262                temp.position(offset);
263                dst.put(temp);
264                return len;
265            }
266            readInternal(dst, position, len);
267            return len;
268        }
269 
270        private void readInternal(ByteBuffer dst, long position, int len)
271                throws IOException {
272            int x = dst.position();
273            readFully(base, position + HEADER_LENGTH, dst);
274            long block = position / BLOCK_SIZE;
275            while (len > 0) {
276                xts.decrypt(block++, BLOCK_SIZE, dst.array(), dst.arrayOffset() + x);
277                x += BLOCK_SIZE;
278                len -= BLOCK_SIZE;
279            }
280        }
281 
282        private static void readFully(FileChannel file, long pos, ByteBuffer dst)
283                throws IOException {
284            do {
285                int len = file.read(dst, pos);
286                if (len < 0) {
287                    throw new EOFException();
288                }
289                pos += len;
290            } while (dst.remaining() > 0);
291        }
292 
293        @Override
294        public int write(ByteBuffer src, long position) throws IOException {
295            init();
296            int len = src.remaining();
297            if ((position & BLOCK_SIZE_MASK) != 0 ||
298                    (len & BLOCK_SIZE_MASK) != 0) {
299                // either the position or the len is unaligned:
300                // read aligned, and then truncate
301                long p = position / BLOCK_SIZE * BLOCK_SIZE;
302                int offset = (int) (position - p);
303                int l = (len + offset + BLOCK_SIZE - 1) / BLOCK_SIZE * BLOCK_SIZE;
304                ByteBuffer temp = ByteBuffer.allocate(l);
305                int available = (int) (size - p + BLOCK_SIZE - 1) / BLOCK_SIZE * BLOCK_SIZE;
306                int readLen = Math.min(l, available);
307                if (readLen > 0) {
308                    readInternal(temp, p, readLen);
309                    temp.rewind();
310                }
311                temp.limit(offset + len);
312                temp.position(offset);
313                temp.put(src);
314                temp.limit(l);
315                temp.rewind();
316                writeInternal(temp, p, l);
317                long p2 = position + len;
318                size = Math.max(size, p2);
319                int plus = (int) (size & BLOCK_SIZE_MASK);
320                if (plus > 0) {
321                    temp = ByteBuffer.allocate(plus);
322                    DataUtils.writeFully(base, p + HEADER_LENGTH + l, temp);
323                }
324                return len;
325            }
326            writeInternal(src, position, len);
327            long p2 = position + len;
328            size = Math.max(size, p2);
329            return len;
330        }
331 
332        private void writeInternal(ByteBuffer src, long position, int len)
333                throws IOException {
334            ByteBuffer crypt = ByteBuffer.allocate(len);
335            crypt.put(src);
336            crypt.flip();
337            long block = position / BLOCK_SIZE;
338            int x = 0, l = len;
339            while (l > 0) {
340                xts.encrypt(block++, BLOCK_SIZE, crypt.array(), crypt.arrayOffset() + x);
341                x += BLOCK_SIZE;
342                l -= BLOCK_SIZE;
343            }
344            writeFully(base, position + HEADER_LENGTH, crypt);
345        }
346 
347        private static void writeFully(FileChannel file, long pos,
348                ByteBuffer src) throws IOException {
349            int off = 0;
350            do {
351                int len = file.write(src, pos + off);
352                off += len;
353            } while (src.remaining() > 0);
354        }
355 
356        @Override
357        public int write(ByteBuffer src) throws IOException {
358            int len = write(src, pos);
359            if (len > 0) {
360                pos += len;
361            }
362            return len;
363        }
364 
365        @Override
366        public long size() throws IOException {
367            init();
368            return size;
369        }
370 
371        @Override
372        public FileChannel truncate(long newSize) throws IOException {
373            init();
374            if (newSize > size) {
375                return this;
376            }
377            if (newSize < 0) {
378                throw new IllegalArgumentException("newSize: " + newSize);
379            }
380            int offset = (int) (newSize & BLOCK_SIZE_MASK);
381            if (offset > 0) {
382                base.truncate(newSize + HEADER_LENGTH + BLOCK_SIZE);
383            } else {
384                base.truncate(newSize + HEADER_LENGTH);
385            }
386            this.size = newSize;
387            pos = Math.min(pos, size);
388            return this;
389        }
390 
391        @Override
392        public void force(boolean metaData) throws IOException {
393            base.force(metaData);
394        }
395 
396        @Override
397        public FileLock tryLock(long position, long size, boolean shared)
398                throws IOException {
399            return base.tryLock(position, size, shared);
400        }
401 
402        @Override
403        public String toString() {
404            return name;
405        }
406 
407    }
408 
409    /**
410     * An XTS implementation as described in
411     * IEEE P1619 (Standard Architecture for Encrypted Shared Storage Media).
412     * See also
413     * http://axelkenzo.ru/downloads/1619-2007-NIST-Submission.pdf
414     */
415    static class XTS {
416 
417        /**
418         * Galois field feedback.
419         */
420        private static final int GF_128_FEEDBACK = 0x87;
421 
422        /**
423         * The AES encryption block size.
424         */
425        private static final int CIPHER_BLOCK_SIZE = 16;
426 
427        private final BlockCipher cipher;
428 
429        XTS(BlockCipher cipher) {
430            this.cipher = cipher;
431        }
432 
433        /**
434         * Encrypt the data.
435         *
436         * @param id the (sector) id
437         * @param len the number of bytes
438         * @param data the data
439         * @param offset the offset within the data
440         */
441        void encrypt(long id, int len, byte[] data, int offset) {
442            byte[] tweak = initTweak(id);
443            int i = 0;
444            for (; i + CIPHER_BLOCK_SIZE <= len; i += CIPHER_BLOCK_SIZE) {
445                if (i > 0) {
446                    updateTweak(tweak);
447                }
448                xorTweak(data, i + offset, tweak);
449                cipher.encrypt(data, i + offset, CIPHER_BLOCK_SIZE);
450                xorTweak(data, i + offset, tweak);
451            }
452            if (i < len) {
453                updateTweak(tweak);
454                swap(data, i + offset, i - CIPHER_BLOCK_SIZE + offset, len - i);
455                xorTweak(data, i - CIPHER_BLOCK_SIZE + offset, tweak);
456                cipher.encrypt(data, i - CIPHER_BLOCK_SIZE + offset, CIPHER_BLOCK_SIZE);
457                xorTweak(data, i - CIPHER_BLOCK_SIZE + offset, tweak);
458            }
459        }
460 
461        /**
462         * Decrypt the data.
463         *
464         * @param id the (sector) id
465         * @param len the number of bytes
466         * @param data the data
467         * @param offset the offset within the data
468         */
469        void decrypt(long id, int len, byte[] data, int offset) {
470            byte[] tweak = initTweak(id), tweakEnd = tweak;
471            int i = 0;
472            for (; i + CIPHER_BLOCK_SIZE <= len; i += CIPHER_BLOCK_SIZE) {
473                if (i > 0) {
474                    updateTweak(tweak);
475                    if (i + CIPHER_BLOCK_SIZE + CIPHER_BLOCK_SIZE > len &&
476                            i + CIPHER_BLOCK_SIZE < len) {
477                        tweakEnd = Arrays.copyOf(tweak, CIPHER_BLOCK_SIZE);
478                        updateTweak(tweak);
479                    }
480                }
481                xorTweak(data, i + offset, tweak);
482                cipher.decrypt(data, i + offset, CIPHER_BLOCK_SIZE);
483                xorTweak(data, i + offset, tweak);
484            }
485            if (i < len) {
486                swap(data, i, i - CIPHER_BLOCK_SIZE + offset, len - i + offset);
487                xorTweak(data, i - CIPHER_BLOCK_SIZE  + offset, tweakEnd);
488                cipher.decrypt(data, i - CIPHER_BLOCK_SIZE + offset, CIPHER_BLOCK_SIZE);
489                xorTweak(data, i - CIPHER_BLOCK_SIZE + offset, tweakEnd);
490            }
491        }
492 
493        private byte[] initTweak(long id) {
494            byte[] tweak = new byte[CIPHER_BLOCK_SIZE];
495            for (int j = 0; j < CIPHER_BLOCK_SIZE; j++, id >>>= 8) {
496                tweak[j] = (byte) (id & 0xff);
497            }
498            cipher.encrypt(tweak, 0, CIPHER_BLOCK_SIZE);
499            return tweak;
500        }
501 
502        private static void xorTweak(byte[] data, int pos, byte[] tweak) {
503            for (int i = 0; i < CIPHER_BLOCK_SIZE; i++) {
504                data[pos + i] ^= tweak[i];
505            }
506        }
507 
508        private static void updateTweak(byte[] tweak) {
509            byte ci = 0, co = 0;
510            for (int i = 0; i < CIPHER_BLOCK_SIZE; i++) {
511                co = (byte) ((tweak[i] >> 7) & 1);
512                tweak[i] = (byte) (((tweak[i] << 1) + ci) & 255);
513                ci = co;
514            }
515            if (co != 0) {
516                tweak[0] ^= GF_128_FEEDBACK;
517            }
518        }
519 
520        private static void swap(byte[] data, int source, int target, int len) {
521            for (int i = 0; i < len; i++) {
522                byte temp = data[source + i];
523                data[source + i] = data[target + i];
524                data[target + i] = temp;
525            }
526        }
527 
528    }
529 
530}

[all classes][org.h2.store.fs]
EMMA 2.0.5312 (C) Vladimir Roubtsov