/** * Flush the file, computing the hash if necessary. After this method completes the hash obtained * in {@link #getHeader()} will be valid, but the file will not be closed. If you want the file to * be closed, use {@link #close()} instead, which invokes this method. * * <p><b>Warning</b>: Because of the hash computation this method can be costly! After calling * this the internal hash computation must be completely reset, so a subsequent write will cause * the hash to be updated from scratch. Use caution with this method. In fact, this is the reason * this method is not named {@code flush}. * * @throws IOException An error occurred writing the file. */ public void finish() throws IOException { if (!_open) return; // Save the current position so we can restore it later. long pos = _backing.getFilePointer(); // If the hash is not valid, compute it now. if (!_hashvalid) { // The hash is not valid. Complete the computation now and store // the resulting hash. _backing.seek(_backing.length()); _updateDigest(); _head.hash = _digest.digest(); _hashvalid = true; // Reset the digest now to force it to be re-computed next time. // This must be done since we just "used up" the existing digest // instance. _resetDigest(); } // Write the header to the backing store. _backing.seek(PicoStructure.HEAD_START); _backing.write(_head.putHeader()); // Restore the file position. _backing.seek(pos); }
/** * Finish the stream, writing the header and the encrypted data to the stream. The underlying * stream is not closed. If you want to cause the underlying stream to be closed, too, use {@code * close()}, which invokes this method and then closes the stream. * * @throws IOException An error occurred writing the file. */ public void finish() throws IOException { // Finish the hash and store it in the header. _head.hash = _hash.digest(); // Write the header to the backing store. _backing.write(_head.putHeader()); // Write the encrypted data to the backing store. _encrypted.flush(); _encrypted.close(); // Now we perform that transfer from temporary file to final file. InputStream fis = new FileInputStream(_tmpfile); byte[] buffer = new byte[1024]; while (fis.available() > 0) { int length = fis.read(buffer); if (length >= 0) { _backing.write(buffer, 0, length); } } // Copy all encrypted data. fis.close(); _backing.flush(); _tmpfile.delete(); _closed = true; }
/** * Get the header for this Pico file. The returned header is a copy of the actual header. * * <p>Note that the returned hash may be {@code null} if it has not been computed. If the file has * been opened, but not written, then the hash will be available via this method. * * @return The header of this file. * @throws IOException The file length cannot be read. */ public PicoHeader getHeader() throws IOException { PicoHeader head = _head.clone(); if (!_hashvalid) { head.hash = null; } return head; }
/* * (non-Javadoc) * * @see java.nio.channels.SeekableByteChannel#read(java.nio.ByteBuffer) */ @Override public int read(ByteBuffer dst) throws IOException { if (dst == null) { throw new NullPointerException("The destination buffer is null."); } if (dst.limit() == 0) { throw new IllegalArgumentException("The destination buffer has zero length."); } if (!_open) return -1; // Make an attempt to read up to r bytes from the channel, where // r is the number of bytes remaining in the buffer, that is, // dst.remaining(), at the moment this method is invoked. long _here = position(); // Build temporary storage. int remain = dst.remaining(); byte[] data = new byte[remain]; int length = _backing.read(data, 0, remain); if (length > 0) { // Iterate thru each byte of temporary storage and decrypt those // bytes back // into the buffer for (int index = 0; index < length; index++) { data[index] = _head.crypt(data[index], index + _here); } // Decrypt all bytes. dst.put(data, 0, length); } return length; }
@Override public void write(byte[] arr, int off, int len) throws IOException { if (_closed) return; // first make copy of relevant part of array // TODO : add bounds checking // JMC: Why not just new byte[len]?? byte[] encodedArr = new byte[len - off]; System.arraycopy(arr, off, encodedArr, 0, len); for (int i = off; i < (off + len); i++) { byte b = arr[i]; // Add to the message digest. _hash.update((byte) b); // Encode. b = _head.crypt((byte) b, _position); _position++; // Add encoded byte to b encodedArr[i] = b; } // Write. _encrypted.write(encodedArr); // System.out.println("SBJHBKJDBH"); }
/** * Make a new Pico output stream, wrapping the provided stream. Use the given key to encrypt the * data. * * @param key The key to use to encrypt. * @param os The stream to get the output. * @throws IOException An error occurred creating the temporary file. */ public PicoOutputStream(byte[] key, OutputStream os) throws IOException { super(os); if (key == null) { throw new NullPointerException("The key is null."); } if (os == null) { throw new NullPointerException("The output stream is null."); } _backing = os; try { _hash = MessageDigest.getInstance(PicoStructure.HASH); } catch (NoSuchAlgorithmException nsae) { throw new RuntimeException("Failed to create hash.", nsae); } // Construct a temporary file to get the encrypted data. _tmpfile = File.createTempFile("pico", "pico"); _tmpfile.deleteOnExit(); _encrypted = new FileOutputStream(_tmpfile); // Build the header. _head = new PicoHeader(); _head.setKey(key); }
/** * Read the next byte at the current position, and return it. Return -1 if the end of the file is * read. * * @return The next byte. * @throws IOException The next byte cannot be read. */ public int read() throws IOException { if (!_open) return -1; long _here = position(); int val = _backing.read(); if (val >= 0) { val = _head.crypt((byte) val, _here); } return val; }
/** * Read the header. This reads the header from an existing file. After this method completes the * header reflects what it stored in the file, including the hash and key. * * @throws PicoException The header structure is incorrect. * @throws IOException The header cannot be read. */ private void _readHeader() throws PicoException, IOException { if (!_open) return; // Save the current position and move to the start of the header. long pos = _backing.getFilePointer(); _backing.seek(PicoStructure.HEAD_START); // Read the header from the file. We read the fixed length portion // here, up through the start of the key. byte[] _fixedhdr = new byte[(int) PicoStructure.FIXED_HEADER_LENGTH]; int length = _backing.read(_fixedhdr); // Make sure we have enough bytes for the magic string. if (length < PicoStructure.MAGIC_LENGTH - PicoStructure.MAGIC_OFFSET) { // The magic string was not present. This cannot be a Pico wrapper // file. throw new PicoException("File too short; missing magic string."); } // Process the fixed portion of the header. After calling this the // key is allocated, but not populated. _head = PicoHeader.getHeader(_fixedhdr); // The hash is valid because we just read it from the file and nothing // has yet been written. All write methods must invalidate the hash. _hashvalid = true; // Go and read the key, now that we know its length. Note that we read // it directly into the array returned by getKey. length = _backing.read(_head.getKey()); // Make sure we have the complete key. The only bad case is that the // file ends before the key is complete. if (length != _head.getKey().length) { throw new PicoException("File too short; incomplete key."); } // Move to the original position in the file. _backing.seek(pos); // Ka-presto! The header has been read. Life is good. }
/** * Write a byte at the current position. * * @param datum The byte. * @throws IOException The byte cannot be written. */ public void write(int datum) throws IOException { if (!_open) return; datum &= 0xff; long _here = position(); // Are we synced up? if (_here == _digestvalidto) { // Yes, digest the byte and move the valid to pointer. _digest.update((byte) datum); _digestvalidto++; // JMC: Was missing. } // Otherwise, advancing of the position will destroy the digest sync. datum = _head.crypt((byte) datum, _here); _backing.write(datum); }
/* (non-Javadoc) * @see java.io.OutputStream#write(int) */ @Override public void write(int datum) throws IOException { if (_closed) return; // Byteify this. datum &= 0xff; // Add to the message digest. _hash.update((byte) datum); // Encode. datum = _head.crypt((byte) datum, _position); // Write. _encrypted.write(datum); _position += 1; }
/** * Create a new Pico file instance from the given random access file. If the file exists and it is * not empty then it is truncated. If it does not exist then it is created. * * @param backing The random access file. * @param key The key to use to encrypt the file. * @throws IOException The file cannot be read. */ protected PicoFile(RandomAccessFile backing, byte[] key) throws IOException { assert backing != null : "Backing is null."; assert key != null : "Key is null."; assert key.length > 0 : "Key is missing."; _backing = backing; _open = true; _resetDigest(); // We are creating a new file, so truncate any existing file and // generate a new header. _backing.setLength(0L); _head = new PicoHeader(); _head.setKey(key); // Now the Header size is fixed since we have the key and know the size // of the hash // we will write later. // This actually positions us to _head.offset + 0 position(0L); }
/* * (non-Javadoc) * * @see java.nio.channels.WritableByteChannel#write(java.nio.ByteBuffer) * * NOTE: Changes the state of src in the normal case, i.e., position = * limit. */ @Override public int write(ByteBuffer src) throws IOException { if (src == null) { throw new NullPointerException("The source buffer is null."); } // Is there something to actually write in source. if (src.limit() == 0 || src.remaining() == 0) { throw new IllegalArgumentException( "The source buffer has zero length or zero remaining bytes."); } if (!_open) return -1; // based on the specification _hear is p. long _here = position(); // Make an attempt to write up to r bytes to the channel, where // r is the number of bytes remaining in the buffer, that is, // src.remaining(), at the moment this method is invoked. // NOTE: not good to use the src.array() since that is the backing array // which // reflects the entire buffer and not from p to remaining. byte[] encr = new byte[src.remaining()]; // Update the digest to the start of the write. _updateDigest(); // Have we reached source's limit? for (int index = 0; src.hasRemaining(); index++) { // The unencrypted data. byte b = src.get(); _digest.update(b); _digestvalidto++; // encrypt it into the buffer. encr[index] = _head.crypt(b, index + _here); } _backing.write(encr); return encr.length; }