OBB: use PBKDF2 for key generation.

Switch to using PBKDF2 for the key generation for OBBs. Any previously
generated OBBs will stop being read correctly. A small pbkdf2gen program
is available to allow generation of appropriate keys with the salts.

Bug: 3059950
Change-Id: If4305c989fd692fd1150eb270dbf751e09c37295
diff --git a/core/java/android/content/res/ObbInfo.java b/core/java/android/content/res/ObbInfo.java
index 5d6ed44..b653f9f 100644
--- a/core/java/android/content/res/ObbInfo.java
+++ b/core/java/android/content/res/ObbInfo.java
@@ -48,6 +48,13 @@
      */
     public int flags;
 
+    /**
+     * The salt for the encryption algorithm.
+     * 
+     * @hide
+     */
+    public byte[] salt;
+
     // Only allow things in this package to instantiate.
     /* package */ ObbInfo() {
     }
@@ -75,6 +82,7 @@
         dest.writeString(packageName);
         dest.writeInt(version);
         dest.writeInt(flags);
+        dest.writeByteArray(salt);
     }
 
     public static final Parcelable.Creator<ObbInfo> CREATOR
@@ -93,5 +101,6 @@
         packageName = source.readString();
         version = source.readInt();
         flags = source.readInt();
+        salt = source.createByteArray();
     }
 }
diff --git a/core/jni/android_content_res_ObbScanner.cpp b/core/jni/android_content_res_ObbScanner.cpp
index 2a9eacf..3fd7985 100644
--- a/core/jni/android_content_res_ObbScanner.cpp
+++ b/core/jni/android_content_res_ObbScanner.cpp
@@ -32,6 +32,7 @@
     jfieldID packageName;
     jfieldID version;
     jfieldID flags;
+    jfieldID salt;
 } gObbInfoClassInfo;
 
 static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL)
@@ -69,6 +70,14 @@
     env->SetObjectField(obbInfo, gObbInfoClassInfo.packageName, packageName);
     env->SetIntField(obbInfo, gObbInfoClassInfo.version, obb->getVersion());
     env->SetIntField(obbInfo, gObbInfoClassInfo.flags, obb->getFlags());
+
+    size_t saltLen;
+    const unsigned char* salt = obb->getSalt(&saltLen);
+    if (saltLen > 0) {
+        jbyteArray saltArray = env->NewByteArray(saltLen);
+        env->SetByteArrayRegion(saltArray, 0, saltLen, (jbyte*)salt);
+        env->SetObjectField(obbInfo, gObbInfoClassInfo.salt, saltArray);
+    }
 }
 
 /*
@@ -99,6 +108,8 @@
             "version", "I");
     GET_FIELD_ID(gObbInfoClassInfo.flags, gObbInfoClassInfo.clazz,
             "flags", "I");
+    GET_FIELD_ID(gObbInfoClassInfo.salt, gObbInfoClassInfo.clazz,
+            "salt", "[B");
 
     return AndroidRuntime::registerNativeMethods(env, "android/content/res/ObbScanner", gMethods,
             NELEM(gMethods));
diff --git a/core/tests/coretests/res/raw/test1.obb b/core/tests/coretests/res/raw/test1.obb
index 170e36f..8466588 100644
--- a/core/tests/coretests/res/raw/test1.obb
+++ b/core/tests/coretests/res/raw/test1.obb
Binary files differ
diff --git a/core/tests/coretests/res/raw/test1_wrongpackage.obb b/core/tests/coretests/res/raw/test1_wrongpackage.obb
index 2e02eaa..d0aafe1 100644
--- a/core/tests/coretests/res/raw/test1_wrongpackage.obb
+++ b/core/tests/coretests/res/raw/test1_wrongpackage.obb
Binary files differ
diff --git a/include/utils/ObbFile.h b/include/utils/ObbFile.h
index 5243f50..47559cd 100644
--- a/include/utils/ObbFile.h
+++ b/include/utils/ObbFile.h
@@ -27,6 +27,7 @@
 
 // OBB flags (bit 0)
 #define OBB_OVERLAY         (1 << 0)
+#define OBB_SALTED          (1 << 1)
 
 class ObbFile : public RefBase {
 protected:
@@ -70,6 +71,26 @@
         mFlags = flags;
     }
 
+    const unsigned char* getSalt(size_t* length) const {
+        if ((mFlags & OBB_SALTED) == 0) {
+            *length = 0;
+            return NULL;
+        }
+
+        *length = sizeof(mSalt);
+        return mSalt;
+    }
+
+    bool setSalt(const unsigned char* salt, size_t length) {
+        if (length != sizeof(mSalt)) {
+            return false;
+        }
+
+        memcpy(mSalt, salt, sizeof(mSalt));
+        mFlags |= OBB_SALTED;
+        return true;
+    }
+
     bool isOverlay() {
         return (mFlags & OBB_OVERLAY) == OBB_OVERLAY;
     }
@@ -103,6 +124,12 @@
     /* Flags for this OBB type. */
     int32_t mFlags;
 
