KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > de > schlichtherle > crypto > io > CipherReadOnlyFile


1 /*
2  * CipherReadOnlyFile.java
3  *
4  * Created on 30. Maerz 2006, 16:04
5  */

6 /*
7  * Copyright 2006 Schlichtherle IT Services
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  * http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  */

21
22 package de.schlichtherle.crypto.io;
23
24 import de.schlichtherle.crypto.SeekableBlockCipher;
25 import de.schlichtherle.io.rof.FilterReadOnlyFile;
26 import de.schlichtherle.io.rof.ReadOnlyFile;
27
28 import java.io.IOException JavaDoc;
29
30 import org.bouncycastle.crypto.Mac;
31
32 /**
33  * A read only file for transparent random read access to an encrypted file.
34  * <p>
35  * The client must call {@link #init(SeekableBlockCipher, long, long)}
36  * before it can actually read anything!
37  * <p>
38  * Note that this class implements its own virtual file pointer.
39  * Thus, if you would like to access the underlying <code>ReadOnlyFile</code>
40  * again after you have finished working with an instance of this class,
41  * you should synchronize their file pointers using the pattern as described
42  * in the base class {@link FilterReadOnlyFile}.
43  *
44  * @author Christian Schlichtherle
45  * @version @version@
46  * @since TrueZIP 6.0
47  */

