diff options
Diffstat (limited to 'jobb')
-rw-r--r-- | jobb/Android.mk | 30 | ||||
-rw-r--r-- | jobb/NOTICE | 50 | ||||
-rw-r--r-- | jobb/etc/Android.mk | 20 | ||||
-rwxr-xr-x | jobb/etc/jobb | 68 | ||||
-rwxr-xr-x | jobb/etc/jobb.bat | 51 | ||||
-rw-r--r-- | jobb/etc/manifest.txt | 2 | ||||
-rw-r--r-- | jobb/src/Twofish/Twofish_Algorithm.java | 835 | ||||
-rw-r--r-- | jobb/src/Twofish/Twofish_Properties.java | 197 | ||||
-rw-r--r-- | jobb/src/com/android/jobb/Base64.java | 276 | ||||
-rw-r--r-- | jobb/src/com/android/jobb/Encoder.java | 33 | ||||
-rw-r--r-- | jobb/src/com/android/jobb/EncryptedBlockFile.java | 379 | ||||
-rw-r--r-- | jobb/src/com/android/jobb/Main.java | 607 | ||||
-rw-r--r-- | jobb/src/com/android/jobb/ObbFile.java | 231 | ||||
-rw-r--r-- | jobb/src/com/android/jobb/PBKDF.java | 55 |
14 files changed, 2834 insertions, 0 deletions
diff --git a/jobb/Android.mk b/jobb/Android.mk new file mode 100644 index 0000000..28e9efd --- /dev/null +++ b/jobb/Android.mk @@ -0,0 +1,30 @@ +# Copyright (C) 2010 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. + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := src + +LOCAL_JAR_MANIFEST := etc/manifest.txt + +LOCAL_JAVA_LIBRARIES := \ + fat32lib + +LOCAL_MODULE := jobb + +include $(BUILD_HOST_JAVA_LIBRARY) +include $(LOCAL_PATH)/etc/Android.mk + diff --git a/jobb/NOTICE b/jobb/NOTICE new file mode 100644 index 0000000..93baeb5 --- /dev/null +++ b/jobb/NOTICE @@ -0,0 +1,50 @@ +Portions of this code: +------------------------------------------------------------------------------- +Copyright (c) 2000 The Legion Of The Bouncy Castle +(http://www.bouncycastle.org) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the "Software") +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense +and/or sell copies of the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +------------------------------------------------------------------------------- +Twofish is uncopyrighted and license-free, and was created and analyzed by: +Bruce Schneier - John Kelsey - Doug Whiting +David Wagner - Chris Hall - Niels Ferguson +------------------------------------------------------------------------------- +Cryptix General License + +Copyright (c) 1995-2005 The Cryptix Foundation Limited. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + 1. Redistributions of source code must retain the copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. +------------------------------------------------------------------------------- +All other code is: + +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. + diff --git a/jobb/etc/Android.mk b/jobb/etc/Android.mk new file mode 100644 index 0000000..b31397a --- /dev/null +++ b/jobb/etc/Android.mk @@ -0,0 +1,20 @@ +# Copyright (C) 2010 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. + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_PREBUILT_EXECUTABLES := jobb +include $(BUILD_HOST_PREBUILT) + diff --git a/jobb/etc/jobb b/jobb/etc/jobb new file mode 100755 index 0000000..c782141 --- /dev/null +++ b/jobb/etc/jobb @@ -0,0 +1,68 @@ +#!/bin/sh +# Copyright 2008, 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. + +# Set up prog to be the path of this script, including following symlinks, +# and set up progdir to be the fully-qualified pathname of its directory. + +prog="$0" +while [ -h "${prog}" ]; do + newProg=`/bin/ls -ld "${prog}"` + newProg=`expr "${newProg}" : ".* -> \(.*\)$"` + if expr "x${newProg}" : 'x/' >/dev/null; then + prog="${newProg}" + else + progdir=`dirname "${prog}"` + prog="${progdir}/${newProg}" + fi +done +oldwd=`pwd` +progdir=`dirname "${prog}"` +cd "${progdir}" +progdir=`pwd` +prog="${progdir}"/`basename "${prog}"` +cd "${oldwd}" + +jarfile=jobb.jar +frameworkdir="$progdir" +libdir="$progdir" +if [ ! -r "$frameworkdir/$jarfile" ] +then + frameworkdir=`dirname "$progdir"`/tools/lib + libdir=`dirname "$progdir"`/tools/lib +fi +if [ ! -r "$frameworkdir/$jarfile" ] +then + frameworkdir=`dirname "$progdir"`/framework + libdir=`dirname "$progdir"`/lib +fi +if [ ! -r "$frameworkdir/$jarfile" ] +then + echo `basename "$prog"`": can't find $jarfile" + exit 1 +fi + +javaCmd="java" + +if [ `uname` = "Linux" ]; then + export GDK_NATIVE_WINDOWS=true +fi + +jarpath="$frameworkdir/$jarfile:$frameworkdir/fat32lib.jar" + +exec "$javaCmd" \ + $os_opts \ + -classpath "$jarpath" \ + com.android.jobb.Main "$@" + diff --git a/jobb/etc/jobb.bat b/jobb/etc/jobb.bat new file mode 100755 index 0000000..a5043c1 --- /dev/null +++ b/jobb/etc/jobb.bat @@ -0,0 +1,51 @@ +@echo off
+rem Copyright (C) 2008 The Android Open Source Project
+rem
+rem Licensed under the Apache License, Version 2.0 (the "License");
+rem you may not use this file except in compliance with the License.
+rem You may obtain a copy of the License at
+rem
+rem http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+
+rem don't modify the caller's environment
+setlocal
+
+rem Set up prog to be the path of this script, including following symlinks,
+rem and set up progdir to be the fully-qualified pathname of its directory.
+set prog=%~f0
+
+rem Change current directory and drive to where the script is, to avoid
+rem issues with directories containing whitespaces.
+cd /d %~dp0
+
+rem Get the CWD as a full path with short names only (without spaces)
+for %%i in ("%cd%") do set prog_dir=%%~fsi
+
+rem Check we have a valid Java.exe in the path.
+set java_exe=
+call lib\find_java.bat
+if not defined java_exe goto :EOF
+
+set jarfile=jobb.jar
+set frameworkdir=
+set libdir=
+
+if exist %frameworkdir%%jarfile% goto JarFileOk
+ set frameworkdir=lib\
+
+if exist %frameworkdir%%jarfile% goto JarFileOk
+ set frameworkdir=..\framework\
+
+:JarFileOk
+
+set jarpath=%frameworkdir%%jarfile%;%frameworkdir%libfat32.jar
+
+call %java_exe% %java_debug% -classpath "%jarpath%" com.android.jobb.Main %*
+
+
diff --git a/jobb/etc/manifest.txt b/jobb/etc/manifest.txt new file mode 100644 index 0000000..305cd61 --- /dev/null +++ b/jobb/etc/manifest.txt @@ -0,0 +1,2 @@ +Main-Class: com.android.jobb.Main +Class-Path: fat32lib.jar diff --git a/jobb/src/Twofish/Twofish_Algorithm.java b/jobb/src/Twofish/Twofish_Algorithm.java new file mode 100644 index 0000000..330d68e --- /dev/null +++ b/jobb/src/Twofish/Twofish_Algorithm.java @@ -0,0 +1,835 @@ +// $Id: $ +// +// $Log: $ +// Revision 1.0 1998/03/24 raif +// + start of history. +// +// $Endlog$ +/* + * Copyright (c) 1997, 1998 Systemics Ltd on behalf of + * the Cryptix Development Team. All rights reserved. + */ +package Twofish; + +import java.io.PrintWriter; +import java.security.InvalidKeyException; + +//........................................................................... +/** + * Twofish is an AES candidate algorithm. It is a balanced 128-bit Feistel + * cipher, consisting of 16 rounds. In each round, a 64-bit S-box value is + * computed from 64 bits of the block, and this value is xored into the other + * half of the block. The two half-blocks are then exchanged, and the next + * round begins. Before the first round, all input bits are xored with key- + * dependent "whitening" subkeys, and after the final round the output bits + * are xored with other key-dependent whitening subkeys; these subkeys are + * not used anywhere else in the algorithm.<p> + * + * Twofish was submitted by Bruce Schneier, Doug Whiting, John Kelsey, Chris + * Hall and David Wagner.<p> + * + * Reference:<ol> + * <li>TWOFISH2.C -- Optimized C API calls for TWOFISH AES submission, + * Version 1.00, April 1998, by Doug Whiting.</ol><p> + * + * <b>Copyright</b> © 1998 + * <a href="http://www.systemics.com/">Systemics Ltd</a> on behalf of the + * <a href="http://www.systemics.com/docs/cryptix/">Cryptix Development Team</a>. + * <br>All rights reserved.<p> + * + * <b>$Revision: $</b> + * @author Raif S. Naffah + */ +public final class Twofish_Algorithm // implicit no-argument constructor +{ +// Debugging methods and variables +//........................................................................... + + static final String NAME = "Twofish_Algorithm"; + static final boolean IN = true, OUT = false; + + static final boolean DEBUG = Twofish_Properties.GLOBAL_DEBUG; + static final int debuglevel = DEBUG ? Twofish_Properties.getLevel(NAME) : 0; + static final PrintWriter err = DEBUG ? Twofish_Properties.getOutput() : null; + + static final boolean TRACE = Twofish_Properties.isTraceable(NAME); + + static void debug (String s) { err.println(">>> "+NAME+": "+s); } + static void trace (boolean in, String s) { + if (TRACE) err.println((in?"==> ":"<== ")+NAME+"."+s); + } + static void trace (String s) { if (TRACE) err.println("<=> "+NAME+"."+s); } + + +// Constants and variables +//........................................................................... + + static final int BLOCK_SIZE = 16; // bytes in a data-block + private static final int ROUNDS = 16; + private static final int MAX_ROUNDS = 16; // max # rounds (for allocating subkeys) + + /* Subkey array indices */ + private static final int INPUT_WHITEN = 0; + private static final int OUTPUT_WHITEN = INPUT_WHITEN + BLOCK_SIZE/4; + private static final int ROUND_SUBKEYS = OUTPUT_WHITEN + BLOCK_SIZE/4; // 2*(# rounds) + + private static final int TOTAL_SUBKEYS = ROUND_SUBKEYS + 2*MAX_ROUNDS; + + private static final int SK_STEP = 0x02020202; + private static final int SK_BUMP = 0x01010101; + private static final int SK_ROTL = 9; + + /** Fixed 8x8 permutation S-boxes */ + private static final byte[][] P = new byte[][] { + { // p0 + (byte) 0xA9, (byte) 0x67, (byte) 0xB3, (byte) 0xE8, + (byte) 0x04, (byte) 0xFD, (byte) 0xA3, (byte) 0x76, + (byte) 0x9A, (byte) 0x92, (byte) 0x80, (byte) 0x78, + (byte) 0xE4, (byte) 0xDD, (byte) 0xD1, (byte) 0x38, + (byte) 0x0D, (byte) 0xC6, (byte) 0x35, (byte) 0x98, + (byte) 0x18, (byte) 0xF7, (byte) 0xEC, (byte) 0x6C, + (byte) 0x43, (byte) 0x75, (byte) 0x37, (byte) 0x26, + (byte) 0xFA, (byte) 0x13, (byte) 0x94, (byte) 0x48, + (byte) 0xF2, (byte) 0xD0, (byte) 0x8B, (byte) 0x30, + (byte) 0x84, (byte) 0x54, (byte) 0xDF, (byte) 0x23, + (byte) 0x19, (byte) 0x5B, (byte) 0x3D, (byte) 0x59, + (byte) 0xF3, (byte) 0xAE, (byte) 0xA2, (byte) 0x82, + (byte) 0x63, (byte) 0x01, (byte) 0x83, (byte) 0x2E, + (byte) 0xD9, (byte) 0x51, (byte) 0x9B, (byte) 0x7C, + (byte) 0xA6, (byte) 0xEB, (byte) 0xA5, (byte) 0xBE, + (byte) 0x16, (byte) 0x0C, (byte) 0xE3, (byte) 0x61, + (byte) 0xC0, (byte) 0x8C, (byte) 0x3A, (byte) 0xF5, + (byte) 0x73, (byte) 0x2C, (byte) 0x25, (byte) 0x0B, + (byte) 0xBB, (byte) 0x4E, (byte) 0x89, (byte) 0x6B, + (byte) 0x53, (byte) 0x6A, (byte) 0xB4, (byte) 0xF1, + (byte) 0xE1, (byte) 0xE6, (byte) 0xBD, (byte) 0x45, + (byte) 0xE2, (byte) 0xF4, (byte) 0xB6, (byte) 0x66, + (byte) 0xCC, (byte) 0x95, (byte) 0x03, (byte) 0x56, + (byte) 0xD4, (byte) 0x1C, (byte) 0x1E, (byte) 0xD7, + (byte) 0xFB, (byte) 0xC3, (byte) 0x8E, (byte) 0xB5, + (byte) 0xE9, (byte) 0xCF, (byte) 0xBF, (byte) 0xBA, + (byte) 0xEA, (byte) 0x77, (byte) 0x39, (byte) 0xAF, + (byte) 0x33, (byte) 0xC9, (byte) 0x62, (byte) 0x71, + (byte) 0x81, (byte) 0x79, (byte) 0x09, (byte) 0xAD, + (byte) 0x24, (byte) 0xCD, (byte) 0xF9, (byte) 0xD8, + (byte) 0xE5, (byte) 0xC5, (byte) 0xB9, (byte) 0x4D, + (byte) 0x44, (byte) 0x08, (byte) 0x86, (byte) 0xE7, + (byte) 0xA1, (byte) 0x1D, (byte) 0xAA, (byte) 0xED, + (byte) 0x06, (byte) 0x70, (byte) 0xB2, (byte) 0xD2, + (byte) 0x41, (byte) 0x7B, (byte) 0xA0, (byte) 0x11, + (byte) 0x31, (byte) 0xC2, (byte) 0x27, (byte) 0x90, + (byte) 0x20, (byte) 0xF6, (byte) 0x60, (byte) 0xFF, + (byte) 0x96, (byte) 0x5C, (byte) 0xB1, (byte) 0xAB, + (byte) 0x9E, (byte) 0x9C, (byte) 0x52, (byte) 0x1B, + (byte) 0x5F, (byte) 0x93, (byte) 0x0A, (byte) 0xEF, + (byte) 0x91, (byte) 0x85, (byte) 0x49, (byte) 0xEE, + (byte) 0x2D, (byte) 0x4F, (byte) 0x8F, (byte) 0x3B, + (byte) 0x47, (byte) 0x87, (byte) 0x6D, (byte) 0x46, + (byte) 0xD6, (byte) 0x3E, (byte) 0x69, (byte) 0x64, + (byte) 0x2A, (byte) 0xCE, (byte) 0xCB, (byte) 0x2F, + (byte) 0xFC, (byte) 0x97, (byte) 0x05, (byte) 0x7A, + (byte) 0xAC, (byte) 0x7F, (byte) 0xD5, (byte) 0x1A, + (byte) 0x4B, (byte) 0x0E, (byte) 0xA7, (byte) 0x5A, + (byte) 0x28, (byte) 0x14, (byte) 0x3F, (byte) 0x29, + (byte) 0x88, (byte) 0x3C, (byte) 0x4C, (byte) 0x02, + (byte) 0xB8, (byte) 0xDA, (byte) 0xB0, (byte) 0x17, + (byte) 0x55, (byte) 0x1F, (byte) 0x8A, (byte) 0x7D, + (byte) 0x57, (byte) 0xC7, (byte) 0x8D, (byte) 0x74, + (byte) 0xB7, (byte) 0xC4, (byte) 0x9F, (byte) 0x72, + (byte) 0x7E, (byte) 0x15, (byte) 0x22, (byte) 0x12, + (byte) 0x58, (byte) 0x07, (byte) 0x99, (byte) 0x34, + (byte) 0x6E, (byte) 0x50, (byte) 0xDE, (byte) 0x68, + (byte) 0x65, (byte) 0xBC, (byte) 0xDB, (byte) 0xF8, + (byte) 0xC8, (byte) 0xA8, (byte) 0x2B, (byte) 0x40, + (byte) 0xDC, (byte) 0xFE, (byte) 0x32, (byte) 0xA4, + (byte) 0xCA, (byte) 0x10, (byte) 0x21, (byte) 0xF0, + (byte) 0xD3, (byte) 0x5D, (byte) 0x0F, (byte) 0x00, + (byte) 0x6F, (byte) 0x9D, (byte) 0x36, (byte) 0x42, + (byte) 0x4A, (byte) 0x5E, (byte) 0xC1, (byte) 0xE0 + }, + { // p1 + (byte) 0x75, (byte) 0xF3, (byte) 0xC6, (byte) 0xF4, + (byte) 0xDB, (byte) 0x7B, (byte) 0xFB, (byte) 0xC8, + (byte) 0x4A, (byte) 0xD3, (byte) 0xE6, (byte) 0x6B, + (byte) 0x45, (byte) 0x7D, (byte) 0xE8, (byte) 0x4B, + (byte) 0xD6, (byte) 0x32, (byte) 0xD8, (byte) 0xFD, + (byte) 0x37, (byte) 0x71, (byte) 0xF1, (byte) 0xE1, + (byte) 0x30, (byte) 0x0F, (byte) 0xF8, (byte) 0x1B, + (byte) 0x87, (byte) 0xFA, (byte) 0x06, (byte) 0x3F, + (byte) 0x5E, (byte) 0xBA, (byte) 0xAE, (byte) 0x5B, + (byte) 0x8A, (byte) 0x00, (byte) 0xBC, (byte) 0x9D, + (byte) 0x6D, (byte) 0xC1, (byte) 0xB1, (byte) 0x0E, + (byte) 0x80, (byte) 0x5D, (byte) 0xD2, (byte) 0xD5, + (byte) 0xA0, (byte) 0x84, (byte) 0x07, (byte) 0x14, + (byte) 0xB5, (byte) 0x90, (byte) 0x2C, (byte) 0xA3, + (byte) 0xB2, (byte) 0x73, (byte) 0x4C, (byte) 0x54, + (byte) 0x92, (byte) 0x74, (byte) 0x36, (byte) 0x51, + (byte) 0x38, (byte) 0xB0, (byte) 0xBD, (byte) 0x5A, + (byte) 0xFC, (byte) 0x60, (byte) 0x62, (byte) 0x96, + (byte) 0x6C, (byte) 0x42, (byte) 0xF7, (byte) 0x10, + (byte) 0x7C, (byte) 0x28, (byte) 0x27, (byte) 0x8C, + (byte) 0x13, (byte) 0x95, (byte) 0x9C, (byte) 0xC7, + (byte) 0x24, (byte) 0x46, (byte) 0x3B, (byte) 0x70, + (byte) 0xCA, (byte) 0xE3, (byte) 0x85, (byte) 0xCB, + (byte) 0x11, (byte) 0xD0, (byte) 0x93, (byte) 0xB8, + (byte) 0xA6, (byte) 0x83, (byte) 0x20, (byte) 0xFF, + (byte) 0x9F, (byte) 0x77, (byte) 0xC3, (byte) 0xCC, + (byte) 0x03, (byte) 0x6F, (byte) 0x08, (byte) 0xBF, + (byte) 0x40, (byte) 0xE7, (byte) 0x2B, (byte) 0xE2, + (byte) 0x79, (byte) 0x0C, (byte) 0xAA, (byte) 0x82, + (byte) 0x41, (byte) 0x3A, (byte) 0xEA, (byte) 0xB9, + (byte) 0xE4, (byte) 0x9A, (byte) 0xA4, (byte) 0x97, + (byte) 0x7E, (byte) 0xDA, (byte) 0x7A, (byte) 0x17, + (byte) 0x66, (byte) 0x94, (byte) 0xA1, (byte) 0x1D, + (byte) 0x3D, (byte) 0xF0, (byte) 0xDE, (byte) 0xB3, + (byte) 0x0B, (byte) 0x72, (byte) 0xA7, (byte) 0x1C, + (byte) 0xEF, (byte) 0xD1, (byte) 0x53, (byte) 0x3E, + (byte) 0x8F, (byte) 0x33, (byte) 0x26, (byte) 0x5F, + (byte) 0xEC, (byte) 0x76, (byte) 0x2A, (byte) 0x49, + (byte) 0x81, (byte) 0x88, (byte) 0xEE, (byte) 0x21, + (byte) 0xC4, (byte) 0x1A, (byte) 0xEB, (byte) 0xD9, + (byte) 0xC5, (byte) 0x39, (byte) 0x99, (byte) 0xCD, + (byte) 0xAD, (byte) 0x31, (byte) 0x8B, (byte) 0x01, + (byte) 0x18, (byte) 0x23, (byte) 0xDD, (byte) 0x1F, + (byte) 0x4E, (byte) 0x2D, (byte) 0xF9, (byte) 0x48, + (byte) 0x4F, (byte) 0xF2, (byte) 0x65, (byte) 0x8E, + (byte) 0x78, (byte) 0x5C, (byte) 0x58, (byte) 0x19, + (byte) 0x8D, (byte) 0xE5, (byte) 0x98, (byte) 0x57, + (byte) 0x67, (byte) 0x7F, (byte) 0x05, (byte) 0x64, + (byte) 0xAF, (byte) 0x63, (byte) 0xB6, (byte) 0xFE, + (byte) 0xF5, (byte) 0xB7, (byte) 0x3C, (byte) 0xA5, + (byte) 0xCE, (byte) 0xE9, (byte) 0x68, (byte) 0x44, + (byte) 0xE0, (byte) 0x4D, (byte) 0x43, (byte) 0x69, + (byte) 0x29, (byte) 0x2E, (byte) 0xAC, (byte) 0x15, + (byte) 0x59, (byte) 0xA8, (byte) 0x0A, (byte) 0x9E, + (byte) 0x6E, (byte) 0x47, (byte) 0xDF, (byte) 0x34, + (byte) 0x35, (byte) 0x6A, (byte) 0xCF, (byte) 0xDC, + (byte) 0x22, (byte) 0xC9, (byte) 0xC0, (byte) 0x9B, + (byte) 0x89, (byte) 0xD4, (byte) 0xED, (byte) 0xAB, + (byte) 0x12, (byte) 0xA2, (byte) 0x0D, (byte) 0x52, + (byte) 0xBB, (byte) 0x02, (byte) 0x2F, (byte) 0xA9, + (byte) 0xD7, (byte) 0x61, (byte) 0x1E, (byte) 0xB4, + (byte) 0x50, (byte) 0x04, (byte) 0xF6, (byte) 0xC2, + (byte) 0x16, (byte) 0x25, (byte) 0x86, (byte) 0x56, + (byte) 0x55, (byte) 0x09, (byte) 0xBE, (byte) 0x91 + } + }; + + /** + * Define the fixed p0/p1 permutations used in keyed S-box lookup. + * By changing the following constant definitions, the S-boxes will + * automatically get changed in the Twofish engine. + */ + private static final int P_00 = 1; + private static final int P_01 = 0; + private static final int P_02 = 0; + private static final int P_03 = P_01 ^ 1; + private static final int P_04 = 1; + + private static final int P_10 = 0; + private static final int P_11 = 0; + private static final int P_12 = 1; + private static final int P_13 = P_11 ^ 1; + private static final int P_14 = 0; + + private static final int P_20 = 1; + private static final int P_21 = 1; + private static final int P_22 = 0; + private static final int P_23 = P_21 ^ 1; + private static final int P_24 = 0; + + private static final int P_30 = 0; + private static final int P_31 = 1; + private static final int P_32 = 1; + private static final int P_33 = P_31 ^ 1; + private static final int P_34 = 1; + + /** Primitive polynomial for GF(256) */ + private static final int GF256_FDBK = 0x169; + private static final int GF256_FDBK_2 = 0x169 / 2; + private static final int GF256_FDBK_4 = 0x169 / 4; + + /** MDS matrix */ + private static final int[][] MDS = new int[4][256]; // blank final + + private static final int RS_GF_FDBK = 0x14D; // field generator + + /** data for hexadecimal visualisation. */ + private static final char[] HEX_DIGITS = { + '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' + }; + + +// Static code - to intialise the MDS matrix +//........................................................................... + + static { + long time = System.currentTimeMillis(); + +if (DEBUG && debuglevel > 6) { +System.out.println("Algorithm Name: "+Twofish_Properties.FULL_NAME); +System.out.println("Electronic Codebook (ECB) Mode"); +System.out.println(); +} + // + // precompute the MDS matrix + // + int[] m1 = new int[2]; + int[] mX = new int[2]; + int[] mY = new int[2]; + int i, j; + for (i = 0; i < 256; i++) { + j = P[0][i] & 0xFF; // compute all the matrix elements + m1[0] = j; + mX[0] = Mx_X( j ) & 0xFF; + mY[0] = Mx_Y( j ) & 0xFF; + + j = P[1][i] & 0xFF; + m1[1] = j; + mX[1] = Mx_X( j ) & 0xFF; + mY[1] = Mx_Y( j ) & 0xFF; + + MDS[0][i] = m1[P_00] << 0 | // fill matrix w/ above elements + mX[P_00] << 8 | + mY[P_00] << 16 | + mY[P_00] << 24; + MDS[1][i] = mY[P_10] << 0 | + mY[P_10] << 8 | + mX[P_10] << 16 | + m1[P_10] << 24; + MDS[2][i] = mX[P_20] << 0 | + mY[P_20] << 8 | + m1[P_20] << 16 | + mY[P_20] << 24; + MDS[3][i] = mX[P_30] << 0 | + m1[P_30] << 8 | + mY[P_30] << 16 | + mX[P_30] << 24; + } + + time = System.currentTimeMillis() - time; + +if (DEBUG && debuglevel > 8) { +System.out.println("=========="); +System.out.println(); +System.out.println("Static Data"); +System.out.println(); +System.out.println("MDS[0][]:"); for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(MDS[0][i*4+j])+", "); System.out.println();} +System.out.println(); +System.out.println("MDS[1][]:"); for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(MDS[1][i*4+j])+", "); System.out.println();} +System.out.println(); +System.out.println("MDS[2][]:"); for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(MDS[2][i*4+j])+", "); System.out.println();} +System.out.println(); +System.out.println("MDS[3][]:"); for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(MDS[3][i*4+j])+", "); System.out.println();} +System.out.println(); +System.out.println("Total initialization time: "+time+" ms."); +System.out.println(); +} + } + + private static final int LFSR1( int x ) { + return (x >> 1) ^ + ((x & 0x01) != 0 ? GF256_FDBK_2 : 0); + } + + private static final int LFSR2( int x ) { + return (x >> 2) ^ + ((x & 0x02) != 0 ? GF256_FDBK_2 : 0) ^ + ((x & 0x01) != 0 ? GF256_FDBK_4 : 0); + } + + private static final int Mx_1( int x ) { return x; } + private static final int Mx_X( int x ) { return x ^ LFSR2(x); } // 5B + private static final int Mx_Y( int x ) { return x ^ LFSR1(x) ^ LFSR2(x); } // EF + + +// Basic API methods +//........................................................................... + + /** + * Expand a user-supplied key material into a session key. + * + * @param key The 64/128/192/256-bit user-key to use. + * @return This cipher's round keys. + * @exception InvalidKeyException If the key is invalid. + */ + public static synchronized Object makeKey (byte[] k) + throws InvalidKeyException { +if (DEBUG) trace(IN, "makeKey("+k+")"); + if (k == null) + throw new InvalidKeyException("Empty key"); + int length = k.length; + if (!(length == 8 || length == 16 || length == 24 || length == 32)) + throw new InvalidKeyException("Incorrect key length"); + +if (DEBUG && debuglevel > 7) { +System.out.println("Intermediate Session Key Values"); +System.out.println(); +System.out.println("Raw="+toString(k)); +System.out.println(); +} + int k64Cnt = length / 8; + int subkeyCnt = ROUND_SUBKEYS + 2*ROUNDS; + int[] k32e = new int[4]; // even 32-bit entities + int[] k32o = new int[4]; // odd 32-bit entities + int[] sBoxKey = new int[4]; + // + // split user key material into even and odd 32-bit entities and + // compute S-box keys using (12, 8) Reed-Solomon code over GF(256) + // + int i, j, offset = 0; + for (i = 0, j = k64Cnt-1; i < 4 && offset < length; i++, j--) { + k32e[i] = (k[offset++] & 0xFF) | + (k[offset++] & 0xFF) << 8 | + (k[offset++] & 0xFF) << 16 | + (k[offset++] & 0xFF) << 24; + k32o[i] = (k[offset++] & 0xFF) | + (k[offset++] & 0xFF) << 8 | + (k[offset++] & 0xFF) << 16 | + (k[offset++] & 0xFF) << 24; + sBoxKey[j] = RS_MDS_Encode( k32e[i], k32o[i] ); // reverse order + } + // compute the round decryption subkeys for PHT. these same subkeys + // will be used in encryption but will be applied in reverse order. + int q, A, B; + int[] subKeys = new int[subkeyCnt]; + for (i = q = 0; i < subkeyCnt/2; i++, q += SK_STEP) { + A = F32( k64Cnt, q , k32e ); // A uses even key entities + B = F32( k64Cnt, q+SK_BUMP, k32o ); // B uses odd key entities + B = B << 8 | B >>> 24; + A += B; + subKeys[2*i ] = A; // combine with a PHT + A += B; + subKeys[2*i + 1] = A << SK_ROTL | A >>> (32-SK_ROTL); + } + // + // fully expand the table for speed + // + int k0 = sBoxKey[0]; + int k1 = sBoxKey[1]; + int k2 = sBoxKey[2]; + int k3 = sBoxKey[3]; + int b0, b1, b2, b3; + int[] sBox = new int[4 * 256]; + for (i = 0; i < 256; i++) { + b0 = b1 = b2 = b3 = i; + switch (k64Cnt & 3) { + case 1: + sBox[ 2*i ] = MDS[0][(P[P_01][b0] & 0xFF) ^ b0(k0)]; + sBox[ 2*i+1] = MDS[1][(P[P_11][b1] & 0xFF) ^ b1(k0)]; + sBox[0x200+2*i ] = MDS[2][(P[P_21][b2] & 0xFF) ^ b2(k0)]; + sBox[0x200+2*i+1] = MDS[3][(P[P_31][b3] & 0xFF) ^ b3(k0)]; + break; + case 0: // same as 4 + b0 = (P[P_04][b0] & 0xFF) ^ b0(k3); + b1 = (P[P_14][b1] & 0xFF) ^ b1(k3); + b2 = (P[P_24][b2] & 0xFF) ^ b2(k3); + b3 = (P[P_34][b3] & 0xFF) ^ b3(k3); + case 3: + b0 = (P[P_03][b0] & 0xFF) ^ b0(k2); + b1 = (P[P_13][b1] & 0xFF) ^ b1(k2); + b2 = (P[P_23][b2] & 0xFF) ^ b2(k2); + b3 = (P[P_33][b3] & 0xFF) ^ b3(k2); + case 2: // 128-bit keys + sBox[ 2*i ] = MDS[0][(P[P_01][(P[P_02][b0] & 0xFF) ^ b0(k1)] & 0xFF) ^ b0(k0)]; + sBox[ 2*i+1] = MDS[1][(P[P_11][(P[P_12][b1] & 0xFF) ^ b1(k1)] & 0xFF) ^ b1(k0)]; + sBox[0x200+2*i ] = MDS[2][(P[P_21][(P[P_22][b2] & 0xFF) ^ b2(k1)] & 0xFF) ^ b2(k0)]; + sBox[0x200+2*i+1] = MDS[3][(P[P_31][(P[P_32][b3] & 0xFF) ^ b3(k1)] & 0xFF) ^ b3(k0)]; + } + } + + Object sessionKey = new Object[] { sBox, subKeys }; + +if (DEBUG && debuglevel > 7) { +System.out.println("S-box[]:"); +for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(sBox[i*4+j])+", "); System.out.println();} +System.out.println(); +for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(sBox[256+i*4+j])+", "); System.out.println();} +System.out.println(); +for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(sBox[512+i*4+j])+", "); System.out.println();} +System.out.println(); +for(i=0;i<64;i++) { for(j=0;j<4;j++) System.out.print("0x"+intToString(sBox[768+i*4+j])+", "); System.out.println();} +System.out.println(); +System.out.println("User (odd, even) keys --> S-Box keys:"); +for(i=0;i<k64Cnt;i++) { System.out.println("0x"+intToString(k32o[i])+" 0x"+intToString(k32e[i])+" --> 0x"+intToString(sBoxKey[k64Cnt-1-i])); } +System.out.println(); +System.out.println("Round keys:"); +for(i=0;i<ROUND_SUBKEYS + 2*ROUNDS;i+=2) { System.out.println("0x"+intToString(subKeys[i])+" 0x"+intToString(subKeys[i+1])); } +System.out.println(); + +} +if (DEBUG) trace(OUT, "makeKey()"); + return sessionKey; + } + + /** + * Encrypt exactly one block of plaintext. + * + * @param in The plaintext. + * @param inOffset Index of in from which to start considering data. + * @param sessionKey The session key to use for encryption. + * @return The ciphertext generated from a plaintext using the session key. + */ + public static byte[] + blockEncrypt (byte[] in, int inOffset, Object sessionKey) { +if (DEBUG) trace(IN, "blockEncrypt("+in+", "+inOffset+", "+sessionKey+")"); + Object[] sk = (Object[]) sessionKey; // extract S-box and session key + int[] sBox = (int[]) sk[0]; + int[] sKey = (int[]) sk[1]; + +if (DEBUG && debuglevel > 6) System.out.println("PT="+toString(in, inOffset, BLOCK_SIZE)); + + int x0 = (in[inOffset++] & 0xFF) | + (in[inOffset++] & 0xFF) << 8 | + (in[inOffset++] & 0xFF) << 16 | + (in[inOffset++] & 0xFF) << 24; + int x1 = (in[inOffset++] & 0xFF) | + (in[inOffset++] & 0xFF) << 8 | + (in[inOffset++] & 0xFF) << 16 | + (in[inOffset++] & 0xFF) << 24; + int x2 = (in[inOffset++] & 0xFF) | + (in[inOffset++] & 0xFF) << 8 | + (in[inOffset++] & 0xFF) << 16 | + (in[inOffset++] & 0xFF) << 24; + int x3 = (in[inOffset++] & 0xFF) | + (in[inOffset++] & 0xFF) << 8 | + (in[inOffset++] & 0xFF) << 16 | + (in[inOffset++] & 0xFF) << 24; + + x0 ^= sKey[INPUT_WHITEN ]; + x1 ^= sKey[INPUT_WHITEN + 1]; + x2 ^= sKey[INPUT_WHITEN + 2]; + x3 ^= sKey[INPUT_WHITEN + 3]; +if (DEBUG && debuglevel > 6) System.out.println("PTw="+intToString(x0)+intToString(x1)+intToString(x2)+intToString(x3)); + + int t0, t1; + int k = ROUND_SUBKEYS; + for (int R = 0; R < ROUNDS; R += 2) { + t0 = Fe32( sBox, x0, 0 ); + t1 = Fe32( sBox, x1, 3 ); + x2 ^= t0 + t1 + sKey[k++]; + x2 = x2 >>> 1 | x2 << 31; + x3 = x3 << 1 | x3 >>> 31; + x3 ^= t0 + 2*t1 + sKey[k++]; +if (DEBUG && debuglevel > 6) System.out.println("CT"+(R)+"="+intToString(x0)+intToString(x1)+intToString(x2)+intToString(x3)); + + t0 = Fe32( sBox, x2, 0 ); + t1 = Fe32( sBox, x3, 3 ); + x0 ^= t0 + t1 + sKey[k++]; + x0 = x0 >>> 1 | x0 << 31; + x1 = x1 << 1 | x1 >>> 31; + x1 ^= t0 + 2*t1 + sKey[k++]; +if (DEBUG && debuglevel > 6) System.out.println("CT"+(R+1)+"="+intToString(x0)+intToString(x1)+intToString(x2)+intToString(x3)); + } + x2 ^= sKey[OUTPUT_WHITEN ]; + x3 ^= sKey[OUTPUT_WHITEN + 1]; + x0 ^= sKey[OUTPUT_WHITEN + 2]; + x1 ^= sKey[OUTPUT_WHITEN + 3]; +if (DEBUG && debuglevel > 6) System.out.println("CTw="+intToString(x0)+intToString(x1)+intToString(x2)+intToString(x3)); + + byte[] result = new byte[] { + (byte) x2, (byte)(x2 >>> 8), (byte)(x2 >>> 16), (byte)(x2 >>> 24), + (byte) x3, (byte)(x3 >>> 8), (byte)(x3 >>> 16), (byte)(x3 >>> 24), + (byte) x0, (byte)(x0 >>> 8), (byte)(x0 >>> 16), (byte)(x0 >>> 24), + (byte) x1, (byte)(x1 >>> 8), (byte)(x1 >>> 16), (byte)(x1 >>> 24), + }; + +if (DEBUG && debuglevel > 6) { +System.out.println("CT="+toString(result)); +System.out.println(); +} +if (DEBUG) trace(OUT, "blockEncrypt()"); + return result; + } + + /** + * Decrypt exactly one block of ciphertext. + * + * @param in The ciphertext. + * @param inOffset Index of in from which to start considering data. + * @param sessionKey The session key to use for decryption. + * @return The plaintext generated from a ciphertext using the session key. + */ + public static byte[] + blockDecrypt (byte[] in, int inOffset, Object sessionKey) { +if (DEBUG) trace(IN, "blockDecrypt("+in+", "+inOffset+", "+sessionKey+")"); + Object[] sk = (Object[]) sessionKey; // extract S-box and session key + int[] sBox = (int[]) sk[0]; + int[] sKey = (int[]) sk[1]; + +if (DEBUG && debuglevel > 6) System.out.println("CT="+toString(in, inOffset, BLOCK_SIZE)); + + int x2 = (in[inOffset++] & 0xFF) | + (in[inOffset++] & 0xFF) << 8 | + (in[inOffset++] & 0xFF) << 16 | + (in[inOffset++] & 0xFF) << 24; + int x3 = (in[inOffset++] & 0xFF) | + (in[inOffset++] & 0xFF) << 8 | + (in[inOffset++] & 0xFF) << 16 | + (in[inOffset++] & 0xFF) << 24; + int x0 = (in[inOffset++] & 0xFF) | + (in[inOffset++] & 0xFF) << 8 | + (in[inOffset++] & 0xFF) << 16 | + (in[inOffset++] & 0xFF) << 24; + int x1 = (in[inOffset++] & 0xFF) | + (in[inOffset++] & 0xFF) << 8 | + (in[inOffset++] & 0xFF) << 16 | + (in[inOffset++] & 0xFF) << 24; + + x2 ^= sKey[OUTPUT_WHITEN ]; + x3 ^= sKey[OUTPUT_WHITEN + 1]; + x0 ^= sKey[OUTPUT_WHITEN + 2]; + x1 ^= sKey[OUTPUT_WHITEN + 3]; +if (DEBUG && debuglevel > 6) System.out.println("CTw="+intToString(x2)+intToString(x3)+intToString(x0)+intToString(x1)); + + int k = ROUND_SUBKEYS + 2*ROUNDS - 1; + int t0, t1; + for (int R = 0; R < ROUNDS; R += 2) { + t0 = Fe32( sBox, x2, 0 ); + t1 = Fe32( sBox, x3, 3 ); + x1 ^= t0 + 2*t1 + sKey[k--]; + x1 = x1 >>> 1 | x1 << 31; + x0 = x0 << 1 | x0 >>> 31; + x0 ^= t0 + t1 + sKey[k--]; +if (DEBUG && debuglevel > 6) System.out.println("PT"+(ROUNDS-R)+"="+intToString(x2)+intToString(x3)+intToString(x0)+intToString(x1)); + + t0 = Fe32( sBox, x0, 0 ); + t1 = Fe32( sBox, x1, 3 ); + x3 ^= t0 + 2*t1 + sKey[k--]; + x3 = x3 >>> 1 | x3 << 31; + x2 = x2 << 1 | x2 >>> 31; + x2 ^= t0 + t1 + sKey[k--]; +if (DEBUG && debuglevel > 6) System.out.println("PT"+(ROUNDS-R-1)+"="+intToString(x2)+intToString(x3)+intToString(x0)+intToString(x1)); + } + x0 ^= sKey[INPUT_WHITEN ]; + x1 ^= sKey[INPUT_WHITEN + 1]; + x2 ^= sKey[INPUT_WHITEN + 2]; + x3 ^= sKey[INPUT_WHITEN + 3]; +if (DEBUG && debuglevel > 6) System.out.println("PTw="+intToString(x2)+intToString(x3)+intToString(x0)+intToString(x1)); + + byte[] result = new byte[] { + (byte) x0, (byte)(x0 >>> 8), (byte)(x0 >>> 16), (byte)(x0 >>> 24), + (byte) x1, (byte)(x1 >>> 8), (byte)(x1 >>> 16), (byte)(x1 >>> 24), + (byte) x2, (byte)(x2 >>> 8), (byte)(x2 >>> 16), (byte)(x2 >>> 24), + (byte) x3, (byte)(x3 >>> 8), (byte)(x3 >>> 16), (byte)(x3 >>> 24), + }; + +if (DEBUG && debuglevel > 6) { +System.out.println("PT="+toString(result)); +System.out.println(); +} +if (DEBUG) trace(OUT, "blockDecrypt()"); + return result; + } + + /** A basic symmetric encryption/decryption test. */ + public static boolean self_test() { return self_test(BLOCK_SIZE); } + + +// own methods +//........................................................................... + + private static final int b0( int x ) { return x & 0xFF; } + private static final int b1( int x ) { return (x >>> 8) & 0xFF; } + private static final int b2( int x ) { return (x >>> 16) & 0xFF; } + private static final int b3( int x ) { return (x >>> 24) & 0xFF; } + + /** + * Use (12, 8) Reed-Solomon code over GF(256) to produce a key S-box + * 32-bit entity from two key material 32-bit entities. + * + * @param k0 1st 32-bit entity. + * @param k1 2nd 32-bit entity. + * @return Remainder polynomial generated using RS code + */ + private static final int RS_MDS_Encode( int k0, int k1) { + int r = k1; + for (int i = 0; i < 4; i++) // shift 1 byte at a time + r = RS_rem( r ); + r ^= k0; + for (int i = 0; i < 4; i++) + r = RS_rem( r ); + return r; + } + + /* + * Reed-Solomon code parameters: (12, 8) reversible code:<p> + * <pre> + * g(x) = x**4 + (a + 1/a) x**3 + a x**2 + (a + 1/a) x + 1 + * </pre> + * where a = primitive root of field generator 0x14D + */ + private static final int RS_rem( int x ) { + int b = (x >>> 24) & 0xFF; + int g2 = ((b << 1) ^ ( (b & 0x80) != 0 ? RS_GF_FDBK : 0 )) & 0xFF; + int g3 = (b >>> 1) ^ ( (b & 0x01) != 0 ? (RS_GF_FDBK >>> 1) : 0 ) ^ g2 ; + int result = (x << 8) ^ (g3 << 24) ^ (g2 << 16) ^ (g3 << 8) ^ b; + return result; + } + + private static final int F32( int k64Cnt, int x, int[] k32 ) { + int b0 = b0(x); + int b1 = b1(x); + int b2 = b2(x); + int b3 = b3(x); + int k0 = k32[0]; + int k1 = k32[1]; + int k2 = k32[2]; + int k3 = k32[3]; + + int result = 0; + switch (k64Cnt & 3) { + case 1: + result = + MDS[0][(P[P_01][b0] & 0xFF) ^ b0(k0)] ^ + MDS[1][(P[P_11][b1] & 0xFF) ^ b1(k0)] ^ + MDS[2][(P[P_21][b2] & 0xFF) ^ b2(k0)] ^ + MDS[3][(P[P_31][b3] & 0xFF) ^ b3(k0)]; + break; + case 0: // same as 4 + b0 = (P[P_04][b0] & 0xFF) ^ b0(k3); + b1 = (P[P_14][b1] & 0xFF) ^ b1(k3); + b2 = (P[P_24][b2] & 0xFF) ^ b2(k3); + b3 = (P[P_34][b3] & 0xFF) ^ b3(k3); + case 3: + b0 = (P[P_03][b0] & 0xFF) ^ b0(k2); + b1 = (P[P_13][b1] & 0xFF) ^ b1(k2); + b2 = (P[P_23][b2] & 0xFF) ^ b2(k2); + b3 = (P[P_33][b3] & 0xFF) ^ b3(k2); + case 2: // 128-bit keys (optimize for this case) + result = + MDS[0][(P[P_01][(P[P_02][b0] & 0xFF) ^ b0(k1)] & 0xFF) ^ b0(k0)] ^ + MDS[1][(P[P_11][(P[P_12][b1] & 0xFF) ^ b1(k1)] & 0xFF) ^ b1(k0)] ^ + MDS[2][(P[P_21][(P[P_22][b2] & 0xFF) ^ b2(k1)] & 0xFF) ^ b2(k0)] ^ + MDS[3][(P[P_31][(P[P_32][b3] & 0xFF) ^ b3(k1)] & 0xFF) ^ b3(k0)]; + break; + } + return result; + } + + private static final int Fe32( int[] sBox, int x, int R ) { + return sBox[ 2*_b(x, R ) ] ^ + sBox[ 2*_b(x, R+1) + 1] ^ + sBox[0x200 + 2*_b(x, R+2) ] ^ + sBox[0x200 + 2*_b(x, R+3) + 1]; + } + + private static final int _b( int x, int N) { + int result = 0; + switch (N%4) { + case 0: result = b0(x); break; + case 1: result = b1(x); break; + case 2: result = b2(x); break; + case 3: result = b3(x); break; + } + return result; + } + + /** @return The length in bytes of the Algorithm input block. */ + public static int blockSize() { return BLOCK_SIZE; } + + /** A basic symmetric encryption/decryption test for a given key size. */ + private static boolean self_test (int keysize) { +if (DEBUG) trace(IN, "self_test("+keysize+")"); + boolean ok = false; + try { + byte[] kb = new byte[keysize]; + byte[] pt = new byte[BLOCK_SIZE]; + int i; + + for (i = 0; i < keysize; i++) + kb[i] = (byte) i; + for (i = 0; i < BLOCK_SIZE; i++) + pt[i] = (byte) i; + +if (DEBUG && debuglevel > 6) { +System.out.println("=========="); +System.out.println(); +System.out.println("KEYSIZE="+(8*keysize)); +System.out.println("KEY="+toString(kb)); +System.out.println(); +} + Object key = makeKey(kb); + +if (DEBUG && debuglevel > 6) { +System.out.println("Intermediate Ciphertext Values (Encryption)"); +System.out.println(); +} + byte[] ct = blockEncrypt(pt, 0, key); + +if (DEBUG && debuglevel > 6) { +System.out.println("Intermediate Plaintext Values (Decryption)"); +System.out.println(); +} + byte[] cpt = blockDecrypt(ct, 0, key); + + ok = areEqual(pt, cpt); + if (!ok) + throw new RuntimeException("Symmetric operation failed"); + } catch (Exception x) { +if (DEBUG && debuglevel > 0) { + debug("Exception encountered during self-test: " + x.getMessage()); + x.printStackTrace(); +} + } +if (DEBUG && debuglevel > 0) debug("Self-test OK? " + ok); +if (DEBUG) trace(OUT, "self_test()"); + return ok; + } + + +// utility static methods (from cryptix.util.core ArrayUtil and Hex classes) +//........................................................................... + + /** @return True iff the arrays have identical contents. */ + private static boolean areEqual (byte[] a, byte[] b) { + int aLength = a.length; + if (aLength != b.length) + return false; + for (int i = 0; i < aLength; i++) + if (a[i] != b[i]) + return false; + return true; + } + + /** + * Returns a string of 8 hexadecimal digits (most significant + * digit first) corresponding to the integer <i>n</i>, which is + * treated as unsigned. + */ + private static String intToString (int n) { + char[] buf = new char[8]; + for (int i = 7; i >= 0; i--) { + buf[i] = HEX_DIGITS[n & 0x0F]; + n >>>= 4; + } + return new String(buf); + } + + /** + * Returns a string of hexadecimal digits from a byte array. Each + * byte is converted to 2 hex symbols. + */ + private static String toString (byte[] ba) { + return toString(ba, 0, ba.length); + } + private static String toString (byte[] ba, int offset, int length) { + char[] buf = new char[length * 2]; + for (int i = offset, j = 0, k; i < offset+length; ) { + k = ba[i++]; + buf[j++] = HEX_DIGITS[(k >>> 4) & 0x0F]; + buf[j++] = HEX_DIGITS[ k & 0x0F]; + } + return new String(buf); + } + + +// main(): use to generate the Intermediate Values KAT +//........................................................................... + + public static void main (String[] args) { + self_test(16); + self_test(24); + self_test(32); + } +}
\ No newline at end of file diff --git a/jobb/src/Twofish/Twofish_Properties.java b/jobb/src/Twofish/Twofish_Properties.java new file mode 100644 index 0000000..2098c8f --- /dev/null +++ b/jobb/src/Twofish/Twofish_Properties.java @@ -0,0 +1,197 @@ +// $Id: $ +// +// $Log: $ +// Revision 1.0 1998/03/24 raif +// + start of history. +// +// $Endlog$ +/* + * Copyright (c) 1997, 1998 Systemics Ltd on behalf of + * the Cryptix Development Team. All rights reserved. + */ +package Twofish; + +import java.io.InputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.Properties; + +/** + * This class acts as a central repository for an algorithm specific + * properties. It reads an (algorithm).properties file containing algorithm- + * specific properties. When using the AES-Kit, this (algorithm).properties + * file is located in the (algorithm).jar file produced by the "jarit" batch/ + * script command.<p> + * + * <b>Copyright</b> © 1997, 1998 + * <a href="http://www.systemics.com/">Systemics Ltd</a> on behalf of the + * <a href="http://www.systemics.com/docs/cryptix/">Cryptix Development Team</a>. + * <br>All rights reserved.<p> + * + * <b>$Revision: $</b> + * @author David Hopwood + * @author Jill Baker + * @author Raif S. Naffah + */ +public class Twofish_Properties // implicit no-argument constructor +{ +// Constants and variables with relevant static code +//........................................................................... + + static final boolean GLOBAL_DEBUG = false; + + static final String ALGORITHM = "Twofish"; + static final double VERSION = 0.2; + static final String FULL_NAME = ALGORITHM + " ver. " + VERSION; + static final String NAME = "Twofish_Properties"; + + static final Properties properties = new Properties(); + + /** Default properties in case .properties file was not found. */ + private static final String[][] DEFAULT_PROPERTIES = { + {"Trace.Twofish_Algorithm", "true"}, + {"Debug.Level.*", "1"}, + {"Debug.Level.Twofish_Algorithm", "9"}, + }; + + static { +if (GLOBAL_DEBUG) System.err.println(">>> " + NAME + ": Looking for " + ALGORITHM + " properties"); + String it = ALGORITHM + ".properties"; + InputStream is = Twofish_Properties.class.getResourceAsStream(it); + boolean ok = is != null; + if (ok) + try { + properties.load(is); + is.close(); +if (GLOBAL_DEBUG) System.err.println(">>> " + NAME + ": Properties file loaded OK..."); + } catch (Exception x) { + ok = false; + } + if (!ok) { +if (GLOBAL_DEBUG) System.err.println(">>> " + NAME + ": WARNING: Unable to load \"" + it + "\" from CLASSPATH."); +if (GLOBAL_DEBUG) System.err.println(">>> " + NAME + ": Will use default values instead..."); + int n = DEFAULT_PROPERTIES.length; + for (int i = 0; i < n; i++) + properties.put( + DEFAULT_PROPERTIES[i][0], DEFAULT_PROPERTIES[i][1]); +if (GLOBAL_DEBUG) System.err.println(">>> " + NAME + ": Default properties now set..."); + } + } + + +// Properties methods (excluding load and save, which are deliberately not +// supported). +//........................................................................... + + /** Get the value of a property for this algorithm. */ + public static String getProperty (String key) { + return properties.getProperty(key); + } + + /** + * Get the value of a property for this algorithm, or return + * <i>value</i> if the property was not set. + */ + public static String getProperty (String key, String value) { + return properties.getProperty(key, value); + } + + /** List algorithm properties to the PrintStream <i>out</i>. */ + public static void list (PrintStream out) { + list(new PrintWriter(out, true)); + } + + /** List algorithm properties to the PrintWriter <i>out</i>. */ + public static void list (PrintWriter out) { + out.println("#"); + out.println("# ----- Begin "+ALGORITHM+" properties -----"); + out.println("#"); + String key, value; + Enumeration en = properties.propertyNames(); + while (en.hasMoreElements()) { + key = (String) en.nextElement(); + value = getProperty(key); + out.println(key + " = " + value); + } + out.println("#"); + out.println("# ----- End "+ALGORITHM+" properties -----"); + } + +// public synchronized void load(InputStream in) throws IOException {} + + public static Enumeration propertyNames() { + return properties.propertyNames(); + } + +// public void save (OutputStream os, String comment) {} + + +// Developer support: Tracing and debugging enquiry methods (package-private) +//........................................................................... + + /** + * Return true if tracing is requested for a given class.<p> + * + * User indicates this by setting the tracing <code>boolean</code> + * property for <i>label</i> in the <code>(algorithm).properties</code> + * file. The property's key is "<code>Trace.<i>label</i></code>".<p> + * + * @param label The name of a class. + * @return True iff a boolean true value is set for a property with + * the key <code>Trace.<i>label</i></code>. + */ + static boolean isTraceable (String label) { + String s = getProperty("Trace." + label); + if (s == null) + return false; + return new Boolean(s).booleanValue(); + } + + /** + * Return the debug level for a given class.<p> + * + * User indicates this by setting the numeric property with key + * "<code>Debug.Level.<i>label</i></code>".<p> + * + * If this property is not set, "<code>Debug.Level.*</code>" is looked up + * next. If neither property is set, or if the first property found is + * not a valid decimal integer, then this method returns 0. + * + * @param label The name of a class. + * @return The required debugging level for the designated class. + */ + static int getLevel (String label) { + String s = getProperty("Debug.Level." + label); + if (s == null) { + s = getProperty("Debug.Level.*"); + if (s == null) + return 0; + } + try { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + return 0; + } + } + + /** + * Return the PrintWriter to which tracing and debugging output is to + * be sent.<p> + * + * User indicates this by setting the property with key <code>Output</code> + * to the literal <code>out</code> or <code>err</code>.<p> + * + * By default or if the set value is not allowed, <code>System.err</code> + * will be used. + */ + static PrintWriter getOutput() { + PrintWriter pw; + String name = getProperty("Output"); + if (name != null && name.equals("out")) + pw = new PrintWriter(System.out, true); + else + pw = new PrintWriter(System.err, true); + return pw; + } +}
\ No newline at end of file diff --git a/jobb/src/com/android/jobb/Base64.java b/jobb/src/com/android/jobb/Base64.java new file mode 100644 index 0000000..7301763 --- /dev/null +++ b/jobb/src/com/android/jobb/Base64.java @@ -0,0 +1,276 @@ +/* +Copyright (c) 2000 The Legion Of The Bouncy Castle +(http://www.bouncycastle.org) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package com.android.jobb; + +public class Base64 +{ + private static final byte[] encodingTable = + { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', + (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', + (byte)'7', (byte)'8', (byte)'9', + (byte)'+', (byte)'/' + }; + + /** + * encode the input data producing a base 64 encoded byte array. + * + * @return a byte array containing the base 64 encoded data. + */ + public static byte[] encode( + byte[] data) + { + byte[] bytes; + + int modulus = data.length % 3; + if (modulus == 0) + { + bytes = new byte[4 * data.length / 3]; + } + else + { + bytes = new byte[4 * ((data.length / 3) + 1)]; + } + + int dataLength = (data.length - modulus); + int a1, a2, a3; + for (int i = 0, j = 0; i < dataLength; i += 3, j += 4) + { + a1 = data[i] & 0xff; + a2 = data[i + 1] & 0xff; + a3 = data[i + 2] & 0xff; + + bytes[j] = encodingTable[(a1 >>> 2) & 0x3f]; + bytes[j + 1] = encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]; + bytes[j + 2] = encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]; + bytes[j + 3] = encodingTable[a3 & 0x3f]; + } + + /* + * process the tail end. + */ + int b1, b2, b3; + int d1, d2; + + switch (modulus) + { + case 0: /* nothing left to do */ + break; + case 1: + d1 = data[data.length - 1] & 0xff; + b1 = (d1 >>> 2) & 0x3f; + b2 = (d1 << 4) & 0x3f; + + bytes[bytes.length - 4] = encodingTable[b1]; + bytes[bytes.length - 3] = encodingTable[b2]; + bytes[bytes.length - 2] = (byte)'='; + bytes[bytes.length - 1] = (byte)'='; + break; + case 2: + d1 = data[data.length - 2] & 0xff; + d2 = data[data.length - 1] & 0xff; + + b1 = (d1 >>> 2) & 0x3f; + b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f; + b3 = (d2 << 2) & 0x3f; + + bytes[bytes.length - 4] = encodingTable[b1]; + bytes[bytes.length - 3] = encodingTable[b2]; + bytes[bytes.length - 2] = encodingTable[b3]; + bytes[bytes.length - 1] = (byte)'='; + break; + } + + return bytes; + } + + /* + * set up the decoding table. + */ + private static final byte[] decodingTable; + + static + { + decodingTable = new byte[128]; + + for (int i = 'A'; i <= 'Z'; i++) + { + decodingTable[i] = (byte)(i - 'A'); + } + + for (int i = 'a'; i <= 'z'; i++) + { + decodingTable[i] = (byte)(i - 'a' + 26); + } + + for (int i = '0'; i <= '9'; i++) + { + decodingTable[i] = (byte)(i - '0' + 52); + } + + decodingTable['+'] = 62; + decodingTable['/'] = 63; + } + + /** + * decode the base 64 encoded input data. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + byte[] data) + { + byte[] bytes; + byte b1, b2, b3, b4; + + if (data[data.length - 2] == '=') + { + bytes = new byte[(((data.length / 4) - 1) * 3) + 1]; + } + else if (data[data.length - 1] == '=') + { + bytes = new byte[(((data.length / 4) - 1) * 3) + 2]; + } + else + { + bytes = new byte[((data.length / 4) * 3)]; + } + + for (int i = 0, j = 0; i < data.length - 4; i += 4, j += 3) + { + b1 = decodingTable[data[i]]; + b2 = decodingTable[data[i + 1]]; + b3 = decodingTable[data[i + 2]]; + b4 = decodingTable[data[i + 3]]; + + bytes[j] = (byte)((b1 << 2) | (b2 >> 4)); + bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2)); + bytes[j + 2] = (byte)((b3 << 6) | b4); + } + + if (data[data.length - 2] == '=') + { + b1 = decodingTable[data[data.length - 4]]; + b2 = decodingTable[data[data.length - 3]]; + + bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4)); + } + else if (data[data.length - 1] == '=') + { + b1 = decodingTable[data[data.length - 4]]; + b2 = decodingTable[data[data.length - 3]]; + b3 = decodingTable[data[data.length - 2]]; + + bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4)); + bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2)); + } + else + { + b1 = decodingTable[data[data.length - 4]]; + b2 = decodingTable[data[data.length - 3]]; + b3 = decodingTable[data[data.length - 2]]; + b4 = decodingTable[data[data.length - 1]]; + + bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4)); + bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2)); + bytes[bytes.length - 1] = (byte)((b3 << 6) | b4); + } + + return bytes; + } + + /** + * decode the base 64 encoded String data. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + String data) + { + byte[] bytes; + byte b1, b2, b3, b4; + + if (data.charAt(data.length() - 2) == '=') + { + bytes = new byte[(((data.length() / 4) - 1) * 3) + 1]; + } + else if (data.charAt(data.length() - 1) == '=') + { + bytes = new byte[(((data.length() / 4) - 1) * 3) + 2]; + } + else + { + bytes = new byte[((data.length() / 4) * 3)]; + } + + for (int i = 0, j = 0; i < data.length() - 4; i += 4, j += 3) + { + b1 = decodingTable[data.charAt(i)]; + b2 = decodingTable[data.charAt(i + 1)]; + b3 = decodingTable[data.charAt(i + 2)]; + b4 = decodingTable[data.charAt(i + 3)]; + + bytes[j] = (byte)((b1 << 2) | (b2 >> 4)); + bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2)); + bytes[j + 2] = (byte)((b3 << 6) | b4); + } + + if (data.charAt(data.length() - 2) == '=') + { + b1 = decodingTable[data.charAt(data.length() - 4)]; + b2 = decodingTable[data.charAt(data.length() - 3)]; + + bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4)); + } + else if (data.charAt(data.length() - 1) == '=') + { + b1 = decodingTable[data.charAt(data.length() - 4)]; + b2 = decodingTable[data.charAt(data.length() - 3)]; + b3 = decodingTable[data.charAt(data.length() - 2)]; + + bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4)); + bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2)); + } + else + { + b1 = decodingTable[data.charAt(data.length() - 4)]; + b2 = decodingTable[data.charAt(data.length() - 3)]; + b3 = decodingTable[data.charAt(data.length() - 2)]; + b4 = decodingTable[data.charAt(data.length() - 1)]; + + bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4)); + bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2)); + bytes[bytes.length - 1] = (byte)((b3 << 6) | b4); + } + + return bytes; + } +} diff --git a/jobb/src/com/android/jobb/Encoder.java b/jobb/src/com/android/jobb/Encoder.java new file mode 100644 index 0000000..15c01f4 --- /dev/null +++ b/jobb/src/com/android/jobb/Encoder.java @@ -0,0 +1,33 @@ +/* + * 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 java.io.IOException; +import java.io.OutputStream; + +/** + * Encode and decode byte arrays (typically from binary to 7-bit ASCII + * encodings). + */ +public interface Encoder +{ + int encode(byte[] data, int off, int length, OutputStream out) throws IOException; + + int decode(byte[] data, int off, int length, OutputStream out) throws IOException; + + int decode(String data, OutputStream out) throws IOException; +} diff --git a/jobb/src/com/android/jobb/EncryptedBlockFile.java b/jobb/src/com/android/jobb/EncryptedBlockFile.java new file mode 100644 index 0000000..deb75c4 --- /dev/null +++ b/jobb/src/com/android/jobb/EncryptedBlockFile.java @@ -0,0 +1,379 @@ +/* + * 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); + } +} diff --git a/jobb/src/com/android/jobb/Main.java b/jobb/src/com/android/jobb/Main.java new file mode 100644 index 0000000..a4ea1fc --- /dev/null +++ b/jobb/src/com/android/jobb/Main.java @@ -0,0 +1,607 @@ +/* + * 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 de.waldheinz.fs.BlockDevice; +import de.waldheinz.fs.FsDirectory; +import de.waldheinz.fs.FsDirectoryEntry; +import de.waldheinz.fs.FsFile; +import de.waldheinz.fs.fat.BootSector; +import de.waldheinz.fs.fat.Fat; +import de.waldheinz.fs.fat.FatFile; +import de.waldheinz.fs.fat.FatFileSystem; +import de.waldheinz.fs.fat.FatLfnDirectory; +import de.waldheinz.fs.fat.FatLfnDirectoryEntry; +import de.waldheinz.fs.fat.FatType; +import de.waldheinz.fs.fat.FatUtils; +import de.waldheinz.fs.fat.SuperFloppyFormatter; +import de.waldheinz.fs.util.FileDisk; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Iterator; +import java.util.Stack; + +public class Main { + + private static final int BLOCK_SIZE = 512; // MUST BE 512 + + public static void printArgs() { + System.out.println("Jobb -- Create OBB files for use on Android"); + System.out.println(); + System.out.println(" -d <directory> Use <directory> as input/output for OBB files"); + System.out.println(" -k <key> Use <key> as password to encrypt/decrypt OBB file"); + System.out.println(" -o <filename> Write OBB file out to <filename>"); + System.out.println(" -v Verbose mode"); + System.out.println(" -h Help; this usage screen"); + System.out.println(" -pn <package> Package name for OBB file"); + System.out.println(" -pv <version> Package version for OBB file"); + System.out.println(" -ov Set overlay flag"); + System.out.println(" -dump <file> Parse and dump OBB file"); + System.out.println(" -about Notices about this tool"); + System.out.println(); + System.out.println("Example: Dump the contents of the encrypted OBB file to the directory"); + System.out.println(" jobb -dump myfile.obb -d ./mydirectory -k mypassword"); + System.out.println(); + System.out.println("Example: Create an encrypted OBB file by recursing the directory path"); + System.out.println(" jobb -pn package.name -pv 1 -d ./mydirectory -k mypassword"); + } + + static String sDirectory; + static File sDirectoryFile; + static boolean sHasOutputDirectory; + static String sKey; + static String sOutputFile; + static boolean sVerboseMode; + static String sPackageName; + static int sPackageVersion = -1; + static byte[] sSalt; + static boolean sOverlay; + static int sFlags; + static String sInputFile; + static byte[] sFishKey; + + private interface FileProcessor { + void processFile(File f); + + void processDirectory(File f); + + /** + * @param dir + */ + void endDirectory(File dir); + } + + static ByteBuffer sTempBuf = ByteBuffer.allocate(1024*1024); + + static public void dumpDirectory(FsDirectory dir, int tabStop, File curDirectory) throws IOException { + Iterator<FsDirectoryEntry> i = dir.iterator(); + while (i.hasNext()) { + final FsDirectoryEntry e = i.next(); + if (e.isDirectory()) { + for (int idx = 0; idx < tabStop; idx++) + System.out.print(' '); + if (e.getName().equals(".") || e.getName().equals("..")) + continue; + for (int idx = 0; idx < tabStop; idx++) + System.out.print(" "); + System.out.println("[" + e + "]"); + dumpDirectory(e.getDirectory(), tabStop + 1, new File(curDirectory, e.getName())); + } else { + for ( int idx = 0; idx < tabStop; idx++ ) System.out.print(" "); + System.out.println( e ); + if ( sHasOutputDirectory ) { + if ( !curDirectory.exists() ) { + if ( false == curDirectory.mkdirs() ) { + throw new IOException("Unable to create directory: " + curDirectory); + } + } + File curFile = new File(curDirectory, e.getName()); + if ( curFile.exists() ) { + throw new IOException("File exists: " + curFile); + } else { + FsFile f = e.getFile(); + FileOutputStream fos = null; + try { + fos = new FileOutputStream(curFile); + FileChannel outputChannel = fos.getChannel(); + int capacity = sTempBuf.capacity(); + long length = f.getLength(); + for ( long pos = 0; pos < length; pos++ ) { + int readLength = (int)(length-pos > capacity ? capacity : length-pos); + sTempBuf.rewind(); + sTempBuf.limit(readLength); + f.read(pos, sTempBuf); + sTempBuf.rewind(); + while(sTempBuf.remaining() > 0) + outputChannel.write(sTempBuf); + pos += readLength; + } + } finally { + if ( null != fos ) fos.close(); + } + } + } + } + } + } + + /** + * @param args + */ + public static void main(String[] args) { + boolean displayHelp = false; + boolean isEncrypted = false; + try { + for (int i = 0; i < args.length; i++) { + String curArg = args[i]; + if (curArg.equals("-d")) { + sDirectory = args[++i]; + sDirectoryFile = new File(sDirectory); + sHasOutputDirectory = true; + } else if (curArg.equals("-k")) { + isEncrypted = true; + sKey = args[++i]; + } else if (curArg.equals("-o")) { + sOutputFile = args[++i]; + } else if (curArg.equals("-h")) { + displayHelp = true; + } else if (curArg.equals("-v")) { + sVerboseMode = true; + } else if (curArg.equals("-pn")) { + sPackageName = args[++i]; + } else if (curArg.equals("-pv")) { + sPackageVersion = Integer.parseInt(args[++i]); + } else if (curArg.equals("-ov")) { + sFlags |= ObbFile.OBB_OVERLAY; + } else if (curArg.equals("-dump")) { + sInputFile = args[++i]; + } else if (curArg.equals("-salt")) { + // special case --- use same salt + String saltString = args[++i]; + BigInteger bi = new BigInteger(saltString, 16); + sSalt = bi.toByteArray(); + if ( sSalt.length != PBKDF.SALT_LEN ) { + displayHelp = true; + } + } else if (curArg.equals("-about")) { + System.out.println("-------------------------------------------------------------------------------"); + System.out.println("Portions of this code:"); + System.out.println("-------------------------------------------------------------------------------"); + System.out.println("Copyright (c) 2000 The Legion Of The Bouncy Castle"); + System.out.println("(http://www.bouncycastle.org)"); + System.out.println(); + System.out.println("Permission is hereby granted, free of charge, to any person obtaining"); + System.out.println("a copy of this software and associated documentation files (the \"Software\""); + System.out.println("to deal in the Software without restriction, including without limitation"); + System.out.println("the rights to use, copy, modify, merge, publish, distribute, sublicense"); + System.out.println("and/or sell copies of the Software, and to permit persons to whom the Software"); + System.out.println("is furnished to do so, subject to the following conditions:"); + System.out.println(); + System.out.println("The above copyright notice and this permission notice shall be included in all"); + System.out.println("copies or substantial portions of the Software."); + System.out.println("-------------------------------------------------------------------------------"); + System.out.println("Twofish is uncopyrighted and license-free, and was created and analyzed by:"); + System.out.println("Bruce Schneier - John Kelsey - Doug Whiting"); + System.out.println("David Wagner - Chris Hall - Niels Ferguson"); + System.out.println("-------------------------------------------------------------------------------"); + System.out.println("Cryptix General License"); + System.out.println(); + System.out.println("Copyright (c) 1995-2005 The Cryptix Foundation Limited."); + System.out.println("All rights reserved."); + System.out.println(""); + System.out.println("Redistribution and use in source and binary forms, with or without"); + System.out.println("modification, are permitted provided that the following conditions are"); + System.out.println("met:"); + System.out.println(); + System.out.println(" 1. Redistributions of source code must retain the copyright notice,"); + System.out.println(" this list of conditions and the following disclaimer."); + System.out.println(" 2. Redistributions in binary form must reproduce the above copyright"); + System.out.println(" notice, this list of conditions and the following disclaimer in"); + System.out.println(" the documentation and/or other materials provided with the"); + System.out.println(" distribution."); + System.out.println("-------------------------------------------------------------------------------"); + return; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + displayHelp = true; + } + if (null != sInputFile) { + ObbFile obbFile = new ObbFile(); + obbFile.readFrom(sInputFile); + System.out.print("Package Name: "); + System.out.println(obbFile.mPackageName); + System.out.print("Package Version: "); + System.out.println(obbFile.mPackageVersion); + if (0 != (obbFile.mFlags & ObbFile.OBB_SALTED)) { + System.out.print("SALT: "); + BigInteger bi = new BigInteger(obbFile.mSalt); + System.out.println(bi.toString(16)); + System.out.println(); + if ( null == sKey ) { + System.out.println("Encrypted file. Please add password."); + return; + } + try { + sFishKey = PBKDF.getKey(sKey,obbFile.mSalt); + bi = new BigInteger(sFishKey); + System.out.println(bi.toString(16)); + } catch (InvalidKeyException e) { + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + isEncrypted = true; + } else { + isEncrypted = false; + } + File obbInputFile = new File(sInputFile); + + BlockDevice fd; + try { + if ( isEncrypted ) { + EncryptedBlockFile ebf = new EncryptedBlockFile(sFishKey, obbInputFile, "r"); + fd = new FileDisk(ebf, ebf.getEncryptedFileChannel(), true); + } else { + fd = new FileDisk(obbInputFile, true); + } + final FatFileSystem fatFs = FatFileSystem.read(fd, true); + final BootSector bs = fatFs.getBootSector(); + final FsDirectory rootDir = fatFs.getRoot(); + if (sVerboseMode) { + System.out.print("Filesystem Type: "); + FatType ft = bs.getFatType(); + if (ft == FatType.FAT32) { + System.out.println("FAT32"); + } else if (ft == FatType.FAT16) { + System.out.println("FAT16"); + } else if (ft == FatType.FAT12) { + System.out.println("FAT12"); + } else { + System.out.println("Unknown"); + } + System.out.print(" OEM Name: "); + System.out.println(bs.getOemName()); + System.out.print(" Bytes Per Sector: "); + System.out.println(bs.getBytesPerSector()); + System.out.print("Sectors per cluster: "); + System.out.println(bs.getSectorsPerCluster()); + System.out.print(" Reserved Sectors: "); + System.out.println(bs.getNrReservedSectors()); + System.out.print(" Fats: "); + System.out.println(bs.getNrFats()); + System.out.print(" Root Dir Entries: "); + System.out.println(bs.getRootDirEntryCount()); + System.out.print(" Medium Descriptor: "); + System.out.println(bs.getMediumDescriptor()); + System.out.print(" Sectors: "); + System.out.println(bs.getSectorCount()); + System.out.print(" Sectors Per Fat: "); + System.out.println(bs.getSectorsPerFat()); + System.out.print(" Heads: "); + System.out.println(bs.getNrHeads()); + System.out.print(" Hidden Sectors: "); + System.out.println(bs.getNrHiddenSectors()); + System.out.print(" Fat Offset: "); + System.out.println(FatUtils.getFatOffset(bs, 0)); + System.out.println(" RootDir: " + rootDir); + } + dumpDirectory(rootDir, 0, sDirectoryFile); + } catch (IOException e) { + e.printStackTrace(); + } catch (InvalidKeyException e) { + e.printStackTrace(); + } + return; + } + boolean printArgs; + if (displayHelp) { + printArgs = true; + } else if ( null == sDirectory ) { + printArgs = true; + System.out.println("A directory to be recursed through [-d directory] is required when creating an OBB filesystem."); + } else if ( null == sOutputFile ) { + printArgs = true; + System.out.println("An output filename [-o outputfile] is required when creating an OBB filesystem."); + } else if ( null == sPackageName ) { + printArgs = true; + System.out.println("A package name [-pn package] is required when creating an OBB filesystem."); + } else if ( -1 == sPackageVersion ) { + printArgs = true; + System.out.println("A package version [-pv package] is required when creating an OBB filesystem."); + } else { + printArgs = false; + } + if (printArgs) { + printArgs(); + } else { + if (null != sKey) { + if ( null == sSalt ) { + sSalt = PBKDF.getRandomSalt(); + } + try { + sFishKey = PBKDF.getKey(sKey, sSalt); + } catch (Exception e) { + e.printStackTrace(); + return; + } + sFlags |= ObbFile.OBB_SALTED; + if (sVerboseMode) { + System.out.println("Crypto: "); + System.out.print("SALT: "); + for (byte b : sSalt) { + int out = b & 0xFF; + System.out.print(Integer.toHexString(out)); + System.out.print(" "); + } + System.out.print("\t"); + System.out.print("KEY: "); + for (byte b : sFishKey) { + int out = b & 0xFF; + System.out.print(Integer.toHexString(out)); + System.out.print(" "); + } + System.out.println(); + } + } + if (sVerboseMode) { + System.out.println("Scanning directory: " + sDirectory); + } + final File f = new File(sDirectory); + + long fileSize = getTotalFileSize(f, 0); + fileSize = getTotalFileSize(f, BLOCK_SIZE*SuperFloppyFormatter.clusterSizeFromSize(fileSize, BLOCK_SIZE)); + if (sVerboseMode) { + System.out.println("Total Files: " + fileSize); + } + long numSectors = fileSize / BLOCK_SIZE; + long clusterSize = SuperFloppyFormatter.clusterSizeFromSize(fileSize, BLOCK_SIZE); + long fatOverhead = 2*numSectors/clusterSize*4; + fatOverhead += clusterSize*BLOCK_SIZE - fatOverhead % (clusterSize*BLOCK_SIZE); + fatOverhead += 2*clusterSize; //start at second cluster + if (sVerboseMode) { + System.out.println("FAT Overhead: " + fatOverhead); + } + long clusterSizeInBytes = clusterSize * BLOCK_SIZE; + long filesystemSize = (( numSectors ) * BLOCK_SIZE + fatOverhead + clusterSizeInBytes -1 ) / clusterSizeInBytes * clusterSizeInBytes; + if (sVerboseMode) { + System.out.println("Filesystem Size: " + filesystemSize); + } + File fsFile = new File(sOutputFile); + if (fsFile.exists()) + fsFile.delete(); + try { + BlockDevice fd; + if ( isEncrypted ) { + try { + EncryptedBlockFile ebf = new EncryptedBlockFile(sFishKey, fsFile, "rw"); + ebf.setLength(filesystemSize); + fd = new FileDisk(ebf, ebf.getEncryptedFileChannel(), false); + } catch (InvalidKeyException e) { + e.printStackTrace(); + return; + } + } else { + fd = FileDisk.create(fsFile, filesystemSize); + } + // fat type set based on device size by SuperFloppyFormatter + final FatFileSystem fs = SuperFloppyFormatter.get(fd).format(); + final String rootPath = f.getAbsolutePath(); + // add the files into the filesystem + processAllFiles(f, new FileProcessor() { + Stack<FatLfnDirectory> mCurDir = new Stack<FatLfnDirectory>(); + + @Override + public void processDirectory(File curFile) { + String directory = curFile.getAbsolutePath().substring(rootPath.length()); + if (sVerboseMode) { + System.out.println("Processing Directory: " + directory + " at cluster " + fs.getFat().getLastFreeCluster()); + } + FatLfnDirectory curDir = fs.getRoot(); + if (directory.length() > 0) { + File tempFile = new File(directory); + Stack<String> pathStack = new Stack<String>(); + do { + pathStack.push(tempFile.getName()); + } while (null != (tempFile = tempFile.getParentFile())); + while (!pathStack.empty()) { + String name = pathStack.pop(); + if (0 == name.length()) + continue; + FatLfnDirectoryEntry entry = curDir.getEntry(name); + if (null != entry) { + if (!entry.isDirectory()) { + throw new RuntimeException( + "File path not FAT compatible - naming conflict!"); + } + } else { + try { + if (sVerboseMode) { + System.out.println("Adding Directory: " + name); + } + entry = curDir.addDirectory(name); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Error adding directory!"); + } + } + try { + curDir = entry.getDirectory(); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Error getting directory"); + } + } + } + mCurDir.push(curDir); + } + + @Override + public void processFile(File curFile) { + FatLfnDirectoryEntry entry; + FatLfnDirectory curDir = mCurDir.peek(); + try { + if (sVerboseMode) { + System.out.println("Adding file: " + + curFile.getAbsolutePath().substring(rootPath.length()) + + " with length " + curFile.length() + " at cluster " + fs.getFat().getLastFreeCluster()); + } + entry = curDir.addFile(curFile.getName()); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Error adding file with name: " + + curFile.getName()); + } + ReadableByteChannel channel = null; + try { + FatFile f = entry.getFile(); + channel = new FileInputStream(curFile).getChannel(); + ByteBuffer buf = ByteBuffer.allocateDirect(1024 * 512); + int numRead = 0; + long offset = 0; + while (true) { + buf.clear(); + numRead = channel.read(buf); + if (numRead < 0) + break; + buf.rewind(); + buf.limit(numRead); + f.write(offset, buf); + offset += numRead; + } + f.flush(); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Error getting/writing file with name: " + + curFile.getName()); + } finally { + if (null != channel) + try { + channel.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @Override + public void endDirectory(File dir) { + mCurDir.pop(); + } + + }); + fs.flush(); + fs.close(); + Fat fat = fs.getFat(); + ObbFile ob = new ObbFile(); + ob.setPackageName(sPackageName); + ob.setPackageVersion(sPackageVersion); + ob.setFlags(sFlags); + if (null != sSalt) { + ob.setSalt(sSalt); + } + ob.writeTo(fsFile); + if (sVerboseMode) { + System.out.println("Success!"); + System.out.println("" + fs.getTotalSpace() + " bytes total"); + System.out.println("" + fs.getFreeSpace() + " bytes free"); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public static long getTotalFileSize(File dir, final int clusterSize) { + final long[] mSize = new long[3]; + final boolean calculateSlop = clusterSize > 0; + processAllFiles(dir, new FileProcessor() { + Stack<int[]> mDirLen = new Stack<int[]>(); + + @Override + public void processFile(File f) { + if (sVerboseMode) { + System.out.println("Adding size for file: " + f.getAbsolutePath()); + } + long length = f.length(); + if ( calculateSlop && length > 0 ) { + int[] dirLen = mDirLen.peek(); + long realLength = ((clusterSize-1)+length) / clusterSize*clusterSize; + long slop = realLength-length; + length += slop; + mSize[0] += length; + mSize[1] += slop; + dirLen[0] += f.getName().length()/13+3; + } else { + mSize[0] += length; + } + } + + @Override + public void processDirectory(File f) { + if ( calculateSlop ) { + int[] dirLen = new int[1]; + dirLen[0] += f.getName().length()/13+4; + mDirLen.push(dirLen); + } + } + + @Override + public void endDirectory(File dir) { + if ( calculateSlop ) { + int[] dirLen = mDirLen.pop(); + long lastDirLen = dirLen[0] * 32; + if ( lastDirLen != 0 ) { + long realLength = ((clusterSize-1)+lastDirLen) / clusterSize*clusterSize; + long slop = realLength-lastDirLen; + mSize[0] += lastDirLen + slop; + mSize[1] += slop; + mSize[2] += lastDirLen; + } + } + } + }); + System.out.println("Slop: " + mSize[1] + " Directory Overhead: " + mSize[2] ); + return mSize[0]; + } + + // Process all files and directories under dir + public static void processAllFiles(File dir, FileProcessor fp) { + if (dir.isDirectory()) { + fp.processDirectory(dir); + String[] children = dir.list(); + for (int i = 0; i < children.length; i++) { + processAllFiles(new File(dir, children[i]), fp); + } + fp.endDirectory(dir); + } else { + fp.processFile(dir); + } + } +} diff --git a/jobb/src/com/android/jobb/ObbFile.java b/jobb/src/com/android/jobb/ObbFile.java new file mode 100644 index 0000000..e23785d --- /dev/null +++ b/jobb/src/com/android/jobb/ObbFile.java @@ -0,0 +1,231 @@ +/* + * 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 java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; + +public class ObbFile { + public static final int OBB_OVERLAY = (1 << 0); + public static final int OBB_SALTED = (1 << 1); + + static final int kFooterTagSize = 8; /* last two 32-bit integers */ + + static final int kFooterMinSize = 33; /* 32-bit signature version (4 bytes) + * 32-bit package version (4 bytes) + * 32-bit flags (4 bytes) + * 64-bit salt (8 bytes) + * 32-bit package name size (4 bytes) + * >=1-character package name (1 byte) + * 32-bit footer size (4 bytes) + * 32-bit footer marker (4 bytes) + */ + + static final int kMaxBufSize = 32768; /* Maximum file read buffer */ + + static final long kSignature = 0x01059983; /* ObbFile signature */ + + static final int kSigVersion = 1; /* We only know about signature version 1 */ + + /* offsets in version 1 of the header */ + static final int kPackageVersionOffset = 4; + static final int kFlagsOffset = 8; + static final int kSaltOffset = 12; + static final int kPackageNameLenOffset = 20; + static final int kPackageNameOffset = 24; + + long mPackageVersion = -1, mFlags; + String mPackageName; + byte[] mSalt = new byte[8]; + + public ObbFile() {} + + public boolean readFrom(String filename) + { + File obbFile = new File(filename); + return readFrom(obbFile); + } + + public boolean readFrom(File obbFile) + { + return parseObbFile(obbFile); + } + + static public long get4LE(ByteBuffer buf) { + buf.order(ByteOrder.LITTLE_ENDIAN); + return (buf.getInt() & 0xFFFFFFFFL); + } + + public void setPackageName(String packageName) { + mPackageName = packageName; + } + + public void setSalt(byte[] salt) { + if ( salt.length != mSalt.length ) { + throw new RuntimeException("salt must be " + mSalt.length + " characters in length"); + } + System.arraycopy(salt, 0, mSalt, 0, mSalt.length); + } + + public void setPackageVersion(long packageVersion) { + mPackageVersion = packageVersion; + } + + public void setFlags(long flags) { + mFlags = flags; + } + + public boolean parseObbFile(File obbFile) + { + try { + long fileLength = obbFile.length(); + + if (fileLength < kFooterMinSize) { + throw new RuntimeException("file is only " + fileLength + " (less than " + kFooterMinSize + " minimum)"); + } + + RandomAccessFile raf = new RandomAccessFile(obbFile, "r"); + raf.seek(fileLength - kFooterTagSize); + byte[] footer = new byte[kFooterTagSize]; + raf.readFully(footer); + ByteBuffer footBuf = ByteBuffer.wrap(footer); + footBuf.position(4); + long fileSig = get4LE(footBuf); + if (fileSig != kSignature) { + throw new RuntimeException("footer didn't match magic string (expected 0x" + Long.toHexString(kSignature) + ";got 0x" + + Long.toHexString(fileSig)+ ")"); + } + + footBuf.rewind(); + long footerSize = get4LE(footBuf); + if (footerSize > fileLength - kFooterTagSize + || footerSize > kMaxBufSize) { + throw new RuntimeException("claimed footer size is too large (0x" + Long.toHexString(footerSize) + "; file size is 0x" + + Long.toHexString(fileLength)+ ")"); + } + + if (footerSize < (kFooterMinSize - kFooterTagSize)) { + throw new RuntimeException("claimed footer size is too small (0x" + Long.toHexString(footerSize) + "; minimum size is 0x" + + Long.toHexString(kFooterMinSize - kFooterTagSize)); + } + + long fileOffset = fileLength - footerSize - kFooterTagSize; + raf.seek(fileOffset); + + footer = new byte[(int)footerSize]; + raf.readFully(footer); + footBuf = ByteBuffer.wrap(footer); + + long sigVersion = get4LE(footBuf); + if (sigVersion != kSigVersion) { + throw new RuntimeException("Unsupported ObbFile version " + sigVersion ); + } + + footBuf.position(kPackageVersionOffset); + mPackageVersion = get4LE(footBuf); + footBuf.position(kFlagsOffset); + mFlags = get4LE(footBuf); + + footBuf.position(kSaltOffset); + footBuf.get(mSalt); + footBuf.position(kPackageNameLenOffset); + long packageNameLen = get4LE(footBuf); + if (packageNameLen == 0 + || packageNameLen > (footerSize - kPackageNameOffset)) { + throw new RuntimeException("bad ObbFile package name length (0x" + Long.toHexString(packageNameLen) + + "; 0x" + Long.toHexString(footerSize - kPackageNameOffset) + "possible)"); + } + byte[] packageNameBuf = new byte[(int)packageNameLen]; + footBuf.position(kPackageNameOffset); + footBuf.get(packageNameBuf); + + mPackageName = new String(packageNameBuf); + return true; + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + public boolean writeTo(String fileName) + { + File obbFile = new File(fileName); + return writeTo(obbFile); + } + + public boolean writeTo(File obbFile) { + if ( !obbFile.exists() ) + return false; + + try { + + long fileLength = obbFile.length(); + RandomAccessFile raf = new RandomAccessFile(obbFile, "rw"); + raf.seek(fileLength); + + if (null == mPackageName || mPackageVersion == -1) { + throw new RuntimeException("tried to write uninitialized ObbFile data"); + } + + FileChannel fc = raf.getChannel(); + ByteBuffer bbInt = ByteBuffer.allocate(4); + bbInt.order(ByteOrder.LITTLE_ENDIAN); + bbInt.putInt(kSigVersion); + bbInt.rewind(); + fc.write(bbInt); + + bbInt.rewind(); + bbInt.putInt((int)mPackageVersion); + bbInt.rewind(); + fc.write(bbInt); + + bbInt.rewind(); + bbInt.putInt((int)mFlags); + bbInt.rewind(); + fc.write(bbInt); + + raf.write(mSalt); + + bbInt.rewind(); + bbInt.putInt(mPackageName.length()); + bbInt.rewind(); + fc.write(bbInt); + + raf.write(mPackageName.getBytes()); + + bbInt.rewind(); + bbInt.putInt(mPackageName.length()+kPackageNameOffset); + bbInt.rewind(); + fc.write(bbInt); + + bbInt.rewind(); + bbInt.putInt((int)kSignature); + bbInt.rewind(); + fc.write(bbInt); + + raf.close(); + return true; + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } +} diff --git a/jobb/src/com/android/jobb/PBKDF.java b/jobb/src/com/android/jobb/PBKDF.java new file mode 100644 index 0000000..4893c00 --- /dev/null +++ b/jobb/src/com/android/jobb/PBKDF.java @@ -0,0 +1,55 @@ +/* + * 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 java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; + +import javax.crypto.SecretKeyFactory; +import javax.crypto.interfaces.PBEKey; +import javax.crypto.spec.PBEKeySpec; + +public class PBKDF { + public static final int SALT_LEN = 8; + private static final int ROUNDS = 1024; + private static final int KEY_BITS = 128; + + public static byte[] getKey(String password, byte[] saltBytes) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException { + PBEKeySpec pwKey = new PBEKeySpec(password.toCharArray(), saltBytes, ROUNDS, KEY_BITS); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + PBEKey pbeKey; + try { + pbeKey = (PBEKey) factory.generateSecret(pwKey); + byte[] pbkdfKey = pbeKey.getEncoded(); + return pbkdfKey; + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + } + return null; + } + + public static byte[] getRandomSalt() { + SecureRandom random = new SecureRandom(); + byte[] saltBytes = new byte[SALT_LEN]; + random.nextBytes(saltBytes); + return saltBytes; + } + +} |