+    /* Whether the file is salted. */
+    bool mSalted;
+
+    /* The encryption salt. */
+    unsigned char mSalt[8];
+
     const char* mFileName;
 
     size_t mFileSize;
diff --git a/libs/utils/ObbFile.cpp b/libs/utils/ObbFile.cpp
index e170ab8..2c3724c 100644
--- a/libs/utils/ObbFile.cpp
+++ b/libs/utils/ObbFile.cpp
@@ -29,10 +29,11 @@
 
 #define kFooterTagSize 8  /* last two 32-bit integers */
 
-#define kFooterMinSize 25 /* 32-bit signature version (4 bytes)
+#define kFooterMinSize 33 /* 32-bit signature version (4 bytes)
                            * 32-bit package version (4 bytes)
                            * 32-bit flags (4 bytes)
-                           * 32-bit package name size (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)
@@ -47,8 +48,9 @@
 /* offsets in version 1 of the header */
 #define kPackageVersionOffset 4
 #define kFlagsOffset          8
-#define kPackageNameLenOffset 12
-#define kPackageNameOffset    16
+#define kSaltOffset           12
+#define kPackageNameLenOffset 20
+#define kPackageNameOffset    24
 
 /*
  * TEMP_FAILURE_RETRY is defined by some, but not all, versions of
@@ -79,11 +81,12 @@
 
 namespace android {
 
-ObbFile::ObbFile() :
-        mPackageName(""),
-        mVersion(-1),
-        mFlags(0)
+ObbFile::ObbFile()
+        : mPackageName("")
+        , mVersion(-1)
+        , mFlags(0)
 {
+    memset(mSalt, 0, sizeof(mSalt));
 }
 
 ObbFile::~ObbFile() {
@@ -192,7 +195,7 @@
 
 #ifdef DEBUG
     for (int i = 0; i < footerSize; ++i) {
-        LOGI("char: 0x%02x", scanBuf[i]);
+        LOGI("char: 0x%02x\n", scanBuf[i]);
     }
 #endif
 
@@ -206,6 +209,8 @@
     mVersion = (int32_t) get4LE((unsigned char*)scanBuf + kPackageVersionOffset);
     mFlags = (int32_t) get4LE((unsigned char*)scanBuf + kFlagsOffset);
 
+    memcpy(&mSalt, (unsigned char*)scanBuf + kSaltOffset, sizeof(mSalt));
+
     uint32_t packageNameLen = get4LE((unsigned char*)scanBuf + kPackageNameLenOffset);
     if (packageNameLen <= 0
             || packageNameLen > (footerSize - kPackageNameOffset)) {
@@ -255,7 +260,7 @@
     my_lseek64(fd, 0, SEEK_END);
 
     if (mPackageName.size() == 0 || mVersion == -1) {
-        LOGW("tried to write uninitialized ObbFile data");
+        LOGW("tried to write uninitialized ObbFile data\n");
         return false;
     }
 
@@ -264,43 +269,48 @@
 
     put4LE(intBuf, kSigVersion);
     if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
-        LOGW("couldn't write signature version: %s", strerror(errno));
+        LOGW("couldn't write signature version: %s\n", strerror(errno));
         return false;
     }
 
     put4LE(intBuf, mVersion);
     if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
-        LOGW("couldn't write package version");
+        LOGW("couldn't write package version\n");
         return false;
     }
 
     put4LE(intBuf, mFlags);
     if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
-        LOGW("couldn't write package version");
+        LOGW("couldn't write package version\n");
+        return false;
+    }
+
+    if (write(fd, mSalt, sizeof(mSalt)) != (ssize_t)sizeof(mSalt)) {
+        LOGW("couldn't write salt: %s\n", strerror(errno));
         return false;
     }
 
     size_t packageNameLen = mPackageName.size();
     put4LE(intBuf, packageNameLen);
     if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
-        LOGW("couldn't write package name length: %s", strerror(errno));
+        LOGW("couldn't write package name length: %s\n", strerror(errno));
         return false;
     }
 
     if (write(fd, mPackageName.string(), packageNameLen) != (ssize_t)packageNameLen) {
-        LOGW("couldn't write package name: %s", strerror(errno));
+        LOGW("couldn't write package name: %s\n", strerror(errno));
         return false;
     }
 
     put4LE(intBuf, kPackageNameOffset + packageNameLen);
     if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
-        LOGW("couldn't write footer size: %s", strerror(errno));
+        LOGW("couldn't write footer size: %s\n", strerror(errno));
         return false;
     }
 
     put4LE(intBuf, kSignature);
     if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
-        LOGW("couldn't write footer magic signature: %s", strerror(errno));
+        LOGW("couldn't write footer magic signature: %s\n", strerror(errno));
         return false;
     }
 
diff --git a/libs/utils/tests/ObbFile_test.cpp b/libs/utils/tests/ObbFile_test.cpp
index 29bb70a..46b30c2 100644
--- a/libs/utils/tests/ObbFile_test.cpp
+++ b/libs/utils/tests/ObbFile_test.cpp
@@ -23,6 +23,7 @@
 #include <gtest/gtest.h>
 
 #include <fcntl.h>
+#include <string.h>
 
 namespace android {
 
@@ -63,6 +64,10 @@
 
     mObbFile->setPackageName(String8(packageName));
     mObbFile->setVersion(versionNum);
+#define SALT_SIZE 8
+    unsigned char salt[SALT_SIZE] = {0x01, 0x10, 0x55, 0xAA, 0xFF, 0x00, 0x5A, 0xA5};
+    EXPECT_TRUE(mObbFile->setSalt(salt, SALT_SIZE))
+            << "Salt should be successfully set";
 
     EXPECT_TRUE(mObbFile->writeTo(mFileName))
             << "couldn't write to fake .obb file";
@@ -77,6 +82,19 @@
     const char* currentPackageName = mObbFile->getPackageName().string();
     EXPECT_STREQ(packageName, currentPackageName)
             << "package name didn't come out the same as it went in";
+
+    size_t saltLen;
+    const unsigned char* newSalt = mObbFile->getSalt(&saltLen);
+
+    EXPECT_EQ(sizeof(salt), saltLen)
+            << "salt sizes were not the same";
+
+    for (int i = 0; i < sizeof(salt); i++) {
+        EXPECT_EQ(salt[i], newSalt[i])
+                << "salt character " << i << " should be equal";
+    }
+    EXPECT_TRUE(memcmp(newSalt, salt, sizeof(salt)) == 0)
+            << "salts should be the same";
 }
 
 }
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 775f5c8..8cf8f6a 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -17,7 +17,6 @@
 package com.android.server;
 
 import com.android.internal.app.IMediaContainerService;
-import com.android.internal.util.HexDump;
 import com.android.server.am.ActivityManagerService;
 
 import android.content.BroadcastReceiver;
@@ -46,13 +45,15 @@
 import android.os.storage.IObbActionListener;
 import android.os.storage.OnObbStateChangeListener;
 import android.os.storage.StorageResultCode;
-import android.security.MessageDigest;
 import android.util.Slog;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.math.BigInteger;
 import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -62,6 +63,10 @@
 import java.util.Map;
 import java.util.Map.Entry;
 
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
 /**
  * MountService implements back-end services for platform storage
  * management.
@@ -154,6 +159,18 @@
     final private HashSet<String> mAsecMountSet = new HashSet<String>();
 
     /**
+     * The size of the crypto algorithm key in bits for OBB files. Currently
+     * Twofish is used which takes 128-bit keys.
+     */
+    private static final int CRYPTO_ALGORITHM_KEY_SIZE = 128;
+
+    /**
+     * The number of times to run SHA1 in the PBKDF2 function for OBB files.
+     * 1024 is reasonably secure and not too slow.
+     */
+    private static final int PBKDF2_HASH_ROUNDS = 1024;
+
+    /**
      * Mounted OBB tracking information. Used to track the current state of all
      * OBBs.
      */
