Skip to content

Commit 1171672

Browse files
Sergio GiroAndroid (Google) Code Review
authored andcommitted
Merge "KeyDerivationFunction: example about treating data encrypted via SHA1PRNG" into nyc-dev
2 parents 48e6404 + 952349c commit 1171672

File tree

10 files changed

+1076
-0
lines changed

10 files changed

+1076
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
LOCAL_PATH:= $(call my-dir)
2+
include $(CLEAR_VARS)
3+
4+
LOCAL_MODULE_TAGS := samples
5+
6+
# Only compile source java files in this apk.
7+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
8+
9+
LOCAL_PACKAGE_NAME := BrokenKeyDerivation
10+
11+
LOCAL_SDK_VERSION := current
12+
13+
include $(BUILD_PACKAGE)
14+
15+
# Use the following include to make our test apk.
16+
include $(call all-makefiles-under,$(LOCAL_PATH))
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Copyright (C) 2007 The Android Open Source Project
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
17+
<!-- Declare the contents of this Android application. The namespace
18+
attribute brings in the Android platform namespace, and the package
19+
supplies a unique name for the application. When writing your
20+
own application, the package name must be changed from "com.example.*"
21+
to come from a domain that you own or have control over. -->
22+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
23+
package="com.example.android.brokenkeyderivation">
24+
<application android:label="Broken Key Derivation">
25+
<activity android:name="BrokenKeyDerivationActivity">
26+
<intent-filter>
27+
<action android:name="android.intent.action.MAIN"/>
28+
<category android:name="android.intent.category.LAUNCHER"/>
29+
</intent-filter>
30+
</activity>
31+
</application>
32+
</manifest>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Copyright (C) 2007 The Android Open Source Project
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
17+
<EditText xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text"
18+
android:layout_width="match_parent"
19+
android:layout_height="match_parent"
20+
android:textSize="18sp"
21+
android:autoText="true"
22+
android:capitalize="sentences" />
23+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Copyright (C) 2007 The Android Open Source Project
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
17+
<resources>
18+
19+
</resources>
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
/*
2+
* Copyright (C) 2007 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.android.brokenkeyderivation;
18+
19+
import android.app.Activity;
20+
import android.content.Context;
21+
import android.os.Bundle;
22+
import android.view.View;
23+
import android.view.WindowManager;
24+
import android.widget.EditText;
25+
26+
import java.io.File;
27+
import java.io.FileInputStream;
28+
import java.io.FileOutputStream;
29+
import java.io.IOException;
30+
import java.nio.charset.StandardCharsets;
31+
import java.security.GeneralSecurityException;
32+
import java.security.SecureRandom;
33+
import java.security.spec.KeySpec;
34+
35+
import javax.crypto.Cipher;
36+
import javax.crypto.SecretKey;
37+
import javax.crypto.SecretKeyFactory;
38+
import javax.crypto.spec.IvParameterSpec;
39+
import javax.crypto.spec.PBEKeySpec;
40+
import javax.crypto.spec.SecretKeySpec;
41+
42+
43+
/**
44+
* Example showing how to decrypt data that was encrypted using SHA1PRNG.
45+
*
46+
* The Crypto provider providing the SHA1PRNG algorithm for random number
47+
* generation is deprecated as of SDK 24.
48+
*
49+
* This algorithm was sometimes incorrectly used to derive keys. See
50+
* <a href="http://android-developers.blogspot.co.uk/2013/02/using-cryptography-to-store-credentials.html">
51+
* here</a> for details.
52+
53+
* This example provides a helper class ({@link InsecureSHA1PRNGKeyDerivator} and shows how to treat
54+
* data that was encrypted in the incorrect way and re-encrypt it in a proper way,
55+
* by using a key derivation function.
56+
*
57+
* The {@link #onCreate(Bundle)} method retrieves encrypted data twice and displays the results.
58+
*
59+
* The mock data is encrypted with an insecure key. The first time it is reencrypted properly and
60+
* the plain text is returned together with a warning message. The second time, as the data is
61+
* properly encrypted, the plain text is returned with a congratulations message.
62+
*/
63+
public class BrokenKeyDerivationActivity extends Activity {
64+
/**
65+
* Method used to derive an <b>insecure</b> key by emulating the SHA1PRNG algorithm from the
66+
* deprecated Crypto provider.
67+
*
68+
* Do not use it to encrypt new data, just to decrypt encrypted data that would be unrecoverable
69+
* otherwise.
70+
*/
71+
private static SecretKey deriveKeyInsecurely(String password, int keySizeInBytes) {
72+
byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
73+
return new SecretKeySpec(
74+
InsecureSHA1PRNGKeyDerivator.deriveInsecureKey(passwordBytes, keySizeInBytes),
75+
"AES");
76+
}
77+
78+
/**
79+
* Example use of a key derivation function, derivating a key securely from a password.
80+
*/
81+
private SecretKey deriveKeySecurely(String password, int keySizeInBytes) {
82+
// Use this to derive the key from the password:
83+
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), retrieveSalt(),
84+
100 /* iterationCount */, keySizeInBytes * 8 /* key size in bits */);
85+
try {
86+
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
87+
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
88+
return new SecretKeySpec(keyBytes, "AES");
89+
} catch (Exception e) {
90+
throw new RuntimeException("Deal with exceptions properly!", e);
91+
}
92+
}
93+
94+
/**
95+
* Retrieve encrypted data using a password. If data is stored with an insecure key, re-encrypt
96+
* with a secure key.
97+
*/
98+
private String retrieveData(String password) {
99+
String decryptedString;
100+
101+
if (isDataStoredWithInsecureKey()) {
102+
SecretKey insecureKey = deriveKeyInsecurely(password, KEY_SIZE);
103+
byte[] decryptedData = decryptData(retrieveEncryptedData(), retrieveIv(), insecureKey);
104+
SecretKey secureKey = deriveKeySecurely(password, KEY_SIZE);
105+
storeDataEncryptedWithSecureKey(encryptData(decryptedData, retrieveIv(), secureKey));
106+
decryptedString = "Warning: data was encrypted with insecure key\n"
107+
+ new String(decryptedData, StandardCharsets.UTF_8);
108+
} else {
109+
SecretKey secureKey = deriveKeySecurely(password, KEY_SIZE);
110+
byte[] decryptedData = decryptData(retrieveEncryptedData(), retrieveIv(), secureKey);
111+
decryptedString = "Great!: data was encrypted with secure key\n"
112+
+ new String(decryptedData, StandardCharsets.UTF_8);
113+
}
114+
return decryptedString;
115+
}
116+
117+
/*
118+
***********************************************************************************************
119+
* The essential point of this example are the three methods above. Everything below this
120+
* comment just gives a concrete example of usage and defines mock methods.
121+
***********************************************************************************************
122+
*/
123+
124+
/**
125+
* Retrieves encrypted data twice and displays the results.
126+
*
127+
* The mock data is encrypted with an insecure key (see {@link #cleanRoomStart()}) and so the
128+
* first time {@link #retrieveData(String)} reencrypts it and returns the plain text with a
129+
* warning message. The second time, as the data is properly encrypted, the plain text is
130+
* returned with a congratulations message.
131+
*/
132+
@Override
133+
public void onCreate(Bundle savedInstanceState) {
134+
super.onCreate(savedInstanceState);
135+
136+
// Remove any files from previous executions of this app and initialize mock encrypted data.
137+
// Just so that the application has the same behaviour every time is run. You don't need to
138+
// do this in your app.
139+
cleanRoomStart();
140+
141+
// Set the layout for this activity. You can find it
142+
// in res/layout/brokenkeyderivation_activity.xml
143+
View view = getLayoutInflater().inflate(R.layout.brokenkeyderivation_activity, null);
144+
setContentView(view);
145+
146+
// Find the text editor view inside the layout.
147+
EditText mEditor = (EditText) findViewById(R.id.text);
148+
149+
String password = "unguessable";
150+
String firstResult = retrieveData(password);
151+
String secondResult = retrieveData(password);
152+
153+
mEditor.setText("First result: " + firstResult + "\nSecond result: " + secondResult);
154+
155+
}
156+
157+
private static byte[] encryptOrDecrypt(
158+
byte[] data, SecretKey key, byte[] iv, boolean isEncrypt) {
159+
try {
160+
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING");
161+
cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key,
162+
new IvParameterSpec(iv));
163+
return cipher.doFinal(data);
164+
} catch (GeneralSecurityException e) {
165+
throw new RuntimeException("This is unconceivable!", e);
166+
}
167+
}
168+
169+
private static byte[] encryptData(byte[] data, byte[] iv, SecretKey key) {
170+
return encryptOrDecrypt(data, key, iv, true);
171+
}
172+
173+
private static byte[] decryptData(byte[] data, byte[] iv, SecretKey key) {
174+
return encryptOrDecrypt(data, key, iv, false);
175+
}
176+
177+
/**
178+
* Remove any files from previous executions of this app and initialize mock encrypted data.
179+
*
180+
* <p>Just so that the application has the same behaviour every time is run. You don't need to
181+
* do this in your app.
182+
*/
183+
private void cleanRoomStart() {
184+
removeFile("salt");
185+
removeFile("iv");
186+
removeFile(SECURE_ENCRYPTION_INDICATOR_FILE_NAME);
187+
// Mock initial data
188+
encryptedData = encryptData(
189+
"I hope it helped!".getBytes(), retrieveIv(),
190+
deriveKeyInsecurely("unguessable", KEY_SIZE));
191+
}
192+
193+
/*
194+
***********************************************************************************************
195+
* Everything below this comment is a succession of mocks that would rarely interest someone on
196+
* Earth. They are merely intended to make the example self contained.
197+
***********************************************************************************************
198+
*/
199+
200+
private boolean isDataStoredWithInsecureKey() {
201+
// Your app should have a way to tell whether the data has been re-encrypted in a secure
202+
// fashion, in this mock we use the existence of a file with a certain name to indicate
203+
// that.
204+
return !fileExists("encrypted_with_secure_key");
205+
}
206+
207+
private byte[] retrieveIv() {
208+
byte[] iv = new byte[IV_SIZE];
209+
// Ideally your data should have been encrypted with a random iv. This creates a random iv
210+
// if not present, in order to encrypt our mock data.
211+
readFromFileOrCreateRandom("iv", iv);
212+
return iv;
213+
}
214+
215+
private byte[] retrieveSalt() {
216+
// Salt must be at least the same size as the key.
217+
byte[] salt = new byte[KEY_SIZE];
218+
// Create a random salt if encrypting for the first time, and save it for future use.
219+
readFromFileOrCreateRandom("salt", salt);
220+
return salt;
221+
}
222+
223+
private byte[] encryptedData = null;
224+
225+
private byte[] retrieveEncryptedData() {
226+
return encryptedData;
227+
}
228+
229+
private void storeDataEncryptedWithSecureKey(byte[] encryptedData) {
230+
// Mock implementation.
231+
this.encryptedData = encryptedData;
232+
writeToFile(SECURE_ENCRYPTION_INDICATOR_FILE_NAME, new byte[1]);
233+
}
234+
235+
/**
236+
* Read from file or return random bytes in the given array.
237+
*
238+
* <p>Save to file if file didn't exist.
239+
*/
240+
private void readFromFileOrCreateRandom(String fileName, byte[] bytes) {
241+
if (fileExists(fileName)) {
242+
readBytesFromFile(fileName, bytes);
243+
return;
244+
}
245+
SecureRandom sr = new SecureRandom();
246+
sr.nextBytes(bytes);
247+
writeToFile(fileName, bytes);
248+
}
249+
250+
private boolean fileExists(String fileName) {
251+
File file = new File(getFilesDir(), fileName);
252+
return file.exists();
253+
}
254+
255+
private void removeFile(String fileName) {
256+
File file = new File(getFilesDir(), fileName);
257+
file.delete();
258+
}
259+
260+
private void writeToFile(String fileName, byte[] bytes) {
261+
try (FileOutputStream fos = openFileOutput(fileName, Context.MODE_PRIVATE)) {
262+
fos.write(bytes);
263+
} catch (IOException e) {
264+
throw new RuntimeException("Couldn't write to " + fileName, e);
265+
}
266+
}
267+
268+
private void readBytesFromFile(String fileName, byte[] bytes) {
269+
try (FileInputStream fis = openFileInput(fileName)) {
270+
int numBytes = 0;
271+
while (numBytes < bytes.length) {
272+
int n = fis.read(bytes, numBytes, bytes.length - numBytes);
273+
if (n <= 0) {
274+
throw new RuntimeException("Couldn't read from " + fileName);
275+
}
276+
numBytes += n;
277+
}
278+
} catch (IOException e) {
279+
throw new RuntimeException("Couldn't read from " + fileName, e);
280+
}
281+
}
282+
283+
private static final int IV_SIZE = 16;
284+
private static final int KEY_SIZE = 32;
285+
private static final String SECURE_ENCRYPTION_INDICATOR_FILE_NAME =
286+
"encrypted_with_secure_key";
287+
}
288+

0 commit comments

Comments
 (0)