All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.schlichtherle.crypto.io.CipherReadOnlyFile Maven / Gradle / Ivy

Go to download

TrueZIP is a Java based Virtual File System (VFS) to enable transparent, multi-threaded read/write access to archive files (ZIP, TAR etc.) as if they were directories. Archive files may be arbitrarily nested and the nesting level is only limited by heap and file system size.

The newest version!
/*
 * Copyright (C) 2006-2010 Schlichtherle IT Services
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.schlichtherle.crypto.io;

import de.schlichtherle.crypto.*;
import de.schlichtherle.io.rof.*;

import java.io.*;

import org.bouncycastle.crypto.*;

/**
 * A read only file for transparent random read access to an encrypted file.
 * 

* The client must call {@link #init(SeekableBlockCipher, long, long)} * before it can actually read anything! *

* Note that this class implements its own virtual file pointer. * Thus, if you would like to access the underlying {@code ReadOnlyFile} * again after you have finished working with an instance of this class, * you should synchronize their file pointers using the pattern as described * in the base class {@link FilterReadOnlyFile}. * * @author Christian Schlichtherle * @version $Id: CipherReadOnlyFile.java,v 1.4 2010/08/20 13:09:45 christian_schlichtherle Exp $ * @since TrueZIP 6.0 */ // // Tactical notes: // // In order to provide optimum performance, this class implements a read ahead // strategy with lazy decryption. // So the encrypted data of the file is read ahead into an internal window // buffer in order to minimize file access. // Upon request by the application only, this buffer is then decrypted block by // block into the buffer provided by the application. // // For similar reasons, this class is NOT a subclass of // BufferedReadOnlyFile though their algorithm and code is pretty similar. // In fact, this class uses an important performance optimization: // Whenever possible, encrypted data in the window buffer is directly // decrypted into the user provided buffer. // If BufferedReadOnlyFile would be used as the base class instead, we would // have to provide another buffer to copy the data into before we could // actually decrypt it, which is redundant. // public abstract class CipherReadOnlyFile extends FilterReadOnlyFile { /** * The maximum buffer length of the window to the encrypted file. * This value has been adjusted to provide optimum performance at minimal * size on a Windows XP computer - results may vary. * Note that the actual size of the window is a multiple of the * cipher's block size and may be smaller than the maximum window size. */ private static final int MAX_WINDOW_LEN = 1024; /** Returns the smaller parameter. */ private static final long min(long a, long b) { return a < b ? a : b; } /** Returns the greater parameter. */ /*private static final long max(long a, long b) { return a < b ? b : a; }*/ /** Start offset of the encrypted data. */ private long start; /** The length of the encrypted data. */ private long length; /** * The virtual file pointer in the encrypted data. * This is relative to the start. */ private long fp; /** * The current offset in the encrypted file where the buffer window starts. * This is always a multiple of the block size. */ private long windowOff; /** * The buffer window to the encrypted file. * Note that this buffer contains encrypted data only. * The actual size of the window is a multiple of the cipher's block size * and may be slightly smaller than {@link #MAX_WINDOW_LEN}. */ private byte[] window; /** The seekable block cipher which allows random access. */ private SeekableBlockCipher cipher; /** * The current offset in the encrypted file where the data starts that * has been decrypted to the block. * This is always a multiple of the block size. */ private long blockOff; /** * The block buffer to use for decryption of partial blocks. * Note that this buffer contains decrypted data only. */ private byte[] block; /** Whether this read only file has been closed or not. */ private boolean closed; /** * Creates a read only file for transparent random read access to an * encrypted file. * The client must call {@link #init(SeekableBlockCipher, long, long)} * before it can actually read anything! * * @param rof A read-only file. * This may be {@code null}, but must be properly init before * the call to {@code init()}. */ public CipherReadOnlyFile(ReadOnlyFile rof) { super(rof); } /** * Initializes this cipher read only file - must be called before first * read access! * * @param start The start offset of the encrypted data in this file. * @param length The length of the encrypted data in this file. * * @throws IOException If this read only file has already been closed. * This exception is not recoverable. * @throws IllegalStateException If this object has already been * initialized. * This exception is not recoverable. * @throws NullPointerException If {@link #rof} is {@code null} * or {@code cipher} is {@code null}. * This exception is recoverable. */ public void init( final SeekableBlockCipher cipher, final long start, final long length) throws IOException { // Check state. if (closed) throw new IOException("file has been closed"); if (this.cipher != null) throw new IllegalStateException("file is already initialized"); // Check state (recoverable). if (rof == null) throw new NullPointerException("rof"); // Check parameters (fail fast). if (cipher == null) throw new NullPointerException("cipher"); if (start < 0 || length < 0) throw new IllegalArgumentException(); this.cipher = cipher; this.start = start; this.length = length; blockOff = length; final int blockLen = cipher.getBlockSize(); block = new byte[blockLen]; windowOff = Long.MIN_VALUE; // invalidate window window = new byte[(MAX_WINDOW_LEN / blockLen) * blockLen]; // round down to multiple of block size assert fp == 0; assert block.length > 0; assert window.length > 0; assert window.length % block.length == 0; } /** * Returns the authentication code of the encrypted data in this cipher * read only file using the given Message Authentication Code (MAC) object. * It is safe to call this method multiple times to detect if the file * has been tampered with meanwhile. * * @param mac A properly initialized MAC object. * * @throws IOException On any I/O related issue. */ protected byte[] computeMac(final Mac mac) throws IOException { final int windowLen = window.length; final byte[] buf = new byte[mac.getMacSize()]; final long safedFp = getFilePointer(); try { for (fp = 0; fp < length; fp += windowLen) { positionWindow(); final long remaining = length - windowOff; mac.update(window, 0, (int) min(windowLen, remaining)); } final int bufLen = mac.doFinal(buf, 0); assert bufLen == buf.length; } finally { fp = safedFp; } return buf; } public long length() throws IOException { ensureInit(); return length; } public long getFilePointer() throws IOException { ensureInit(); return fp; } public void seek(final long fp) throws IOException { ensureInit(); if (fp < 0) throw new IOException("file pointer must not be negative"); if (fp > length) throw new IOException("file pointer (" + fp + ") is larger than file length (" + length + ")"); this.fp = fp; } public int read() throws IOException { // Check state. ensureInit(); if (fp >= length) return -1; // Position block and return its decrypted data. positionBlock(); return block[(int) (fp++ % block.length)] & 0xff; } public int read(final byte[] buf, final int off, final int len) throws IOException { if (len == 0) return 0; // be fault-tolerant and compatible to RandomAccessFile // Check state. ensureInit(); if (fp >= length) return -1; // Check parameters. if (buf == null) throw new NullPointerException("buf"); final int offPlusLen = off + len; if ((off | len | offPlusLen | buf.length - offPlusLen) < 0) throw new IndexOutOfBoundsException(); // Setup. final int blockLen = block.length; int read = 0; // amount of decrypted data copied to buf { // Partial read of decrypted data block at the start. final int o = (int) (fp % blockLen); if (o != 0) { // The file pointer is not on a block boundary. positionBlock(); read = (int) min(len, blockLen - o); read = (int) min(read, length - fp); System.arraycopy(block, o, buf, off, read); fp += read; } } { // Full read of decrypted data blocks in the middle. long blockCounter = fp / blockLen; while (read + blockLen < len && fp + blockLen <= length) { // The file pointer is starting and ending on block boundaries. positionWindow(); cipher.setBlockCounter(blockCounter++); cipher.processBlock(window, (int) (fp - windowOff), buf, off + read); read += blockLen; fp += blockLen; } } // Partial read of decrypted data block at the end. if (read < len && fp < length) { // The file pointer is not on a block boundary. positionBlock(); final int n = (int) min(len - read, length - fp); System.arraycopy(block, 0, buf, off + read, n); read += n; fp += n; } // Assert that at least one byte has been read if len isn't zero. // Note that EOF has been tested before. assert read > 0; return read; } /** * Ensures that this file is open and has been initialized. * * @throws IOException If the preconditions do not hold. */ private final void ensureInit() throws IOException { if (cipher == null) throw new IOException("file is closed or not initialized"); } /** * Closes this read only file and releases any resources associated with it. * This method invalidates the state of this object, causing any subsequent * calls to a public method to fail with an {@link IOException}. * * @throws IOException If an I/O error occurs. */ public void close() throws IOException { if (closed) return; // Order is important here! closed = true; cipher = null; rof.close(); } /** * Ensures that the block with the decrypted data for partial reading is * positioned so that it contains the current virtual file pointer * in the encrypted file. * * @throws IOException On any I/O related issue. * The block is not moved in this case. */ private void positionBlock() throws IOException { // Check block position. final long fp = this.fp; final int blockLen = block.length; if (blockOff <= fp) { final long nextBlockOff = blockOff + blockLen; if (fp < nextBlockOff) return; } // Move block. positionWindow(); final long blockCounter = fp / blockLen; blockOff = blockCounter * blockLen; // Decrypt block from window. cipher.setBlockCounter(blockCounter); cipher.processBlock(window, (int) (blockOff - windowOff), block, 0); } /** * Ensures that the window is positioned so that the block containing * the current virtual file pointer in the encrypted file is entirely * contained in it. * * @throws IOException On any I/O related issue. * The window is invalidated in this case. */ private void positionWindow() throws IOException { // Check window position. final long fp = this.fp; final int windowLen = window.length; final long nextWindowOff = windowOff + windowLen; if (windowOff <= fp && fp < nextWindowOff) return; try { // Move window in the encrypted file. final int blockLen = block.length; windowOff = fp / blockLen * blockLen; // round down to multiple of block size if (windowOff != nextWindowOff) rof.seek(windowOff + start); // Fill window until end of file or buffer. // This should normally complete in one loop cycle, but we do not // depend on this as it would be a violation of ReadOnlyFile's // contract. int n = 0; do { int read = rof.read(window, n, windowLen - n); if (read < 0) break; n += read; } while (n < windowLen); } catch (IOException ioe) { windowOff = -windowLen - 1; // force seek() at next positionWindow() throw ioe; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy