Merge "KeyDerivationFunction: example about treating data encrypted via SHA1PRNG"
diff --git a/samples/BrokenKeyDerivation/Android.mk b/samples/BrokenKeyDerivation/Android.mk
new file mode 100644
index 0000000..df767c3
--- /dev/null
+++ b/samples/BrokenKeyDerivation/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := BrokenKeyDerivation
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/samples/BrokenKeyDerivation/AndroidManifest.xml b/samples/BrokenKeyDerivation/AndroidManifest.xml
new file mode 100644
index 0000000..f792945
--- /dev/null
+++ b/samples/BrokenKeyDerivation/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<!-- Declare the contents of this Android application.  The namespace
+     attribute brings in the Android platform namespace, and the package
+     supplies a unique name for the application.  When writing your
+     own application, the package name must be changed from "com.example.*"
+     to come from a domain that you own or have control over. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.brokenkeyderivation">
+    <application android:label="Broken Key Derivation">
+        <activity android:name="BrokenKeyDerivationActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/samples/BrokenKeyDerivation/res/layout/brokenkeyderivation_activity.xml b/samples/BrokenKeyDerivation/res/layout/brokenkeyderivation_activity.xml
new file mode 100644
index 0000000..bcbcb3e
--- /dev/null
+++ b/samples/BrokenKeyDerivation/res/layout/brokenkeyderivation_activity.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<EditText xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:textSize="18sp"
+    android:autoText="true"
+    android:capitalize="sentences" />
+
diff --git a/samples/BrokenKeyDerivation/res/values/strings.xml b/samples/BrokenKeyDerivation/res/values/strings.xml
new file mode 100644
index 0000000..741d4d5
--- /dev/null
+++ b/samples/BrokenKeyDerivation/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<resources>
+
+</resources>
diff --git a/samples/BrokenKeyDerivation/src/com/example/android/brokenkeyderivation/BrokenKeyDerivationActivity.java b/samples/BrokenKeyDerivation/src/com/example/android/brokenkeyderivation/BrokenKeyDerivationActivity.java
new file mode 100644
index 0000000..efca314
--- /dev/null
+++ b/samples/BrokenKeyDerivation/src/com/example/android/brokenkeyderivation/BrokenKeyDerivationActivity.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2007 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.example.android.brokenkeyderivation;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.EditText;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.security.spec.KeySpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+
+/**
+ * Example showing how to decrypt data that was encrypted using SHA1PRNG.
+ *
+ * The Crypto provider providing the SHA1PRNG algorithm for random number
+ * generation is deprecated as of SDK 24.
+ *
+ * This algorithm was sometimes incorrectly used to derive keys. See
+ * <a href="http://android-developers.blogspot.co.uk/2013/02/using-cryptography-to-store-credentials.html">
+ * here</a> for details.
+
+ * This example provides a helper class ({@link InsecureSHA1PRNGKeyDerivator} and shows how to treat
+ * data that was encrypted in the incorrect way and re-encrypt it in a proper way,
+ * by using a key derivation function.
+ *
+ * The {@link #onCreate(Bundle)} method retrieves encrypted data twice and displays the results.
+ *
+ * The mock data is encrypted with an insecure key. The first time it is reencrypted properly and
+ * the plain text is returned together with a warning message. The second time, as the data is
+ * properly encrypted, the plain text is returned with a congratulations message.
+ */
+public class BrokenKeyDerivationActivity extends Activity {
+    /**
+     * Method used to derive an <b>insecure</b> key by emulating the SHA1PRNG algorithm from the
+     * deprecated Crypto provider.
+     *
+     * Do not use it to encrypt new data, just to decrypt encrypted data that would be unrecoverable
+     * otherwise.
+     */
+    private static SecretKey deriveKeyInsecurely(String password, int keySizeInBytes) {
+        byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
+        return new SecretKeySpec(
+                InsecureSHA1PRNGKeyDerivator.deriveInsecureKey(passwordBytes, keySizeInBytes),
+                "AES");
+    }
+
+    /**
+     * Example use of a key derivation function, derivating a key securely from a password.
+     */
+    private SecretKey deriveKeySecurely(String password, int keySizeInBytes) {
+        // Use this to derive the key from the password:
+        KeySpec keySpec = new PBEKeySpec(password.toCharArray(), retrieveSalt(),
+                100 /* iterationCount */, keySizeInBytes * 8 /* key size in bits */);
+        try {
+            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+            byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
+            return new SecretKeySpec(keyBytes, "AES");
+        } catch (Exception e) {
+            throw new RuntimeException("Deal with exceptions properly!", e);
+        }
+    }
+
+    /**
+     * Retrieve encrypted data using a password. If data is stored with an insecure key, re-encrypt
+     * with a secure key.
+     */
+    private String retrieveData(String password) {
+        String decryptedString;
+
+        if (isDataStoredWithInsecureKey()) {
+            SecretKey insecureKey = deriveKeyInsecurely(password, KEY_SIZE);
+            byte[] decryptedData = decryptData(retrieveEncryptedData(), retrieveIv(), insecureKey);
+            SecretKey secureKey = deriveKeySecurely(password, KEY_SIZE);
+            storeDataEncryptedWithSecureKey(encryptData(decryptedData, retrieveIv(), secureKey));
+            decryptedString = "Warning: data was encrypted with insecure key\n"
+                    + new String(decryptedData, StandardCharsets.UTF_8);
+        } else {
+            SecretKey secureKey = deriveKeySecurely(password, KEY_SIZE);
+            byte[] decryptedData = decryptData(retrieveEncryptedData(), retrieveIv(), secureKey);
+            decryptedString = "Great!: data was encrypted with secure key\n"
+                    + new String(decryptedData, StandardCharsets.UTF_8);
+        }
+        return decryptedString;
+    }
+
+    /*
+     ***********************************************************************************************
+     * The essential point of this example are the three methods above. Everything below this
+     * comment just gives a concrete example of usage and defines mock methods.
+     ***********************************************************************************************
+     */
+
+    /**
+     * Retrieves encrypted data twice and displays the results.
+     *
+     * The mock data is encrypted with an insecure key (see {@link #cleanRoomStart()}) and so the
+     * first time {@link #retrieveData(String)} reencrypts it and returns the plain text with a
+     * warning message. The second time, as the data is properly encrypted, the plain text is
+     * returned with a congratulations message.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Remove any files from previous executions of this app and initialize mock encrypted data.
+        // Just so that the application has the same behaviour every time is run. You don't need to
+        // do this in your app.
+        cleanRoomStart();
+
+        // Set the layout for this activity.  You can find it
+        // in res/layout/brokenkeyderivation_activity.xml
+        View view = getLayoutInflater().inflate(R.layout.brokenkeyderivation_activity, null);
+        setContentView(view);
+
+        // Find the text editor view inside the layout.
+        EditText mEditor = (EditText) findViewById(R.id.text);
+
+        String password = "unguessable";
+        String firstResult = retrieveData(password);
+        String secondResult = retrieveData(password);
+
+        mEditor.setText("First result: " + firstResult + "\nSecond result: " + secondResult);
+
+    }
+
+    private static byte[] encryptOrDecrypt(
+            byte[] data, SecretKey key, byte[] iv, boolean isEncrypt) {
+        try {
+            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING");
+            cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key,
+                    new IvParameterSpec(iv));
+            return cipher.doFinal(data);
+        } catch (GeneralSecurityException e) {
+            throw new RuntimeException("This is unconceivable!", e);
+        }
+    }
+
+    private static byte[] encryptData(byte[] data, byte[] iv, SecretKey key) {
+        return encryptOrDecrypt(data, key, iv, true);
+    }
+
+    private static byte[] decryptData(byte[] data, byte[] iv, SecretKey key) {
+        return encryptOrDecrypt(data, key, iv, false);
+    }
+
+    /**
+     * Remove any files from previous executions of this app and initialize mock encrypted data.
+     *
+     * <p>Just so that the application has the same behaviour every time is run. You don't need to
+     * do this in your app.
+     */
+    private void cleanRoomStart() {
+        removeFile("salt");
+        removeFile("iv");
+        removeFile(SECURE_ENCRYPTION_INDICATOR_FILE_NAME);
+        // Mock initial data
+        encryptedData = encryptData(
+                "I hope it helped!".getBytes(), retrieveIv(),
+                deriveKeyInsecurely("unguessable", KEY_SIZE));
+    }
+
+    /*
+     ***********************************************************************************************
+     * Everything below this comment is a succession of mocks that would rarely interest someone on
+     * Earth. They are merely intended to make the example self contained.
+     ***********************************************************************************************
+     */
+
+    private boolean isDataStoredWithInsecureKey() {
+        // Your app should have a way to tell whether the data has been re-encrypted in a secure
+        // fashion, in this mock we use the existence of a file with a certain name to indicate
+        // that.
+        return !fileExists("encrypted_with_secure_key");
+    }
+
+    private byte[] retrieveIv() {
+        byte[] iv = new byte[IV_SIZE];
+        // Ideally your data should have been encrypted with a random iv. This creates a random iv
+        // if not present, in order to encrypt our mock data.
+        readFromFileOrCreateRandom("iv", iv);
+        return iv;
+    }
+
+    private byte[] retrieveSalt() {
+        // Salt must be at least the same size as the key.
+        byte[] salt = new byte[KEY_SIZE];
+        // Create a random salt if encrypting for the first time, and save it for future use.
+        readFromFileOrCreateRandom("salt", salt);
+        return salt;
+    }
+
+    private byte[] encryptedData = null;
+
+    private byte[] retrieveEncryptedData() {
+        return encryptedData;
+    }
+
+    private void storeDataEncryptedWithSecureKey(byte[] encryptedData) {
+        // Mock implementation.
+        this.encryptedData = encryptedData;
+        writeToFile(SECURE_ENCRYPTION_INDICATOR_FILE_NAME, new byte[1]);
+    }
+
+    /**
+     * Read from file or return random bytes in the given array.
+     *
+     * <p>Save to file if file didn't exist.
+     */
+    private void readFromFileOrCreateRandom(String fileName, byte[] bytes) {
+        if (fileExists(fileName)) {
+            readBytesFromFile(fileName, bytes);
+            return;
+        }
+        SecureRandom sr = new SecureRandom();
+        sr.nextBytes(bytes);
+        writeToFile(fileName, bytes);
+    }
+
+    private boolean fileExists(String fileName) {
+        File file = new File(getFilesDir(), fileName);
+        return file.exists();
+    }
+
+    private void removeFile(String fileName) {
+        File file = new File(getFilesDir(), fileName);
+        file.delete();
+    }
+
+    private void writeToFile(String fileName, byte[] bytes) {
+        try (FileOutputStream fos = openFileOutput(fileName, Context.MODE_PRIVATE)) {
+            fos.write(bytes);
+        } catch (IOException e) {
+            throw new RuntimeException("Couldn't write to " + fileName, e);
+        }
+    }
+
+    private void readBytesFromFile(String fileName, byte[] bytes) {
+        try (FileInputStream fis = openFileInput(fileName)) {
+            int numBytes = 0;
+            while (numBytes < bytes.length) {
+                int n = fis.read(bytes, numBytes, bytes.length - numBytes);
+                if (n <= 0) {
+                    throw new RuntimeException("Couldn't read from " + fileName);
+                }
+                numBytes += n;
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("Couldn't read from " + fileName, e);
+        }
+    }
+
+    private static final int IV_SIZE = 16;
+    private static final int KEY_SIZE = 32;
+    private static final String SECURE_ENCRYPTION_INDICATOR_FILE_NAME =
+            "encrypted_with_secure_key";
+}
+
diff --git a/samples/BrokenKeyDerivation/src/com/example/android/brokenkeyderivation/InsecureSHA1PRNGKeyDerivator.java b/samples/BrokenKeyDerivation/src/com/example/android/brokenkeyderivation/InsecureSHA1PRNGKeyDerivator.java
new file mode 100644
index 0000000..96d8b8d
--- /dev/null
+++ b/samples/BrokenKeyDerivation/src/com/example/android/brokenkeyderivation/InsecureSHA1PRNGKeyDerivator.java
@@ -0,0 +1,609 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.example.android.brokenkeyderivation;
+
+/**
+ * Stripped-down version of the SHA1PRNG provided by the Crypto provider.
+ *
+ * The Crypto provider that offers this functionality was deprecated on Android.
+ *
+ * Use this class only to retrieve encrypted data that couldn't be retrieved otherwise.
+ */
+class InsecureSHA1PRNGKeyDerivator {
+
+    /**
+     * Only public method. Derive a key from the given seed.
+     *
+     * Use this method only to retrieve encrypted data that couldn't be retrieved otherwise.
+     *
+     * @param seed seed used for the random generator, usually coming from a password
+     * @param keySizeInBytes length of the array returned
+     */
+    public static byte[] deriveInsecureKey(byte[] seed, int keySizeInBytes) {
+        InsecureSHA1PRNGKeyDerivator derivator = new InsecureSHA1PRNGKeyDerivator();
+        derivator.setSeed(seed);
+        byte[] key = new byte[keySizeInBytes];
+        derivator.nextBytes(key);
+        return key;
+    }
+
+    // constants to use in expressions operating on bytes in int and long variables:
+    // END_FLAGS - final bytes in words to append to message;
+    //             see "ch.5.1 Padding the Message, FIPS 180-2"
+    // RIGHT1    - shifts to right for left half of long
+    // RIGHT2    - shifts to right for right half of long
+    // LEFT      - shifts to left for bytes
+    // MASK      - mask to select counter's bytes after shift to right
+
+    private static final int[] END_FLAGS = { 0x80000000, 0x800000, 0x8000, 0x80 };
+
+    private static final int[] RIGHT1 = { 0, 40, 48, 56 };
+
+    private static final int[] RIGHT2 = { 0, 8, 16, 24 };
+
+    private static final int[] LEFT = { 0, 24, 16, 8 };
+
+    private static final int[] MASK = { 0xFFFFFFFF, 0x00FFFFFF, 0x0000FFFF,
+            0x000000FF };
+
+    // HASHBYTES_TO_USE defines # of bytes returned by "computeHash(byte[])"
+    // to use to form byte array returning by the "nextBytes(byte[])" method
+    // Note, that this implementation uses more bytes than it is defined
+    // in the above specification.
+    private static final int HASHBYTES_TO_USE = 20;
+
+    // value of 16 defined in the "SECURE HASH STANDARD", FIPS PUB 180-2
+    private static final int FRAME_LENGTH = 16;
+
+    // miscellaneous constants defined in this implementation:
+    // COUNTER_BASE - initial value to set to "counter" before computing "nextBytes(..)";
+    //                note, that the exact value is not defined in STANDARD
+    // HASHCOPY_OFFSET   - offset for copy of current hash in "copies" array
+    // EXTRAFRAME_OFFSET - offset for extra frame in "copies" array;
+    //                     as the extra frame follows the current hash frame,
+    //                     EXTRAFRAME_OFFSET is equal to length of current hash frame
+    // FRAME_OFFSET      - offset for frame in "copies" array
+    // MAX_BYTES - maximum # of seed bytes processing which doesn't require extra frame
+    //             see (1) comments on usage of "seed" array below and
+    //             (2) comments in "engineNextBytes(byte[])" method
+    //
+    // UNDEFINED  - three states of engine; initially its state is "UNDEFINED"
+    // SET_SEED     call to "engineSetSeed"  sets up "SET_SEED" state,
+    // NEXT_BYTES   call to "engineNextByte" sets up "NEXT_BYTES" state
+
+    private static final int COUNTER_BASE = 0;
+
+    private static final int HASHCOPY_OFFSET = 0;
+
+    private static final int EXTRAFRAME_OFFSET = 5;
+
+    private static final int FRAME_OFFSET = 21;
+
+    private static final int MAX_BYTES = 48;
+
+    private static final int UNDEFINED = 0;
+
+    private static final int SET_SEED = 1;
+
+    private static final int NEXT_BYTES = 2;
+
+    // Structure of "seed" array:
+    // -  0-79 - words for computing hash
+    // - 80    - unused
+    // - 81    - # of seed bytes in current seed frame
+    // - 82-86 - 5 words, current seed hash
+    private transient int[] seed;
+
+    // total length of seed bytes, including all processed
+    private transient long seedLength;
+
+    // Structure of "copies" array
+    // -  0-4  - 5 words, copy of current seed hash
+    // -  5-20 - extra 16 words frame;
+    //           is used if final padding exceeds 512-bit length
+    // - 21-36 - 16 word frame to store a copy of remaining bytes
+    private transient int[] copies;
+
+    // ready "next" bytes; needed because words are returned
+    private transient byte[] nextBytes;
+
+    // index of used bytes in "nextBytes" array
+    private transient int nextBIndex;
+
+    // variable required according to "SECURE HASH STANDARD"
+    private transient long counter;
+
+    // contains int value corresponding to engine's current state
+    private transient int state;
+
+    /**
+     *  constant defined in "SECURE HASH STANDARD"
+     */
+    private static final int H0 = 0x67452301;
+
+
+    /**
+     *  constant defined in "SECURE HASH STANDARD"
+     */
+    private static final int H1 = 0xEFCDAB89;
+
+
+    /**
+     *  constant defined in "SECURE HASH STANDARD"
+     */
+    private static final int H2 = 0x98BADCFE;
+
+
+    /**
+     *  constant defined in "SECURE HASH STANDARD"
+     */
+    private static final int H3 = 0x10325476;
+
+
+    /**
+     *  constant defined in "SECURE HASH STANDARD"
+     */
+    private static final int H4 = 0xC3D2E1F0;
+
+
+    /**
+     * offset in buffer to store number of bytes in 0-15 word frame
+     */
+    private static final int BYTES_OFFSET = 81;
+
+
+    /**
+     * offset in buffer to store current hash value
+     */
+    private static final int HASH_OFFSET = 82;
+
+
+    /**
+     * # of bytes in H0-H4 words; <BR>
+     * in this implementation # is set to 20 (in general # varies from 1 to 20)
+     */
+    private static final int DIGEST_LENGTH = 20;
+
+    // The "seed" array is used to compute both "current seed hash" and "next bytes".
+    //
+    // As the "SHA1" algorithm computes a hash of entire seed by splitting it into
+    // a number of the 512-bit length frames (512 bits = 64 bytes = 16 words),
+    // "current seed hash" is a hash (5 words, 20 bytes) for all previous full frames;
+    // remaining bytes are stored in the 0-15 word frame of the "seed" array.
+    //
+    // As for calculating "next bytes",
+    // both remaining bytes and "current seed hash" are used,
+    // to preserve the latter for following "setSeed(..)" commands,
+    // the following technique is used:
+    // - upon getting "nextBytes(byte[])" invoked, single or first in row,
+    //   which requires computing new hash, that is,
+    //   there is no more bytes remaining from previous "next bytes" computation,
+    //   remaining bytes are copied into the 21-36 word frame of the "copies" array;
+    // - upon getting "setSeed(byte[])" invoked, single or first in row,
+    //   remaining bytes are copied back.
+
+    private InsecureSHA1PRNGKeyDerivator() {
+        seed = new int[HASH_OFFSET + EXTRAFRAME_OFFSET];
+        seed[HASH_OFFSET] = H0;
+        seed[HASH_OFFSET + 1] = H1;
+        seed[HASH_OFFSET + 2] = H2;
+        seed[HASH_OFFSET + 3] = H3;
+        seed[HASH_OFFSET + 4] = H4;
+
+        seedLength = 0;
+        copies = new int[2 * FRAME_LENGTH + EXTRAFRAME_OFFSET];
+        nextBytes = new byte[DIGEST_LENGTH];
+        nextBIndex = HASHBYTES_TO_USE;
+        counter = COUNTER_BASE;
+        state = UNDEFINED;
+    }
+
+    /*
+     * The method invokes the SHA1Impl's "updateHash(..)" method
+     * to update current seed frame and
+     * to compute new intermediate hash value if the frame is full.
+     *
+     * After that it computes a length of whole seed.
+     */
+    private void updateSeed(byte[] bytes) {
+
+        // on call:   "seed" contains current bytes and current hash;
+        // on return: "seed" contains new current bytes and possibly new current hash
+        //            if after adding, seed bytes overfill its buffer
+        updateHash(seed, bytes, 0, bytes.length - 1);
+
+        seedLength += bytes.length;
+    }
+
+    /**
+     * Changes current seed by supplementing a seed argument to the current seed,
+     * if this already set;
+     * the argument is used as first seed otherwise. <BR>
+     *
+     * The method overrides "engineSetSeed(byte[])" in class SecureRandomSpi.
+     *
+     * @param
+     *       seed - byte array
+     * @throws
+     *       NullPointerException - if null is passed to the "seed" argument
+     */
+    private void setSeed(byte[] seed) {
+        if (seed == null) {
+            throw new NullPointerException("seed == null");
+        }
+
+        if (state == NEXT_BYTES) { // first setSeed after NextBytes; restoring hash
+            System.arraycopy(copies, HASHCOPY_OFFSET, this.seed, HASH_OFFSET,
+                    EXTRAFRAME_OFFSET);
+        }
+        state = SET_SEED;
+
+        if (seed.length != 0) {
+            updateSeed(seed);
+        }
+    }
+
+    /**
+     * Writes random bytes into an array supplied.
+     * Bits in a byte are from left to right. <BR>
+     *
+     * To generate random bytes, the "expansion of source bits" method is used,
+     * that is,
+     * the current seed with a 64-bit counter appended is used to compute new bits.
+     * The counter is incremented by 1 for each 20-byte output. <BR>
+     *
+     * The method overrides engineNextBytes in class SecureRandomSpi.
+     *
+     * @param
+     *       bytes - byte array to be filled in with bytes
+     * @throws
+     *       NullPointerException - if null is passed to the "bytes" argument
+     */
+    protected synchronized void nextBytes(byte[] bytes) {
+
+        int i, n;
+
+        long bits; // number of bits required by Secure Hash Standard
+        int nextByteToReturn; // index of ready bytes in "bytes" array
+        int lastWord; // index of last word in frame containing bytes
+
+        // This is a bug since words are 4 bytes. Android used to keep it this way for backward
+        // compatibility.
+        final int extrabytes = 7;// # of bytes to add in order to computer # of 8 byte words
+
+        if (bytes == null) {
+            throw new NullPointerException("bytes == null");
+        }
+
+        // This is a bug since extraBytes == 7 instead of 3. Android used to keep it this way for
+        // backward compatibility.
+        lastWord = seed[BYTES_OFFSET] == 0 ? 0
+                : (seed[BYTES_OFFSET] + extrabytes) >> 3 - 1;
+
+        if (state == UNDEFINED) {
+
+            throw new IllegalStateException("No seed supplied!");
+
+        } else if (state == SET_SEED) {
+
+            System.arraycopy(seed, HASH_OFFSET, copies, HASHCOPY_OFFSET,
+                    EXTRAFRAME_OFFSET);
+
+            // possible cases for 64-byte frame:
+            //
+            // seed bytes < 48      - remaining bytes are enough for all, 8 counter bytes,
+            //                        0x80, and 8 seedLength bytes; no extra frame required
+            // 48 < seed bytes < 56 - remaining 9 bytes are for 0x80 and 8 counter bytes
+            //                        extra frame contains only seedLength value at the end
+            // seed bytes > 55      - extra frame contains both counter's bytes
+            //                        at the beginning and seedLength value at the end;
+            //                        note, that beginning extra bytes are not more than 8,
+            //                        that is, only 2 extra words may be used
+
+            // no need to set to "0" 3 words after "lastWord" and
+            // more than two words behind frame
+            for (i = lastWord + 3; i < FRAME_LENGTH + 2; i++) {
+                seed[i] = 0;
+            }
+
+            bits = (seedLength << 3) + 64; // transforming # of bytes into # of bits
+
+            // putting # of bits into two last words (14,15) of 16 word frame in
+            // seed or copies array depending on total length after padding
+            if (seed[BYTES_OFFSET] < MAX_BYTES) {
+                seed[14] = (int) (bits >>> 32);
+                seed[15] = (int) (bits & 0xFFFFFFFF);
+            } else {
+                copies[EXTRAFRAME_OFFSET + 14] = (int) (bits >>> 32);
+                copies[EXTRAFRAME_OFFSET + 15] = (int) (bits & 0xFFFFFFFF);
+            }
+
+            nextBIndex = HASHBYTES_TO_USE; // skipping remaining random bits
+        }
+        state = NEXT_BYTES;
+
+        if (bytes.length == 0) {
+            return;
+        }
+
+        nextByteToReturn = 0;
+
+        // possibly not all of HASHBYTES_TO_USE bytes were used previous time
+        n = (HASHBYTES_TO_USE - nextBIndex) < (bytes.length - nextByteToReturn) ? HASHBYTES_TO_USE
+                - nextBIndex
+                : bytes.length - nextByteToReturn;
+        if (n > 0) {
+            System.arraycopy(nextBytes, nextBIndex, bytes, nextByteToReturn, n);
+            nextBIndex += n;
+            nextByteToReturn += n;
+        }
+
+        if (nextByteToReturn >= bytes.length) {
+            return; // return because "bytes[]" are filled in
+        }
+
+        n = seed[BYTES_OFFSET] & 0x03;
+        for (;;) {
+            if (n == 0) {
+
+                seed[lastWord] = (int) (counter >>> 32);
+                seed[lastWord + 1] = (int) (counter & 0xFFFFFFFF);
+                seed[lastWord + 2] = END_FLAGS[0];
+
+            } else {
+
+                seed[lastWord] |= (int) ((counter >>> RIGHT1[n]) & MASK[n]);
+                seed[lastWord + 1] = (int) ((counter >>> RIGHT2[n]) & 0xFFFFFFFF);
+                seed[lastWord + 2] = (int) ((counter << LEFT[n]) | END_FLAGS[n]);
+            }
+            if (seed[BYTES_OFFSET] > MAX_BYTES) {
+                copies[EXTRAFRAME_OFFSET] = seed[FRAME_LENGTH];
+                copies[EXTRAFRAME_OFFSET + 1] = seed[FRAME_LENGTH + 1];
+            }
+
+            computeHash(seed);
+
+            if (seed[BYTES_OFFSET] > MAX_BYTES) {
+
+                System.arraycopy(seed, 0, copies, FRAME_OFFSET, FRAME_LENGTH);
+                System.arraycopy(copies, EXTRAFRAME_OFFSET, seed, 0,
+                        FRAME_LENGTH);
+
+                computeHash(seed);
+                System.arraycopy(copies, FRAME_OFFSET, seed, 0, FRAME_LENGTH);
+            }
+            counter++;
+
+            int j = 0;
+            for (i = 0; i < EXTRAFRAME_OFFSET; i++) {
+                int k = seed[HASH_OFFSET + i];
+                nextBytes[j] = (byte) (k >>> 24); // getting first  byte from left
+                nextBytes[j + 1] = (byte) (k >>> 16); // getting second byte from left
+                nextBytes[j + 2] = (byte) (k >>> 8); // getting third  byte from left
+                nextBytes[j + 3] = (byte) (k); // getting fourth byte from left
+                j += 4;
+            }
+
+            nextBIndex = 0;
+            j = HASHBYTES_TO_USE < (bytes.length - nextByteToReturn) ? HASHBYTES_TO_USE
+                    : bytes.length - nextByteToReturn;
+
+            if (j > 0) {
+                System.arraycopy(nextBytes, 0, bytes, nextByteToReturn, j);
+                nextByteToReturn += j;
+                nextBIndex += j;
+            }
+
+            if (nextByteToReturn >= bytes.length) {
+                break;
+            }
+        }
+    }
+
+    /**
+     * The method generates a 160 bit hash value using
+     * a 512 bit message stored in first 16 words of int[] array argument and
+     * current hash value stored in five words, beginning OFFSET+1, of the array argument.
+     * Computation is done according to SHA-1 algorithm.
+     *
+     * The resulting hash value replaces the previous hash value in the array;
+     * original bits of the message are not preserved.
+     *
+     * No checks on argument supplied, that is,
+     * a calling method is responsible for such checks.
+     * In case of incorrect array passed to the method
+     * either NPE or IndexOutOfBoundException gets thrown by JVM.
+     *
+     * @params
+     *        arrW - integer array; arrW.length >= (BYTES_OFFSET+6); <BR>
+     *               only first (BYTES_OFFSET+6) words are used
+     */
+    private static void computeHash(int[] arrW) {
+
+        int  a = arrW[HASH_OFFSET   ];
+        int  b = arrW[HASH_OFFSET +1];
+        int  c = arrW[HASH_OFFSET +2];
+        int  d = arrW[HASH_OFFSET +3];
+        int  e = arrW[HASH_OFFSET +4];
+
+        int temp;
+
+        // In this implementation the "d. For t = 0 to 79 do" loop
+        // is split into four loops. The following constants:
+        //     K = 5A827999   0 <= t <= 19
+        //     K = 6ED9EBA1  20 <= t <= 39
+        //     K = 8F1BBCDC  40 <= t <= 59
+        //     K = CA62C1D6  60 <= t <= 79
+        // are hex literals in the loops.
+
+        for ( int t = 16; t < 80 ; t++ ) {
+
+            temp  = arrW[t-3] ^ arrW[t-8] ^ arrW[t-14] ^ arrW[t-16];
+            arrW[t] = ( temp<<1 ) | ( temp>>>31 );
+        }
+
+        for ( int t = 0 ; t < 20 ; t++ ) {
+
+            temp = ( ( a<<5 ) | ( a>>>27 )   ) +
+                    ( ( b & c) | ((~b) & d)   ) +
+                    ( e + arrW[t] + 0x5A827999 ) ;
+            e = d;
+            d = c;
+            c = ( b<<30 ) | ( b>>>2 ) ;
+            b = a;
+            a = temp;
+        }
+        for ( int t = 20 ; t < 40 ; t++ ) {
+
+            temp = ((( a<<5 ) | ( a>>>27 ))) + (b ^ c ^ d) + (e + arrW[t] + 0x6ED9EBA1) ;
+            e = d;
+            d = c;
+            c = ( b<<30 ) | ( b>>>2 ) ;
+            b = a;
+            a = temp;
+        }
+        for ( int t = 40 ; t < 60 ; t++ ) {
+
+            temp = (( a<<5 ) | ( a>>>27 )) + ((b & c) | (b & d) | (c & d)) +
+                    (e + arrW[t] + 0x8F1BBCDC) ;
+            e = d;
+            d = c;
+            c = ( b<<30 ) | ( b>>>2 ) ;
+            b = a;
+            a = temp;
+        }
+        for ( int t = 60 ; t < 80 ; t++ ) {
+
+            temp = ((( a<<5 ) | ( a>>>27 ))) + (b ^ c ^ d) + (e + arrW[t] + 0xCA62C1D6) ;
+            e = d;
+            d = c;
+            c = ( b<<30 ) | ( b>>>2 ) ;
+            b = a;
+            a = temp;
+        }
+
+        arrW[HASH_OFFSET   ] += a;
+        arrW[HASH_OFFSET +1] += b;
+        arrW[HASH_OFFSET +2] += c;
+        arrW[HASH_OFFSET +3] += d;
+        arrW[HASH_OFFSET +4] += e;
+    }
+
+    /**
+     * The method appends new bytes to existing ones
+     * within limit of a frame of 64 bytes (16 words).
+     *
+     * Once a length of accumulated bytes reaches the limit
+     * the "computeHash(int[])" method is invoked on the array to compute updated hash,
+     * and the number of bytes in the frame is set to 0.
+     * Thus, after appending all bytes, the array contain only those bytes
+     * that were not used in computing final hash value yet.
+     *
+     * No checks on arguments passed to the method, that is,
+     * a calling method is responsible for such checks.
+     *
+     * @params
+     *        intArray  - int array containing bytes to which to append;
+     *                    intArray.length >= (BYTES_OFFSET+6)
+     * @params
+     *        byteInput - array of bytes to use for the update
+     * @params
+     *        from      - the offset to start in the "byteInput" array
+     * @params
+     *        to        - a number of the last byte in the input array to use,
+     *                that is, for first byte "to"==0, for last byte "to"==input.length-1
+     */
+    private static void updateHash(int[] intArray, byte[] byteInput, int fromByte, int toByte) {
+
+        // As intArray contains a packed bytes
+        // the buffer's index is in the intArray[BYTES_OFFSET] element
+
+        int index = intArray[BYTES_OFFSET];
+        int i = fromByte;
+        int maxWord;
+        int nBytes;
+
+        int wordIndex = index >>2;
+        int byteIndex = index & 0x03;
+
+        intArray[BYTES_OFFSET] = ( index + toByte - fromByte + 1 ) & 077 ;
+
+        // In general case there are 3 stages :
+        // - appending bytes to non-full word,
+        // - writing 4 bytes into empty words,
+        // - writing less than 4 bytes in last word
+
+        if ( byteIndex != 0 ) {       // appending bytes in non-full word (as if)
+
+            for ( ; ( i <= toByte ) && ( byteIndex < 4 ) ; i++ ) {
+                intArray[wordIndex] |= ( byteInput[i] & 0xFF ) << ((3 - byteIndex)<<3) ;
+                byteIndex++;
+            }
+            if ( byteIndex == 4 ) {
+                wordIndex++;
+                if ( wordIndex == 16 ) {          // intArray is full, computing hash
+
+                    computeHash(intArray);
+                    wordIndex = 0;
+                }
+            }
+            if ( i > toByte ) {                 // all input bytes appended
+                return ;
+            }
+        }
+
+        // writing full words
+
+        maxWord = (toByte - i + 1) >> 2;           // # of remaining full words, may be "0"
+        for ( int k = 0; k < maxWord ; k++ ) {
+
+            intArray[wordIndex] = ( ((int) byteInput[i   ] & 0xFF) <<24 ) |
+                    ( ((int) byteInput[i +1] & 0xFF) <<16 ) |
+                    ( ((int) byteInput[i +2] & 0xFF) <<8  ) |
+                    ( ((int) byteInput[i +3] & 0xFF)      )  ;
+            i += 4;
+            wordIndex++;
+
+            if ( wordIndex < 16 ) {     // buffer is not full yet
+                continue;
+            }
+            computeHash(intArray);      // buffer is full, computing hash
+            wordIndex = 0;
+        }
+
+        // writing last incomplete word
+        // after writing free byte positions are set to "0"s
+
+        nBytes = toByte - i +1;
+        if ( nBytes != 0 ) {
+
+            int w =  ((int) byteInput[i] & 0xFF) <<24 ;
+
+            if ( nBytes != 1 ) {
+                w |= ((int) byteInput[i +1] & 0xFF) <<16 ;
+                if ( nBytes != 2) {
+                    w |= ((int) byteInput[i +2] & 0xFF) <<8 ;
+                }
+            }
+            intArray[wordIndex] = w;
+        }
+
+        return ;
+    }
+}
diff --git a/samples/BrokenKeyDerivation/tests/Android.mk b/samples/BrokenKeyDerivation/tests/Android.mk
new file mode 100644
index 0000000..fc161a9
--- /dev/null
+++ b/samples/BrokenKeyDerivation/tests/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_PACKAGE_NAME := BrokenKeyDerivationTests
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_INSTRUMENTATION_FOR := BrokenKeyDerivation
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/samples/BrokenKeyDerivation/tests/AndroidManifest.xml b/samples/BrokenKeyDerivation/tests/AndroidManifest.xml
new file mode 100644
index 0000000..323e227
--- /dev/null
+++ b/samples/BrokenKeyDerivation/tests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
+      package="com.example.android.brokenkeyderivation.tests">
+
+    <!-- We add an application tag here just so that we can indicate that
+         this package needs to link against the android.test library,
+         which is needed when building test cases. -->    
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+  <instrumentation android:name="android.test.InstrumentationTestRunner"
+      android:targetPackage="com.example.android.brokenkeyderivation"
+      android:label="BrokenKeyDerivation tests">
+  </instrumentation>  
+  
+</manifest>
diff --git a/samples/BrokenKeyDerivation/tests/build.properties b/samples/BrokenKeyDerivation/tests/build.properties
new file mode 100644
index 0000000..e0c39de
--- /dev/null
+++ b/samples/BrokenKeyDerivation/tests/build.properties
@@ -0,0 +1 @@
+tested.project.dir=..
diff --git a/samples/BrokenKeyDerivation/tests/src/com/example/android/brokenkeyderivation/BrokenKeyDerivationTest.java b/samples/BrokenKeyDerivation/tests/src/com/example/android/brokenkeyderivation/BrokenKeyDerivationTest.java
new file mode 100644
index 0000000..14e11df
--- /dev/null
+++ b/samples/BrokenKeyDerivation/tests/src/com/example/android/brokenkeyderivation/BrokenKeyDerivationTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.example.android.brokenkeyderivation;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+/**
+ * Make sure that the main launcher activity opens up properly, which will be
+ * verified by {@link #testActivityTestCaseSetUpProperly}.
+ */
+public class BrokenKeyDerivationTest extends ActivityInstrumentationTestCase2<BrokenKeyDerivationActivity> {
+
+    /**
+     * Creates an {@link ActivityInstrumentationTestCase2} for the {@link BrokenKeyDerivationActivity} activity.
+     */
+    public BrokenKeyDerivationTest() {
+        super(BrokenKeyDerivationActivity.class);
+    }
+
+    /**
+     * Verifies that the activity under test can be launched.
+     */
+    public void testActivityTestCaseSetUpProperly() {
+        assertNotNull("activity should be launched successfully", getActivity());
+    }
+}