aboutsummaryrefslogtreecommitdiffstats
path: root/jobb
diff options
context:
space:
mode:
authorDan Galpin <dgalpin@google.com>2012-08-27 10:36:33 -0700
committerDan Galpin <dgalpin@google.com>2012-10-24 18:08:57 -0700
commitc5373d3e32964676de5f584c1849d1258389ca33 (patch)
treeb0d5b2168e68e20b5ac29e43016ff26cc63916af /jobb
parent8f66190a05b17fb1183678ca5ffdfc4e1015b297 (diff)
downloadsdk-c5373d3e32964676de5f584c1849d1258389ca33.zip
sdk-c5373d3e32964676de5f584c1849d1258389ca33.tar.gz
sdk-c5373d3e32964676de5f584c1849d1258389ca33.tar.bz2
Client-side jobb (obb-creation) tool.
This is the client side jobb tool along with a few executable scripts for Linux/Mac and Windows. It requires the use of the fat32lib in external. This tool creates filesystems by recursing through directories. The Windows/Mac scripts have yet to be tested. Change-Id: I031739e034bba2f3153fc9c715ce9da306253dfb
Diffstat (limited to 'jobb')
-rw-r--r--jobb/Android.mk30
-rw-r--r--jobb/NOTICE50
-rw-r--r--jobb/etc/Android.mk20
-rwxr-xr-xjobb/etc/jobb68
-rwxr-xr-xjobb/etc/jobb.bat51
-rw-r--r--jobb/etc/manifest.txt2
-rw-r--r--jobb/src/Twofish/Twofish_Algorithm.java835
-rw-r--r--jobb/src/Twofish/Twofish_Properties.java197
-rw-r--r--jobb/src/com/android/jobb/Base64.java276
-rw-r--r--jobb/src/com/android/jobb/Encoder.java33
-rw-r--r--jobb/src/com/android/jobb/EncryptedBlockFile.java379
-rw-r--r--jobb/src/com/android/jobb/Main.java607
-rw-r--r--jobb/src/com/android/jobb/ObbFile.java231
-rw-r--r--jobb/src/com/android/jobb/PBKDF.java55
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> &copy; 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> &copy; 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;
+ }
+
+}