@@ -1901,16 +1918,23 @@
             if (mKey == null) {
                 hashedKey = "none";
             } else {
-                final MessageDigest md;
                 try {
-                    md = MessageDigest.getInstance("MD5");
+                    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+
+                    KeySpec ks = new PBEKeySpec(mKey.toCharArray(), obbInfo.salt,
+                            PBKDF2_HASH_ROUNDS, CRYPTO_ALGORITHM_KEY_SIZE);
+                    SecretKey key = factory.generateSecret(ks);
+                    BigInteger bi = new BigInteger(key.getEncoded());
+                    hashedKey = bi.toString(16);
                 } catch (NoSuchAlgorithmException e) {
-                    Slog.e(TAG, "Could not load MD5 algorithm", e);
-                    sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
+                    Slog.e(TAG, "Could not load PBKDF2 algorithm", e);
+                    sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
+                    return;
+                } catch (InvalidKeySpecException e) {
+                    Slog.e(TAG, "Invalid key spec when loading PBKDF2 algorithm", e);
+                    sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
                     return;
                 }
-
-                hashedKey = HexDump.toHexString(md.digest(mKey.getBytes()));
             }
 
             int rc = StorageResultCode.OperationSucceeded;
diff --git a/tools/obbtool/Android.mk b/tools/obbtool/Android.mk
index b02c1cb..9300bb7 100644
--- a/tools/obbtool/Android.mk
+++ b/tools/obbtool/Android.mk
@@ -13,6 +13,8 @@
 LOCAL_SRC_FILES := \
 	Main.cpp
 