48 //
49
// Tactical notes:
50
//
51
// In order to provide optimum performance, this class implements a read ahead
52
// strategy with lazy decryption.
53
// So the encrypted data of the file is read ahead into an internal window
54
// buffer in order to minimize file access.
55
// Upon request by the application only, this buffer is then decrypted block by
56
// block into the buffer provided by the application.
57
//
58
// For similar reasons, this class is NOT a subclass of
59
// BufferedReadOnlyFile though their algorithm and code is pretty similar.
60
// In fact, this class uses an important performance optimization:
61
// Whenever possible, encrypted data in the window buffer is directly
62
// decrypted into the user provided buffer.
63
// If BufferedReadOnlyFile would be used as the base class instead, we would
64
// have to provide another buffer to copy the data into before we could
65
// actually decrypt it, which is redundant.
66
//
67
public abstract class CipherReadOnlyFile extends FilterReadOnlyFile {
68     
69     /**
70      * The maximum buffer length of the window to the encrypted file.
71      * This value has been adjusted to provide optimum performance at minimal
72      * size on a Windows XP computer - results may vary.
73      * Note that the <em>actual</em> size of the window is a multiple of the
74      * cipher's block size and may be smaller than the maximum window size.
75      */

76     private static final int MAX_WINDOW_LEN = 1024;
77
78     /** Returns the smaller parameter. */
79     private static final long min(long a, long b) {
80         return a < b ? a : b;
81     }
82
83     /** Returns the greater parameter. */
84     private static final long max(long a, long b) {
85         return a < b ? b : a;
86     }
87
88     /** Start offset of the encrypted data. */
89     private long start;
90
91     /** The length of the encrypted data. */
92     private long length;
93
94     /**
95      * The virtual file pointer in the encrypted data.
96      * This is relative to the start.
97      */

98     private long fp;
99
100     /**
101      * The current offset in the encrypted file where the buffer window starts.
102      * This is always a multiple of the block size.
103      */

104     private long windowOff;
105
106     /**
107      * The buffer window to the encrypted file.
108      * Note that this buffer contains encrypted data only.
109      * The actual size of the window is a multiple of the cipher's block size
110      * and may be slightly smaller than {@link #MAX_WINDOW_LEN}.
111      */

112     private byte[] window;
113
114     /** The seekable block cipher which allows random access. */
115     private SeekableBlockCipher cipher;
116
117     /**
118      * The current offset in the encrypted file where the data starts that
119      * has been decrypted to the block.
120      * This is always a multiple of the block size.
121      */

122     private long blockOff;
123
124     /**
125      * The block buffer to use for decryption of partial blocks.
126      * Note that this buffer contains decrypted data only.
127      */

128     private byte[] block;
129
130     /** Whether this read only file has been closed or not. */
131     private boolean closed;
132
133     /**
134      * Creates a read only file for transparent random read access to an
135      * encrypted file.
136      * The client must call {@link #init(SeekableBlockCipher, long, long)}
137      * before it can actually read anything!
138      *
139      * @param rof A read-only file.
140      * This may be <code>null</code>, but must be properly init before
141      * the call to <code>init()</code>.
142      */

143     public CipherReadOnlyFile(ReadOnlyFile rof) {
144         super(rof);
145     }
146
147     /**
148      * Initializes this cipher read only file - must be called before first
149      * read access!
150      *
151      * @param start The start offset of the encrypted data in this file.
152      * @param length The length of the encrypted data in this file.
153      *
154      * @throws IOException If this read only file has already been closed.
155      * This exception is <em>not</em> recoverable.
156      * @throws IllegalStateException If this object has already been
157      * initialized.
158      * This exception is <em>not</em> recoverable.
159      * @throws NullPointerException If {@link #rof} is <tt>null</tt>
160      * or <tt>cipher</tt> is <tt>null</tt>.
161      * This exception <em>is</em> recoverable.
162      */

163     public void init(
164             final SeekableBlockCipher cipher,
165             final long start,
166             final long length)
167     throws IOException JavaDoc {
168         // Check state.
169
if (closed)
170             throw new IOException JavaDoc("CipherReadOnlyFile has already been closed!");
171         if (this.cipher != null)
172             throw new IllegalStateException JavaDoc("CipherReadOnlyFile has already been initialized!");
173
174         // Check state (recoverable).
175
if (rof == null)
176             throw new NullPointerException JavaDoc("rof");
177
178         // Check parameters (fail fast).
179
if (cipher == null)
180             throw new NullPointerException JavaDoc("cipher");
181         if (start < 0 || length < 0)
182             throw new IllegalArgumentException JavaDoc();
183
184         this.cipher = cipher;
185         this.start = start;
186         this.length = length;
187
188         blockOff = length;
189         final int blockLen = cipher.getBlockSize();
190         block = new byte[blockLen];
191         windowOff = Long.MIN_VALUE; // invalidate window
192
window = new byte[(MAX_WINDOW_LEN / blockLen) * blockLen]; // round down to multiple of block size
193

194         assert fp == 0;
195         assert block.length > 0;
196         assert window.length > 0;
197         assert window.length % block.length == 0;
198         assert cipher != null;
199     }
200
201     /**
202      * Returns the authentication code of the encrypted data in this cipher
203      * read only file using the given Message Authentication Code (MAC) object.
204      * It is safe to call this method multiple times to detect if the file
205      * has been tampered with meanwhile.
206      *
207      * @param mac A properly initialized MAC object.
208      *
209      * @throws IOException On any I/O related issue.
210      */

211     protected byte[] computeMac(final Mac mac)
212     throws IOException JavaDoc {
213         final int windowLen = window.length;
214         final byte[] buf = new byte[mac.getMacSize()];
215
216         final long safedFp = getFilePointer();
217         try {
218             for (fp = 0; fp < length; fp += windowLen) {
219                 positionWindow();
220                 final long remaining = length - windowOff;
221                 mac.update(window, 0, (int) min(windowLen, remaining));
222             }
223             final int bufLen = mac.doFinal(buf, 0);
224             assert bufLen == buf.length;
225         } finally {
226             fp = safedFp;
227         }
228
229         return buf;
230     }
231
232     public long length() throws IOException JavaDoc {
233         ensureInit();
234
235         return length;
236     }
237
238     public long getFilePointer() throws IOException JavaDoc {
239         ensureInit();
240
241         return fp;
242     }
243
244     public void seek(final long fp) throws IOException JavaDoc {
245         if (fp < 0)
246             throw new IOException JavaDoc("File pointer must not be negative!");
247
248         ensureInit();
249
250         if (fp > length)
251             throw new IOException JavaDoc("File pointer (" + fp
252                     + ") is larger than file length (" + length + ")!");
253
254         this.fp = fp;
255     }
256
257     public int read() throws IOException JavaDoc {
258         // Check state.
259
ensureInit();
260         if (fp >= length)
261             return -1;
262
263         // Position block and return its decrypted data.
264
positionBlock();
265         return block[(int) (fp++ % block.length)];
266     }
267
268     public int read(final byte[] buf, final int off, final int len)
269     throws IOException JavaDoc {
270         // Check parameters.
271
if (buf == null)
272             throw new NullPointerException JavaDoc("buf");
273         final int offPlusLen = off + len;
274         if ((off | len | offPlusLen | buf.length - offPlusLen) < 0)
275         throw new IndexOutOfBoundsException JavaDoc();
276         if (len == 0)
277             return 0; // be fault-tolerant and compatible to RandomAccessFile
278

279         // Check state.
280
ensureInit();
281         if (fp >= length)
282             return -1;
283
284         // Setup.
285
final int blockLen = block.length;
286         int read = 0; // amount of decrypted data copied to buf
287

288         {
289             // Partial read of decrypted data block at the start.
290
final int o = (int) (fp % blockLen);
291             if (o != 0) {
292                 // The file pointer is not on a block boundary.
293
positionBlock();
294                 read = (int) min(len, blockLen - o);
295                 read = (int) min(read, length - fp);
296                 System.arraycopy(block, o, buf, off, read);
297                 fp += read;
298             }
299         }
300
301         {
302             // Full read of decrypted data blocks in the center.
303
long blockCounter = fp / blockLen;
304             while (read + blockLen < len && fp + blockLen <= length) {
305                 // The file pointer is starting and ending on block boundaries.
306
positionWindow();
307                 cipher.setBlockCounter(blockCounter++);
308                 cipher.processBlock(window, (int) (fp - windowOff), buf, off + read);
309                 read += blockLen;
310                 fp += blockLen;
311             }
312         }
313         
314         // Partial read of decrypted data block at the end.
315
if (read < len && fp < length) {
316             // The file pointer is not on a block boundary.
317
positionBlock();
318             final int n = (int) min(len - read, length - fp);
319             System.arraycopy(block, 0, buf, off + read, n);
320             read += n;
321             fp += n;
322         }
323         
324         // Assert that at least one byte has been read if len isn't zero.
325
// Note that EOF has been tested before.
326
assert read > 0;
327         return read;
328     }
329
330     public int skipBytes(int n) throws IOException JavaDoc {
331         if (n <= 0)
332             return 0; // for compatibility to RandomAccessFile in case of closed
333

334         // Check state.
335
ensureInit();
336
337         if (fp >= length)
338             return 0;
339         final long remaining = length - fp;
340         if (n > remaining)
341             n = (int) remaining;
342         fp += n;
343
344         return n;
345     }
346
347     /**
348      * Ensures that this read only file is open and has been initialized.
349      *
350      * @throws IOException If the preconditions do not hold.
351      */

352     private final void ensureInit() throws IOException JavaDoc {
353         if (cipher == null)
354             throw new IOException JavaDoc("CipherReadOnlyFile has already been closed or is not initialized!");
355     }
356
357     /**
358      * Closes this read only file and releases any resources associated with it.
359      * This method invalidates the state of this object, causing any subsequent
360      * calls to a public method to fail with an {@link IOException}.
361      *
362      * @throws IOException If an I/O error occurs.
363      */

364     public void close() throws IOException JavaDoc {
365         // Order is important here!
366
if (!closed) {
367             closed = true;
368             cipher = null;
369             rof.close();
370         }
371     }
372
373     /**
374      * Ensures that the block with the decrypted data for partial reading is
375      * positioned so that it contains the current virtual file pointer
376      * in the encrypted file.
377      *
378      * @throws IOException On any I/O related issue.
379      * The block is not moved in this case.
380      */

381     private void positionBlock() throws IOException JavaDoc {
382         // Check block position.
383
final long fp = this.fp;
384         final int blockLen = block.length;
385         if (blockOff <= fp) {
386             final long nextBlockOff = blockOff + blockLen;
387             if (fp < nextBlockOff)
388                 return;
389         }
390
391         // Move block.
392
positionWindow();
393         final long blockCounter = fp / blockLen;
394         blockOff = blockCounter * blockLen;
395         
396         // Decrypt block from window.
397
cipher.setBlockCounter(blockCounter);
398         cipher.processBlock(window, (int) (blockOff - windowOff), block, 0);
399     }
400
401     /**
402      * Ensures that the window is positioned so that the block containing
403      * the current virtual file pointer in the encrypted file is entirely
404      * contained in it.
405      *
406      * @throws IOException On any I/O related issue.
407      * The window is invalidated in this case.
408      */

409     private void positionWindow() throws IOException JavaDoc {
410         // Check window position.
411
final long fp = this.fp;
412         final int windowLen = window.length;
413         final long nextWindowOff = windowOff + windowLen;
414         if (windowOff <= fp && fp < nextWindowOff)
415             return;
416     
417         try {
418             // Move window in the encrypted file.
419
final int blockLen = block.length;
420             windowOff = (fp / blockLen) * blockLen; // round down to multiple of block size
421
if (windowOff != nextWindowOff)
422                 rof.seek(windowOff + start);
423
424             // Fill window until end of file or buffer.
425
// This should normally complete in one loop cycle, but we do not
426
// depend on this as it would be a violation of ReadOnlyFile's
427
// contract.
428
int n = 0;
429             do {
430                 int read = rof.read(window, n, windowLen - n);
431                 if (read < 0)
432                     break;
433                 n += read;
434             } while (n < windowLen);
435         } catch (IOException JavaDoc ioe) {
436             windowOff = -windowLen - 1; // force seek() at next positionWindow()
437
throw ioe;
438         }
439     }
440 }
441
Popular Tags