/* * Copyright (C) 2012 The Android Open Source Project * * 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 com.android.jobb; import Twofish.Twofish_Algorithm; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.security.InvalidKeyException; import java.util.Arrays; public class EncryptedBlockFile extends RandomAccessFile { private final class EncryptedBlockFileChannel extends FileChannel { final FileChannel mFC; protected EncryptedBlockFileChannel(FileChannel wrappedFC) { super(); mFC = wrappedFC; } @Override public void force(boolean metaData) throws IOException { mFC.force(metaData); } @Override public FileLock lock(long position, long size, boolean shared) throws IOException { throw new RuntimeException("Lock not implemented"); } @Override public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { throw new RuntimeException("MappedByteBuffer not implemented"); } @Override public long position() throws IOException { return mFC.position(); } @Override public FileChannel position(long newPosition) throws IOException { mFC.position(newPosition); return this; } @Override public int read(ByteBuffer dst) throws IOException { long position = position(); int read = read(dst, position); if ( read >= 0 ) { position += read; position(position); } return read; } @Override public int read(ByteBuffer dest, long position) throws IOException { boolean isMisaligned; boolean isPartial; boolean doubleBuffer; int toRead = dest.remaining(); int targetRead = toRead; int numSectors = toRead / BYTES_PER_SECTOR; if ((position + toRead) > length()) throw new IOException( "reading past end of device"); int alignmentOff; int firstSector = (int) position / BYTES_PER_SECTOR; if ( 0 != (alignmentOff = (int)(position % BYTES_PER_SECTOR ))) { toRead += alignmentOff; numSectors = toRead/BYTES_PER_SECTOR; isMisaligned = true; doubleBuffer = true; System.out.println("Alignment off reading from sector: " + firstSector); } else { isMisaligned = false; doubleBuffer = false; alignmentOff = 0; } int partialReadSize; if ( 0 != (partialReadSize = (int)(toRead % BYTES_PER_SECTOR ))) { isPartial = true; doubleBuffer = true; numSectors = toRead/BYTES_PER_SECTOR + 1; System.out.println("Partial read from sector: " + firstSector); } else { isPartial = false; } ByteBuffer tempDest; if ( doubleBuffer ) { tempDest = ByteBuffer.allocate(BYTES_PER_SECTOR); } else { tempDest = null; } int lastSector = firstSector + numSectors; if ( isMisaligned ) { // first sector is misaligned. Read and decrypt into temp dest readDecryptedSector(firstSector++, tempDest); tempDest.position(alignmentOff); // special case -- small sector; if ( firstSector == lastSector && isPartial ) { tempDest.limit(partialReadSize); } dest.put(tempDest); } for ( int i = firstSector; i < lastSector; i++ ) { if ( firstSector+1 == lastSector && isPartial ) { readDecryptedSector(i, tempDest); tempDest.rewind(); tempDest.limit(partialReadSize); dest.put(tempDest); } else { readDecryptedSector(i, dest); } } return targetRead; } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { throw new RuntimeException("Scattering Channel Read not implemented"); } @Override public long size() throws IOException { return mFC.size(); } @Override public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { throw new RuntimeException("File Channel transfer not implemented"); } @Override public long transferTo(long position, long count, WritableByteChannel target) throws IOException { throw new RuntimeException("File Channel transfer to not implemented"); } @Override public FileChannel truncate(long size) throws IOException { mFC.truncate(size); return this; } @Override public FileLock tryLock(long position, long size, boolean shared) throws IOException { return mFC.tryLock(position, size, shared); } @Override public int write(ByteBuffer src) throws IOException { long position = position(); int write = write(src, position); if ( write >= 0 ) { position += write; position(position); } return write; } @Override public int write(ByteBuffer src, long position) throws IOException { int toWrite = src.remaining(); int targetWrite = toWrite; int firstSector = (int) position / BYTES_PER_SECTOR; int numSectors = toWrite / BYTES_PER_SECTOR; boolean fixAccess = false; long readOffset; if ( 0 != position % BYTES_PER_SECTOR ) { long alignmentOff = (position % BYTES_PER_SECTOR); readOffset = position - alignmentOff; toWrite += alignmentOff; numSectors = toWrite/BYTES_PER_SECTOR; fixAccess = true; System.out.println("Alignment off writing to sector: " + firstSector); } else { readOffset = position; } if ( 0 != toWrite % BYTES_PER_SECTOR ) { numSectors = toWrite/BYTES_PER_SECTOR + 1; fixAccess = true; System.out.println("Partial Sector [" + toWrite % BYTES_PER_SECTOR + "] writing to sector: " + firstSector); } if ( fixAccess ) { ByteBuffer dest = ByteBuffer.allocate(numSectors * BYTES_PER_SECTOR); read(dest, readOffset); int bufOffset = (int)(position - readOffset); dest.position(bufOffset); dest.put(src); src = dest; src.rewind(); } int lastSector = firstSector + numSectors; for ( int i = firstSector; i < lastSector; i++ ) { writeEncryptedSector(i, src); } return targetWrite; } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { throw new RuntimeException("Scattering Channel Write not implemented"); } @Override protected void implCloseChannel() throws IOException { // TODO Auto-generated method stub } /** * plain: the initial vector is the 32-bit little-endian version of the * sector number, padded with zeros if necessary. */ private void cryptIVPlainGen(int sector, byte[] out) { Arrays.fill(out, (byte)0); out[0] = (byte)(sector & 0xff); out[1] = (byte)(sector >> 8 & 0xff); out[2] = (byte)(sector >> 16 & 0xff); out[3] = (byte)(sector >>> 24); } private void readDecryptedSector(int sector, ByteBuffer dest) throws IOException { ByteBuffer temp = ByteBuffer.allocate(BYTES_PER_SECTOR); int toRead = BYTES_PER_SECTOR; int devOffset = BYTES_PER_SECTOR*sector; // number of chained twofish blocks int blockSize = Twofish_Algorithm.blockSize(); byte[] bufLast = new byte[blockSize]; int numBlocks = toRead / blockSize; // read unencrypted sector while (toRead > 0) { final int read = mFC.read(temp, devOffset); if (read < 0) throw new IOException(); toRead -= read; devOffset += read; } temp.rewind(); // set initialization vector cryptIVPlainGen(sector, bufLast); byte[] buf = new byte[blockSize]; for (int i = 0; i < numBlocks; i++) { temp.get(buf); // decrypt with chained blocks --- xor with the previous encrypted block byte[] decryptBuf = Twofish_Algorithm.blockDecrypt(buf, 0, mKey); for (int j = 0; j < blockSize; j++) { decryptBuf[j] ^= bufLast[j]; } System.arraycopy(buf, 0, bufLast, 0, blockSize); dest.put(decryptBuf); } } private void writeEncryptedSector(int sector, ByteBuffer src) throws IOException { byte[] sectorBuf = new byte[BYTES_PER_SECTOR]; int toRead = BYTES_PER_SECTOR; int devOffset = BYTES_PER_SECTOR*sector; // number of chained twofish blocks int blockSize = Twofish_Algorithm.blockSize(); byte[] bufLast = new byte[blockSize]; int numBlocks = toRead / blockSize; // fetch unencrypted sector src.get(sectorBuf); // set initialization vector cryptIVPlainGen(sector, bufLast); int pos = 0; byte[] buf = new byte[blockSize]; for (int i = 0; i < numBlocks; i++) { System.arraycopy(sectorBuf, pos, buf, 0, blockSize); // encrypt with chained blocks --- xor with the previous encrypted block for (int j = 0; j < blockSize; j++) { buf[j] ^= bufLast[j]; } byte[] encryptBuf = Twofish_Algorithm.blockEncrypt(buf, 0, mKey); bufLast = encryptBuf; int toWrite = blockSize; ByteBuffer encryptBuffer = ByteBuffer.wrap(encryptBuf); while (toWrite > 0) { final int written = mFC.write(encryptBuffer, devOffset); if (written < 0) throw new IOException(); toWrite -= written; devOffset += written; } pos += blockSize; } } } public EncryptedBlockFileChannel getEncryptedFileChannel() { return mEBFC; } /** * This will clear the file as well as set the length. It would be easy enough * to preserve the blocks, but that is not the intention of this class. */ @Override public void setLength(long newLength) throws IOException { int numsectors = (int)newLength/BYTES_PER_SECTOR; if ( newLength % BYTES_PER_SECTOR != 0 ) { throw new IOException("Invalid file size!"); } super.setLength(newLength); // write encrypted empty sectors into the block storage byte[] byteBuf = new byte[BYTES_PER_SECTOR]; ByteBuffer buf = ByteBuffer.wrap(byteBuf); for ( int i = 0; i < numsectors; i++ ) { buf.rewind(); mEBFC.write(buf); } } /** * The number of bytes per sector for all {@code FileDisk} instances. */ public final static int BYTES_PER_SECTOR = 512; private final Object mKey; private final EncryptedBlockFileChannel mEBFC; public EncryptedBlockFile(byte[] key, File file, String mode) throws FileNotFoundException, InvalidKeyException { super(file, mode); mEBFC = new EncryptedBlockFileChannel(getChannel()); if (!file.exists()) throw new FileNotFoundException(); mKey = Twofish_Algorithm.makeKey(key); } }