+LOCAL_CFLAGS := -Wall -Werror
+
 #LOCAL_C_INCLUDES +=
 
 LOCAL_STATIC_LIBRARIES := \
@@ -27,4 +29,18 @@
 
 include $(BUILD_HOST_EXECUTABLE)
 
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := pbkdf2gen
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_CFLAGS := -Wall -Werror
+
+LOCAL_SRC_FILES := pbkdf2gen.cpp
+
+LOCAL_SHARED_LIBRARIES := libcrypto
+
+include $(BUILD_HOST_EXECUTABLE)
+
 endif # TARGET_BUILD_APPS
diff --git a/tools/obbtool/Main.cpp b/tools/obbtool/Main.cpp
index 49e077f..932dbec 100644
--- a/tools/obbtool/Main.cpp
+++ b/tools/obbtool/Main.cpp
@@ -20,6 +20,7 @@
 #include <getopt.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 
 using namespace android;
 
@@ -29,7 +30,9 @@
 static int wantUsage = 0;
 static int wantVersion = 0;
 
-#define ADD_OPTS "n:v:o"
+#define SALT_LEN 8
+
+#define ADD_OPTS "n:v:os:"
 static const struct option longopts[] = {
     {"help",       no_argument, &wantUsage,   1},
     {"version",    no_argument, &wantVersion, 1},
@@ -38,14 +41,27 @@
     {"name",       required_argument, NULL, 'n'},
     {"version",    required_argument, NULL, 'v'},
     {"overlay",    optional_argument, NULL, 'o'},
+    {"salt",       required_argument, NULL, 's'},
 
     {NULL, 0, NULL, '\0'}
 };
 
-struct package_info_t {
+class PackageInfo {
+public:
+    PackageInfo()
+            : packageName(NULL)
+            , packageVersion(-1)
+            , overlay(false)
+            , salted(false)
+    {
+        memset(&salt, 0, sizeof(salt));
+    }
+
     char* packageName;
     int packageVersion;
     bool overlay;
+    bool salted;
+    unsigned char salt[SALT_LEN];
 };
 
 /*
@@ -59,6 +75,13 @@
         " %s a[dd] [ OPTIONS ] FILENAME\n"
         "   Adds an OBB signature to the file.\n\n", gProgName);
     fprintf(stderr,
+        "   Options:\n"
+        "     -n <package name>      sets the OBB package name (required)\n"
+        "     -v <OBB version>       sets the OBB version (required)\n"
+        "     -o                     sets the OBB overlay flag\n"
+        "     -s <8 byte hex salt>   sets the crypto key salt (if encrypted)\n"
+        "\n");
+    fprintf(stderr,
         " %s r[emove] FILENAME\n"
         "   Removes the OBB signature from the file.\n\n", gProgName);
     fprintf(stderr,
@@ -66,7 +89,7 @@
         "   Prints the OBB signature information of a file.\n\n", gProgName);
 }
 
-void doAdd(const char* filename, struct package_info_t* info) {
+void doAdd(const char* filename, struct PackageInfo* info) {
     ObbFile *obb = new ObbFile();
     if (obb->readFrom(filename)) {
         fprintf(stderr, "ERROR: %s: OBB signature already present\n", filename);
@@ -76,6 +99,9 @@
     obb->setPackageName(String8(info->packageName));
     obb->setVersion(info->packageVersion);
     obb->setOverlay(info->overlay);
+    if (info->salted) {
+        obb->setSalt(info->salt, SALT_LEN);
+    }
 
     if (!obb->writeTo(filename)) {
         fprintf(stderr, "ERROR: %s: couldn't write OBB signature: %s\n",
@@ -113,6 +139,40 @@
     printf("     Version: %d\n", obb->getVersion());
     printf("       Flags: 0x%08x\n", obb->getFlags());
     printf("     Overlay: %s\n", obb->isOverlay() ? "true" : "false");
+    printf("        Salt: ");
+
+    size_t saltLen;
+    const unsigned char* salt = obb->getSalt(&saltLen);
+    if (salt != NULL) {
+        for (int i = 0; i < SALT_LEN; i++) {
+            printf("%02x", salt[i]);
+        }
+        printf("\n");
+    } else {
+        printf("<empty>\n");
+    }
+}
+
+bool fromHex(char h, unsigned char *b) {
+    if (h >= '0' && h <= '9') {
+        *b = h - '0';
+        return true;
+    } else if (h >= 'a' && h <= 'f') {
+        *b = h - 'a' + 10;
+        return true;
+    } else if (h >= 'A' && h <= 'F') {
+        *b = h - 'A' + 10;
+        return true;
+    }
+    return false;
+}
+
+bool hexToByte(char h1, char h2, unsigned char* b) {
+    unsigned char first, second;
+    if (!fromHex(h1, &first)) return false;
+    if (!fromHex(h2, &second)) return false;
+    *b = (first << 4) | second;
+    return true;
 }
 
 /*
@@ -120,11 +180,9 @@
  */
 int main(int argc, char* const argv[])
 {
-    const char *prog = argv[0];
-    struct options *options;
     int opt;
     int option_index = 0;
-    struct package_info_t package_info;
+    struct PackageInfo package_info;
 
     int result = 1;    // pessimistically assume an error.
 
@@ -145,7 +203,7 @@
             package_info.packageName = optarg;
             break;
         case 'v': {
-            char *end;
+            char* end;
             package_info.packageVersion = strtol(optarg, &end, 10);
             if (*optarg == '\0' || *end != '\0') {
                 fprintf(stderr, "ERROR: invalid version; should be integer!\n\n");
@@ -157,6 +215,25 @@
         case 'o':
             package_info.overlay = true;
             break;
+        case 's':
+            if (strlen(optarg) != SALT_LEN * 2) {
+                fprintf(stderr, "ERROR: salt must be 8 bytes in hex (e.g., ABCD65031337D00D)\n\n");
+                wantUsage = 1;
+                goto bail;
+            }
+
+            package_info.salted = true;
+
+            unsigned char b;
+            for (int i = 0, j = 0; i < SALT_LEN; i++, j+=2) {
+                if (!hexToByte(optarg[j], optarg[j+1], &b)) {
+                    fprintf(stderr, "ERROR: salt must be in hex (e.g., ABCD65031337D00D)\n");
+                    wantUsage = 1;
+                    goto bail;
+                }
+                package_info.salt[i] = b;
+            }
+            break;
         case '?':
             wantUsage = 1;
             goto bail;
diff --git a/tools/obbtool/mkobb.sh b/tools/obbtool/mkobb.sh
index ba5256f..725250d 100755
--- a/tools/obbtool/mkobb.sh
+++ b/tools/obbtool/mkobb.sh
@@ -35,6 +35,7 @@
     UMOUNTBIN=`which umount`
     DDBIN=`which dd`
     RSYNCBIN=`which rsync`
+    PBKDF2GEN=`which pbkdf2gen`
 }
 
 check_prereqs() {
@@ -76,6 +77,11 @@
         echo "ERROR: ${LOSETUPBIN} is not executable!"
         exit 1
     fi
+
+    if [ "${PBKDF2GEN}x" = "x" ]; then \
+        echo "ERROR: Could not find pbkdf2gen in your path!"
+        exit 1
+    fi
 }
 
 cleanup() {
@@ -142,7 +148,6 @@
 usage() {
     echo "mkobb.sh -- Create OBB files for use on Android"
     echo ""
-    echo " -c             Use an encrypted OBB; must specify key"
     echo " -d <directory> Use <directory> as input for OBB files"
     echo " -k <key>       Use <key> to encrypt OBB file"
     echo " -K             Prompt for key to encrypt OBB file"
@@ -156,7 +161,7 @@
 
 use_crypto=0
 
-args=`getopt -o cd:hk:Ko:v -- "$@"`
+args=`getopt -o d:hk:Ko:v -- "$@"`
 eval set -- "$args"
 
 while true; do \
@@ -223,9 +228,9 @@
 ${LOSETUPBIN} ${loop_dev} ${tempfile} || ( echo "ERROR: couldn't create loopback device"; exit 1 )
 
 if [ ${use_crypto} -eq 1 ]; then \
-    hashed_key=`echo -n "${key}" | md5sum | awk '{ print $1 }'`
+    eval `${PBKDF2GEN} ${key}`
     unique_dm_name=`basename ${tempfile}`
-    echo "0 `blockdev --getsize ${loop_dev}` crypt ${CRYPTO} ${hashed_key} 0 ${loop_dev} 0" | dmsetup create ${unique_dm_name}
+    echo "0 `blockdev --getsize ${loop_dev}` crypt ${CRYPTO} ${key} 0 ${loop_dev} 0" | dmsetup create ${unique_dm_name}
     old_loop_dev=${loop_dev}
     loop_dev=/dev/mapper/${unique_dm_name}
 fi
@@ -253,6 +258,11 @@
 
 echo "Successfully created \`${filename}'"
 
+if [ ${use_crypto} -eq 1 ]; then \
+    echo "salt for use with obbtool is:"
+    echo "${salt}"
+fi
+
 #
 # Undo all the temporaries
 #
diff --git a/tools/obbtool/pbkdf2gen.cpp b/tools/obbtool/pbkdf2gen.cpp
new file mode 100644
index 0000000..98d67c0
--- /dev/null
+++ b/tools/obbtool/pbkdf2gen.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+#include <openssl/evp.h>
+
+#include <sys/types.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+/**
+ * Simple program to generate a key based on PBKDF2 with preset inputs.
+ *
+ * Will print out the salt and key in hex.
+ */
+
+#define SALT_LEN 8
+#define ROUNDS 1024
+#define KEY_BITS 128
+
+int main(int argc, char* argv[])
+{
+    if (argc != 2) {
+        fprintf(stderr, "Usage: %s <password>\n", argv[0]);
+        exit(1);
+    }
+
+    int fd = open("/dev/urandom", O_RDONLY);
+    if (fd < 0) {
+        fprintf(stderr, "Could not open /dev/urandom: %s\n", strerror(errno));
+        close(fd);
+        exit(1);
+    }
+
+    unsigned char salt[SALT_LEN];
+
+    if (read(fd, &salt, SALT_LEN) != SALT_LEN) {
+        fprintf(stderr, "Could not read salt from /dev/urandom: %s\n", strerror(errno));
+        close(fd);
+        exit(1);
+    }
+    close(fd);
+
+    unsigned char rawKey[KEY_BITS];
+
+    if (PKCS5_PBKDF2_HMAC_SHA1(argv[1], strlen(argv[1]), salt, SALT_LEN,
+            ROUNDS, KEY_BITS, rawKey) != 1) {
+        fprintf(stderr, "Could not generate PBKDF2 output: %s\n", strerror(errno));
+        exit(1);
+    }
+
+    printf("salt=");
+    for (int i = 0; i < SALT_LEN; i++) {
+        printf("%02x", salt[i]);
+    }
+    printf("\n");
+
+    printf("key=");
+    for (int i = 0; i < (KEY_BITS / 8); i++) {
+        printf("%02x", rawKey[i]);
+    }
+    printf("\n");
+}