├── .gitignore
├── .travis.yml
├── README.md
├── code-quality
├── android_checks.xml
└── suppressions.xml
├── pom.xml
├── secure-preferences
├── .gitignore
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ ├── com
│ │ └── github
│ │ │ └── kovmarci86
│ │ │ └── android
│ │ │ └── secure
│ │ │ └── preferences
│ │ │ ├── SecureFactory.java
│ │ │ ├── SecureSharedPreferences.java
│ │ │ ├── SecuredEditor.java
│ │ │ ├── encryption
│ │ │ ├── EncryptionAlgorithm.java
│ │ │ ├── EncryptionException.java
│ │ │ └── EncryptionHelper.java
│ │ │ └── util
│ │ │ └── SecureUtils.java
│ │ └── edu
│ │ └── gmu
│ │ └── tec
│ │ └── scout
│ │ └── utilities
│ │ └── Encryption.java
│ └── test
│ └── java
│ └── com
│ └── github
│ └── kovmarci86
│ └── android
│ └── secure
│ └── preferences
│ ├── SecureFactoryTest.java
│ ├── SecureSharedPreferencesTest.java
│ ├── SecuredEditorTest.java
│ ├── encryption
│ ├── EncryptionExceptionTest.java
│ └── EncryptionHelperTest.java
│ └── util
│ └── SecureUtilsTest.java
├── secure-storage-it
├── .gitignore
├── AndroidManifest.xml
├── pom.xml
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ └── icon.png
│ ├── drawable-ldpi
│ │ └── icon.png
│ ├── drawable-mdpi
│ │ └── icon.png
│ ├── layout
│ │ └── main.xml
│ └── values
│ │ └── strings.xml
└── src
│ └── main
│ └── java
│ └── com
│ └── github
│ └── kovmarci86
│ └── android
│ └── secure
│ └── preferences
│ ├── SecureFactoryFunctionalTest.java
│ └── SecureSharedPreferencesFunctionalTest.java
├── secure-storage-sample-app
├── .gitignore
├── AndroidManifest.xml
├── androlog
│ └── androlog.properties
├── pom.xml
├── proguard.conf
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ └── icon.png
│ ├── drawable-ldpi
│ │ └── icon.png
│ ├── drawable-mdpi
│ │ └── icon.png
│ ├── layout
│ │ ├── activity_main.xml
│ │ └── activity_secure_login.xml
│ └── values
│ │ ├── strings.xml
│ │ └── strings_activity_secure_login.xml
└── src
│ └── main
│ └── java
│ └── com
│ └── github
│ └── kovmarci86
│ └── android
│ └── secure
│ ├── MainActivity.java
│ ├── SecureLogin.java
│ └── service
│ └── settings
│ └── UserDataServce.java
└── test-key.keystore
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .project
3 | .settings
4 | .classpath
5 | bin/
6 | gen/
7 | .checkstyle
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | before_install:
3 | # download the latest android sdk and unzip
4 | - wget http://dl.google.com/android/android-sdk_r21-linux.tgz
5 | - tar -zxf android-sdk_r21-linux.tgz
6 | # setup your ANDROID_HOME and PATH environment variables
7 | # use ~/builds/[Github username]/[project]/android-sdk-linux
8 | - export ANDROID_HOME=`pwd`/android-sdk-linux
9 | - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
10 | - android list sdk -a
11 | - android update sdk --filter 1,2,12 --no-ui --force
12 | # - ls ${ANDROID_HOME}/platform-tools
13 | # - ls -l /home/travis/build/kovmarci86/android-secure-preferences/secure-storage-sample-app
14 | # - ls -l /home/travis/build/kovmarci86/android-secure-preferences/android-sdk-linux/platform-tools/
15 | # - ls -l /home/travis/build/kovmarci86/android-secure-preferences/android-sdk-linux/platforms/
16 | # - chmod 777 /home/travis/build/kovmarci86/android-secure-preferences/android-sdk-linux/platform-tools/*
17 | # - /home/travis/build/kovmarci86/android-secure-preferences/android-sdk-linux/platform-tools/aapt
18 | install:
19 | - echo "Building core"
20 | - cd secure-preferences
21 | - mvn test
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | android-secure-preferences
2 | ==========================
3 |
4 | About
5 | -----
6 | This project uses the Encryption class from:
7 | http://www.java2s.com/Code/Android/Security/AESEncryption.htm
8 |
9 | Gives an implementation of SharedPreferences, which encrypts given values with AES.
10 |
11 |
12 | Environment setup
13 | -----------------
14 | - install maven
15 | - install Android sdk
16 | - install sdk deployer ( https://github.com/mosabua/maven-android-sdk-deployer )
17 | - check out source to a directory.
18 |
19 | Build
20 | -----
21 | - mvn clean install
22 |
23 | Android compatibility
24 | ---------------------
25 | Project requires API level 8 due to Base64 Android API level requirements.
26 |
27 | Binary
28 | ------
29 | Until GitHub deprecates download section, use:
30 |
31 | https://github.com/kovmarci86/android-secure-preferences/downloads
32 |
--------------------------------------------------------------------------------
/code-quality/android_checks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/code-quality/suppressions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.github.kovmarci86.android.secure
6 | secure-storage-parent
7 | pom
8 | secure-storage - Parent
9 |
10 |
11 | secure-storage-sample-app
12 | secure-storage-it
13 | secure-preferences
14 |
15 |
16 |
17 |
18 | Marcell Kovacs (kovmarci86)
19 |
20 | Developer
21 |
22 |
23 | NoTiCe
24 |
25 |
26 |
27 | Tomas Prochazka (tprochazka)
28 |
29 | Contributor
30 |
31 |
32 | tprochazka
33 |
34 |
35 |
36 |
37 |
38 | scm:git:kovmarci86@github.com/kovmarci86/android-secure-preferences.git
39 | scm:git:kovmarci86@github.com/kovmarci86/android-secure-preferences.git
40 | git:git:kovmarci86@github.com/kovmarci86/android-secure-preferences.git
41 |
42 |
43 |
44 |
45 | starhost-andsecureprefs
46 | ftp://starhost.hu/web/andsecureprefs/
47 |
48 |
49 |
50 |
51 | 4.1.1.4
52 | github
53 |
54 | android-secure-preferences
55 | kovmarci86
56 |
57 |
58 |
59 |
60 |
61 | org.apache.maven.plugins
62 | maven-javadoc-plugin
63 | 2.9
64 |
65 | public
66 | ApiDocs
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | com.github.kovmarci86.android.secure
76 | secure-storage-sample-app
77 | 0.0.2-SNAPSHOT
78 | jar
79 | provided
80 |
81 |
82 | com.github.kovmarci86.android.secure
83 | secure-storage-sample-app
84 | apk
85 | 0.0.2-SNAPSHOT
86 | ${zipaligned-classifier}
87 | provided
88 |
89 |
90 | com.github.kovmarci86.android.secure
91 | secure-preferences
92 | 0.0.2-SNAPSHOT
93 |
94 |
95 | com.google.android
96 | android
97 | ${platform.version}
98 | provided
99 |
100 |
101 | com.google.android
102 | android-test
103 | ${platform.version}
104 | provided
105 |
106 |
107 |
108 |
109 | de.akquinet.android.androlog
110 | androlog
111 | 1.0.5
112 |
113 |
114 |
115 | junit
116 | junit
117 | 4.10
118 | provided
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 | com.github.github
127 | downloads-maven-plugin
128 | 0.5
129 |
130 | ${project.version} release of ${project.name}
131 | true
132 | true
133 |
134 |
135 |
136 |
137 |
138 |
139 | com.jayway.maven.plugins.android.generation2
140 | android-maven-plugin
141 | 3.6.0
142 | true
143 |
144 | ${project.basedir}/AndroidManifest.xml
145 | ${project.basedir}/assets
146 | ${project.basedir}/res
147 | ${project.basedir}/src/main/native
148 |
149 | 8
150 |
151 | true
152 |
153 |
154 |
155 | maven-compiler-plugin
156 | 2.5.1
157 | true
158 |
159 | 1.6
160 | 1.6
161 |
162 |
163 |
164 | maven-enforcer-plugin
165 | 1.0
166 |
167 |
168 | maven-release-plugin
169 | 2.1
170 |
171 | true
172 |
173 |
174 |
175 | maven-jarsigner-plugin
176 | 1.2
177 | true
178 |
179 | true
180 |
181 | ${project.build.directory}/${project.build.finalName}.${project.packaging}
182 | true
183 | true
184 | ${sign.keystore}
185 | ${sign.alias}
186 | ${sign.storepass}
187 | ${sign.keypass}
188 |
189 |
190 |
191 | com.pyx4me
192 | proguard-maven-plugin
193 | 2.0.4
194 |
195 |
196 | net.sf.proguard
197 | proguard
198 | 4.4
199 | runtime
200 |
201 |
202 |
203 | 4.4
204 |
205 |
206 |
207 | org.apache.maven.plugins
208 | maven-site-plugin
209 | 3.2
210 |
211 |
212 | org.apache.maven.wagon
213 | wagon-ssh
214 | 2.3
215 |
216 |
217 | org.apache.maven.wagon
218 | wagon-ftp
219 | 1.0-beta-6
220 |
221 |
222 |
223 |
224 |
225 | org.codehaus.mojo
226 | emma-maven-plugin
227 | 1.0-alpha-3
228 |
229 |
230 | org.apache.maven.plugins
231 | maven-checkstyle-plugin
232 | 2.9.1
233 |
234 |
235 | org.apache.maven.plugins
236 | maven-project-info-reports-plugin
237 | 2.6
238 |
239 | true
240 | true
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 | release
253 |
254 |
255 | performRelease
256 | true
257 |
258 |
259 |
260 |
261 |
262 | com.jayway.maven.plugins.android.generation2
263 | android-maven-plugin
264 | ${android-plugin}
265 |
266 | true
267 |
268 |
269 |
270 |
271 | maven-enforcer-plugin
272 |
273 |
274 | enforce-signing-properties
275 |
276 | enforce
277 |
278 |
279 |
280 |
281 | sign.keystore
282 | The 'sign.keystore' property is missing. It must
283 | contain the path to the
284 | keystore used to sign the
285 | application.
286 |
287 |
288 |
289 |
290 | ${sign.keystore}
291 |
292 | The 'sign.keystore' property does not point to a
293 | file. It must contain the
294 | path to the keystore used to sign
295 | the application.
296 |
297 |
298 |
299 | sign.alias
300 | The 'sign.alias' property is missing. It must
301 | contain the key alias used to
302 | sign the application.
303 |
304 |
305 |
306 | sign.storepass
307 | The 'sign.storepass' property is missing. It must
308 | contain the password of
309 | the keystore used to sign the
310 | application.
311 |
312 |
313 |
314 | sign.keypass
315 | The 'sign.keypass' property is missing. It must
316 | contain the password of the
317 | key used to sign the application.
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 | linux
330 |
331 |
332 | unix
333 |
334 |
335 |
336 | ${java.home}/jre/lib/rt.jar
337 | ${java.home}/jre/lib/jsse.jar
338 |
339 |
340 |
342 |
343 | mac
344 |
345 |
346 | mac
347 |
348 |
349 |
350 |
351 |
352 |
353 | ${java.home}/../Classes/classes.jar
354 | ${java.home}/../Classes/jsse.jar
355 |
356 |
357 |
358 | windows
359 |
360 |
361 | windows
362 |
363 |
364 |
365 | ${java.home}/jre/lib/rt.jar
366 | ${java.home}/jre/lib/jsse.jar
367 |
368 |
369 |
370 |
371 | http://starhost.hu/andsecureprefs/
372 | 0.0.2-SNAPSHOT
373 |
374 |
375 |
376 |
--------------------------------------------------------------------------------
/secure-preferences/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /*.iml
3 | /out/
4 |
--------------------------------------------------------------------------------
/secure-preferences/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.github.kovmarci86.android.secure
6 | secure-storage-parent
7 | 0.0.2-SNAPSHOT
8 |
9 | secure-preferences
10 | SecureSharedPreferences
11 |
12 |
13 | com.google.android
14 | android
15 | provided
16 |
17 |
18 | org.slf4j
19 | slf4j-api
20 | 1.7.2
21 |
22 |
23 | junit
24 | junit
25 |
26 |
27 | org.easymock
28 | easymock
29 | 3.1
30 | test
31 |
32 |
33 | org.powermock
34 | powermock-module-junit4
35 | 1.5.1
36 | test
37 |
38 |
39 | org.powermock
40 | powermock-api-easymock
41 | 1.5.1
42 | test
43 |
44 |
45 |
46 |
47 |
48 |
49 | com.github.github
50 | downloads-maven-plugin
51 | 0.5
52 |
53 | ${project.version} release of ${project.name}
54 | true
55 | true
56 | ${repositoryName}
57 | ${repositoryOwner}
58 |
59 |
60 |
61 |
62 | AES Encrypted SharedPreferences for Android applications.
63 | Requires API Level 8 - Froyo
64 |
65 |
--------------------------------------------------------------------------------
/secure-preferences/src/main/java/com/github/kovmarci86/android/secure/preferences/SecureFactory.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences;
2 |
3 | import java.io.UnsupportedEncodingException;
4 | import java.security.NoSuchAlgorithmException;
5 |
6 | import javax.crypto.NoSuchPaddingException;
7 |
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | import android.content.Context;
12 | import android.content.SharedPreferences;
13 |
14 | import com.github.kovmarci86.android.secure.preferences.encryption.EncryptionAlgorithm;
15 | import com.github.kovmarci86.android.secure.preferences.util.SecureUtils;
16 |
17 | import edu.gmu.tec.scout.utilities.Encryption;
18 |
19 | /**
20 | * A factory class to ease the creation of the SecureSharedPreferences instance.
21 | * @author NoTiCe
22 | */
23 | public final class SecureFactory {
24 | private static final String INITIALIZATION_ERROR = "Can not initialize SecureSharedPreferences";
25 | public static final int VERSION_1 = 1;
26 | public static final int LATEST_VERSION = VERSION_1;
27 | private static final Logger LOGGER = LoggerFactory.getLogger(SecureFactory.class);
28 |
29 | /**
30 | * Hidden util constructor.
31 | */
32 | private SecureFactory() {
33 | }
34 |
35 | /**
36 | * Creates the {@link SecureSharedPreferences} instance with a given original and an {@link EncryptionAlgorithm}.
37 | * This function does a version check and the required migrations when the local structure is outdated or not encrypted yet.
38 | * @param original The original {@link SharedPreferences}, which can be also a {@link SecureSharedPreferences} instance.
39 | * @param encryption The {@link EncryptionAlgorithm} to use.
40 | * @return A {@link SecureSharedPreferences} instance.
41 | */
42 | public static SecureSharedPreferences getPreferences(SharedPreferences original, EncryptionAlgorithm encryption) {
43 | SecureSharedPreferences sharedPreferences;
44 | if (original instanceof SecureSharedPreferences) {
45 | sharedPreferences = (SecureSharedPreferences) original;
46 | } else {
47 | sharedPreferences = new SecureSharedPreferences(original, encryption);
48 | }
49 | if (SecureUtils.getVersion(sharedPreferences) < VERSION_1) {
50 | LOGGER.info("Initial migration to Secure storage.");
51 | SecureUtils.migrateData(original, sharedPreferences, VERSION_1);
52 | }
53 | return sharedPreferences;
54 | }
55 |
56 | /**
57 | * Creates the {@link SecureSharedPreferences} instance with a given original and an {@link EncryptionAlgorithm}.
58 | * This function does a version check and the required migrations when the local structure is outdated or not encrypted yet.
59 | * @param original The original {@link SharedPreferences}, which can be also a {@link SecureSharedPreferences} instance.
60 | * @param password The password to use. This will use the {@link Encryption} implementation of the {@link EncryptionAlgorithm}.
61 | * @return A {@link SecureSharedPreferences} instance.
62 | * @throws SecurityException When the {@link EncryptionAlgorithm} can not be initialized.
63 | */
64 | public static SecureSharedPreferences getPreferences(SharedPreferences original, String password) throws SecurityException {
65 | try {
66 | EncryptionAlgorithm encryption = new Encryption(password);
67 | return getPreferences(original, encryption);
68 | } catch (UnsupportedEncodingException e) {
69 | throw new SecurityException(INITIALIZATION_ERROR, e);
70 | } catch (NoSuchAlgorithmException e) {
71 | throw new SecurityException(INITIALIZATION_ERROR, e);
72 | } catch (NoSuchPaddingException e) {
73 | throw new SecurityException(INITIALIZATION_ERROR, e);
74 | }
75 | }
76 |
77 | /**
78 | * Creates a {@link SecureSharedPreferences} instance.
79 | * @param context The current context.
80 | * @param preferencesName The name of the {@link SharedPreferences}.
81 | * @param password The password
82 | * @return The initialized {@link SecureSharedPreferences}.
83 | */
84 | public static SecureSharedPreferences getPreferences(Context context, String preferencesName, String password) {
85 | try {
86 | return getPreferences(context, preferencesName, new Encryption(password));
87 | } catch (UnsupportedEncodingException e) {
88 | throw new SecurityException(INITIALIZATION_ERROR, e);
89 | } catch (NoSuchAlgorithmException e) {
90 | throw new SecurityException(INITIALIZATION_ERROR, e);
91 | } catch (NoSuchPaddingException e) {
92 | throw new SecurityException(INITIALIZATION_ERROR, e);
93 | }
94 | }
95 |
96 | /**
97 | * Creates a {@link SecureSharedPreferences} instance.
98 | * @param context The current context.
99 | * @param preferencesName The name of the {@link SharedPreferences}.
100 | * @param encryption The {@link EncryptionAlgorithm} to use.
101 | * @return The initialized {@link SecureSharedPreferences}.
102 | */
103 | public static SecureSharedPreferences getPreferences(Context context, String preferencesName, EncryptionAlgorithm encryption) {
104 | return getPreferences(context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE), encryption);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/secure-preferences/src/main/java/com/github/kovmarci86/android/secure/preferences/SecureSharedPreferences.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences;
2 |
3 | import java.util.Map;
4 | import java.util.Set;
5 |
6 | import android.annotation.TargetApi;
7 | import android.content.SharedPreferences;
8 | import android.os.Build;
9 |
10 | import com.github.kovmarci86.android.secure.preferences.encryption.EncryptionAlgorithm;
11 | import com.github.kovmarci86.android.secure.preferences.encryption.EncryptionHelper;
12 |
13 | /**
14 | * Decorates SharedPreferences with AES Encryption.
15 | * @author NoTiCe
16 | */
17 | public class SecureSharedPreferences implements SharedPreferences {
18 | private SharedPreferences prefs;
19 | private EncryptionAlgorithm encryption;
20 | private EncryptionHelper helper;
21 |
22 | /**
23 | * Initializes with a single {@link SharedPreferences}
24 | * and the {@link edu.gmu.tec.scout.utilities.Encryption} to use.
25 | *
26 | * @param preferences
27 | * The {@link SharedPreferences} to use.
28 | * @param encryption
29 | * The {@link edu.gmu.tec.scout.utilities.Encryption} to use.
30 | */
31 | public SecureSharedPreferences(SharedPreferences preferences, EncryptionAlgorithm encryption) {
32 | this.prefs = preferences;
33 | this.encryption = encryption;
34 | helper = new EncryptionHelper(encryption);
35 | }
36 |
37 | @Override
38 | public boolean contains(String key) {
39 | return prefs.contains(key);
40 | }
41 |
42 | @Override
43 | public SecuredEditor edit() {
44 | return new SecuredEditor(helper, prefs.edit());
45 | }
46 |
47 | @Override
48 | public Map getAll() {
49 | return prefs.getAll();
50 | }
51 |
52 | @Override
53 | public boolean getBoolean(String key, boolean defValue) {
54 | return helper.readAndDecodeTemplate(prefs, key, defValue);
55 | }
56 |
57 | @Override
58 | public float getFloat(String key, float defValue) {
59 | return helper.readAndDecodeTemplate(prefs, key, defValue);
60 | }
61 |
62 | @Override
63 | public int getInt(String key, int defValue) {
64 | return helper.readAndDecodeTemplate(prefs, key, defValue);
65 | }
66 |
67 | @Override
68 | public long getLong(String key, long defValue) {
69 | return helper.readAndDecodeTemplate(prefs, key, defValue);
70 | }
71 |
72 | @Override
73 | public String getString(String key, String defValue) {
74 | return helper.readAndDecodeTemplate(prefs, key, defValue);
75 | }
76 |
77 | @TargetApi(value = Build.VERSION_CODES.HONEYCOMB)
78 | @Override
79 | public Set getStringSet(String key, Set defValues) {
80 | return helper.readAndDecodeTemplate(prefs, key, defValues);
81 | }
82 |
83 | @Override
84 | public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
85 | prefs.registerOnSharedPreferenceChangeListener(listener);
86 | }
87 |
88 | @Override
89 | public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
90 | prefs.unregisterOnSharedPreferenceChangeListener(listener);
91 | }
92 |
93 | protected EncryptionAlgorithm getEncryption() {
94 | return encryption;
95 | }
96 |
97 | protected SharedPreferences getPrefs() {
98 | return prefs;
99 | }
100 |
101 | protected void setHelper(EncryptionHelper helper) {
102 | this.helper = helper;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/secure-preferences/src/main/java/com/github/kovmarci86/android/secure/preferences/SecuredEditor.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences;
2 |
3 | import java.util.Set;
4 |
5 | import android.content.SharedPreferences;
6 | import android.content.SharedPreferences.Editor;
7 | import android.os.Build;
8 |
9 | import com.github.kovmarci86.android.secure.preferences.encryption.EncryptionHelper;
10 |
11 | /**
12 | * An {@link Editor} decorator using AES encription.
13 | *
14 | * @author NoTiCe
15 | */
16 | public class SecuredEditor implements Editor {
17 | private Editor editor;
18 | private EncryptionHelper helper;
19 |
20 | /**
21 | * Initializes with the {@link EncryptionHelper} an the original
22 | * {@link Editor}.
23 | * @param helper
24 | * The helper to use.
25 | * @param edit
26 | * The editor to use.
27 | */
28 | public SecuredEditor(EncryptionHelper helper, Editor edit) {
29 | this.helper = helper;
30 | this.editor = edit;
31 | }
32 |
33 | @Override
34 | public SecuredEditor putString(String key, String value) {
35 | editor.putString(key, helper.encode(value));
36 | return this;
37 | }
38 |
39 | @Override
40 | public SecuredEditor putStringSet(String key, Set values) {
41 | editor.putString(key, helper.encode(values));
42 | return this;
43 | }
44 |
45 | @Override
46 | public SecuredEditor putInt(String key, int value) {
47 | editor.putString(key, helper.encode(value));
48 | return this;
49 | }
50 |
51 | @Override
52 | public SecuredEditor putLong(String key, long value) {
53 | editor.putString(key, helper.encode(value));
54 | return this;
55 | }
56 |
57 | @Override
58 | public SecuredEditor putFloat(String key, float value) {
59 | editor.putString(key, helper.encode(value));
60 | return this;
61 | }
62 |
63 | @Override
64 | public SecuredEditor putBoolean(String key, boolean value) {
65 | editor.putString(key, helper.encode(value));
66 | return this;
67 | }
68 |
69 | @Override
70 | public SecuredEditor remove(String key) {
71 | editor.remove(key);
72 | return this;
73 | }
74 |
75 | @Override
76 | public SecuredEditor clear() {
77 | editor.clear();
78 | return this;
79 | }
80 |
81 | @Override
82 | public boolean commit() {
83 | return editor.commit();
84 | }
85 |
86 | @Override
87 | public void apply() {
88 | editor.apply();
89 | }
90 |
91 | /**
92 | * Compatibility version of original {@link android.content.SharedPreferences.Editor#apply()}
93 | * method that simply call {@link android.content.SharedPreferences.Editor#commit()} for pre Android Honeycomb (API 11).
94 | * This method is thread safe also on pre API 11.
95 | * Note that when two editors are modifying preferences at the same time, the last one to call apply wins. (Android Doc)
96 | */
97 | public void save() {
98 | compatilitySave(this);
99 | }
100 |
101 | /**
102 | * Saves the {@link SharedPreferences}. See save method.
103 | * @param editor The editor to save/commit.
104 | */
105 | public static void compatilitySave(Editor editor) {
106 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
107 | editor.apply();
108 | } else {
109 | synchronized (SecuredEditor.class) {
110 | editor.commit();
111 | }
112 | }
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/secure-preferences/src/main/java/com/github/kovmarci86/android/secure/preferences/encryption/EncryptionAlgorithm.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences.encryption;
2 |
3 | /**
4 | * Encryption algorithm.
5 | * @author NoTiCe
6 | */
7 | public interface EncryptionAlgorithm {
8 | /**
9 | * Encrypts the given bytes.
10 | * @param bytes
11 | * The bytes to encrypt.
12 | * @return The encrypted bytes.
13 | * @throws EncryptionException When the encryiption fails.
14 | */
15 | byte[] encrypt(byte[] bytes) throws EncryptionException;
16 |
17 | /**
18 | * Decrypts the given bytes.
19 | * @param bytes
20 | * The bytes to decrypt.
21 | * @return The decrypted bytes.
22 | * @throws EncryptionException When the encryiption fails.
23 | */
24 | byte[] decrypt(byte[] bytes) throws EncryptionException;
25 | }
26 |
--------------------------------------------------------------------------------
/secure-preferences/src/main/java/com/github/kovmarci86/android/secure/preferences/encryption/EncryptionException.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences.encryption;
2 |
3 | /**
4 | * A custom exception for ecoding.
5 | * @author NoTiCe
6 | *
7 | */
8 | public class EncryptionException extends Exception {
9 |
10 | /**
11 | * Inherited from {@link Exception}.
12 | */
13 | public EncryptionException() {
14 | super();
15 | }
16 |
17 | /**
18 | * Inherited from {@link Exception}.
19 | * @param message The message.
20 | * @param cause The root cause.
21 | */
22 | public EncryptionException(String message, Throwable cause) {
23 | super(message, cause);
24 | }
25 |
26 | /**
27 | * Inherited from {@link Exception}.
28 | * @param message The message.
29 | */
30 | public EncryptionException(String message) {
31 | super(message);
32 | }
33 |
34 | /**
35 | * Inherited from {@link Exception}.
36 | * @param cause The root cause.
37 | */
38 | public EncryptionException(Throwable cause) {
39 | super(cause);
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/secure-preferences/src/main/java/com/github/kovmarci86/android/secure/preferences/encryption/EncryptionHelper.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences.encryption;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.io.ByteArrayOutputStream;
5 | import java.io.IOException;
6 | import java.io.ObjectInputStream;
7 | import java.io.ObjectOutputStream;
8 |
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 |
12 | import android.content.SharedPreferences;
13 | import android.util.Base64;
14 |
15 | /**
16 | * Encrypting / decrypting support algorithms and type conversions.
17 | * @author NoTiCe
18 | *
19 | */
20 | public class EncryptionHelper {
21 | private static final Logger LOGGER = LoggerFactory.getLogger(EncryptionHelper.class);
22 | private EncryptionAlgorithm encryption;
23 |
24 | /**
25 | * Initializes with ecryption.
26 | * @param encryption The {@link EncryptionAlgorithm} to use.
27 | */
28 | public EncryptionHelper(EncryptionAlgorithm encryption) {
29 | super();
30 | this.encryption = encryption;
31 | }
32 |
33 | /**
34 | * Reads a value from a {@link SharedPreferences}.
35 | * @param The type of the result and the default value.
36 | * @param prefs The preferences to use.
37 | * @param key The key to read.
38 | * @param defValue The default value, when the key does not exist.
39 | * @return Return the T type of result.
40 | */
41 | @SuppressWarnings("unchecked")
42 | public T readAndDecodeTemplate(SharedPreferences prefs, String key, T defValue) {
43 | T result = defValue;
44 | ObjectInputStream ois = readDecoded(prefs, key);
45 | if (ois != null) {
46 | try {
47 | result = (T) ois.readObject();
48 | } catch (IOException e) {
49 | LOGGER.error("Error reading value by key: {}", key, e);
50 | } catch (ClassNotFoundException e) {
51 | LOGGER.error("Error reading value by key: {}", key, e);
52 | }
53 | }
54 | return result;
55 | }
56 |
57 | /**
58 | * Encodes a single value to string.
59 | * May result null on an internal problem.
60 | * @param The type of the value.
61 | * @param value The T type of value to encrypt.
62 | * @return The encrypted value as string.
63 | */
64 | public String encode(T value) {
65 | String result = null;
66 | if (value != null) {
67 | try {
68 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
69 | ObjectOutputStream oos = new ObjectOutputStream(baos);
70 | oos.writeObject(value);
71 | byte[] byteArray = baos.toByteArray();
72 | byte[] encrypt = encryption.encrypt(byteArray);
73 | result = Base64.encodeToString(encrypt, Base64.DEFAULT);
74 | } catch (IOException e) {
75 | LOGGER.error("Error encoding value", e);
76 | } catch (EncryptionException e) {
77 | LOGGER.error("Error encoding value", e);
78 | }
79 | }
80 | return result;
81 | }
82 |
83 | private ObjectInputStream readDecoded(SharedPreferences prefs, String key) {
84 | String stringValue = prefs.getString(key, null);
85 | ObjectInputStream result;
86 | if (stringValue != null) {
87 | try {
88 | result = createDecodedObjectStream(stringValue);
89 | } catch (EncryptionException e) {
90 | LOGGER.error("Error reading from properties. Key: {}", key, e);
91 | result = null;
92 | }
93 | } else {
94 | result = null;
95 | }
96 | return result;
97 | }
98 |
99 | private ObjectInputStream createDecodedObjectStream(String stringValue) throws EncryptionException {
100 | byte[] decodedBytes = Base64.decode(stringValue, Base64.DEFAULT);
101 | byte[] decoded = encryption.decrypt(decodedBytes);
102 | try {
103 | return new ObjectInputStream(new ByteArrayInputStream(decoded));
104 | } catch (IOException e) {
105 | throw new EncryptionException(e);
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/secure-preferences/src/main/java/com/github/kovmarci86/android/secure/preferences/util/SecureUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences.util;
2 |
3 | import java.util.Map;
4 | import java.util.Set;
5 |
6 | import android.content.SharedPreferences;
7 | import android.content.SharedPreferences.Editor;
8 |
9 | import com.github.kovmarci86.android.secure.preferences.SecureFactory;
10 | import com.github.kovmarci86.android.secure.preferences.SecuredEditor;
11 |
12 | /**
13 | * Util classes for {@link SecureFactory}.
14 | * @author NoTiCe
15 | */
16 | public final class SecureUtils {
17 | private static final String VERSION_KEY = "SecurePreferences_version";
18 |
19 | /**
20 | * Hidden util constructor.
21 | */
22 | private SecureUtils() {
23 | }
24 |
25 | /**
26 | * Copies data from one {@link SharedPreferences} to another.
27 | * @param from The source.
28 | * @param to The target.
29 | * @param version The version code to write into the preferences for future check.
30 | */
31 | @SuppressWarnings("unchecked")
32 | public static void migrateData(SharedPreferences from, SharedPreferences to, int version) {
33 | Map all = from.getAll();
34 | Set keySet = all.keySet();
35 | Editor edit = to.edit();
36 | for (String key : keySet) {
37 | Object object = all.get(key);
38 | if (object == null) {
39 | // should not reach here
40 | edit.remove(key);
41 | } else if (object instanceof String) {
42 | edit.putString(key, (String) object);
43 | } else if (object instanceof Integer) {
44 | edit.putInt(key, (Integer) object);
45 | } else if (object instanceof Long) {
46 | edit.putLong(key, (Long) object);
47 | } else if (object instanceof Float) {
48 | edit.putFloat(key, (Float) object);
49 | } else if (object instanceof Boolean) {
50 | edit.putBoolean(key, (Boolean) object);
51 | } else if (object instanceof Set>) {
52 | edit.putStringSet(key, (Set) object);
53 | }
54 | }
55 | edit.putInt(VERSION_KEY, version);
56 | SecuredEditor.compatilitySave(edit);
57 | }
58 |
59 | /**
60 | * Gets the version of {@link SharedPreferences} if any.
61 | * @param preferences
62 | * @return The version or -1.
63 | */
64 | public static int getVersion(SharedPreferences preferences) {
65 | int currentVersion = preferences.getInt(VERSION_KEY, -1);
66 | return currentVersion;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/secure-preferences/src/main/java/edu/gmu/tec/scout/utilities/Encryption.java:
--------------------------------------------------------------------------------
1 | package edu.gmu.tec.scout.utilities;
2 |
3 | import java.io.UnsupportedEncodingException;
4 | import java.security.InvalidKeyException;
5 | import java.security.MessageDigest;
6 | import java.security.NoSuchAlgorithmException;
7 |
8 | import javax.crypto.BadPaddingException;
9 | import javax.crypto.Cipher;
10 | import javax.crypto.IllegalBlockSizeException;
11 | import javax.crypto.NoSuchPaddingException;
12 | import javax.crypto.spec.SecretKeySpec;
13 |
14 | import com.github.kovmarci86.android.secure.preferences.encryption.EncryptionAlgorithm;
15 | import com.github.kovmarci86.android.secure.preferences.encryption.EncryptionException;
16 |
17 | /**
18 | * This class is from the following site:
19 | * http://www.java2s.com/Code/Android/Security/AESEncryption.htm with some
20 | * modification.
21 | * @author http://www.java2s.com/Code/Android/Security/AESEncryption.htm
22 | */
23 | public class Encryption implements EncryptionAlgorithm {
24 | /**
25 | * Author of the base of this implementation.
26 | */
27 | public static final String UTILS_AUTHOR = "http://www.java2s.com/Code/Android/Security/AESEncryption.htm";
28 | private SecretKeySpec skeySpec;
29 | private Cipher cipher;
30 |
31 | /**
32 | * Initializes with raw key.
33 | * @param keyraw The raw key. Must NOT be null.
34 | * @throws UnsupportedEncodingException When no UTF-8 support found.
35 | * @throws NoSuchAlgorithmException When no MD5 support found.
36 | * @throws NoSuchPaddingException When the AES/ECB/PKCS5Padding is not supported.
37 | */
38 | public Encryption(byte[] keyraw) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException {
39 | if (keyraw == null) {
40 | throw new IllegalArgumentException("null key given");
41 | } else {
42 | skeySpec = new SecretKeySpec(keyraw, "AES");
43 | cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
44 | }
45 | }
46 |
47 | /**
48 | * Initializes with a password to use for encrypt.
49 | * @param passphrase The string password.
50 | * @throws UnsupportedEncodingException When no UTF-8 support found.
51 | * @throws NoSuchAlgorithmException When no MD5 support found.
52 | * @throws NoSuchPaddingException When the AES/ECB/PKCS5Padding is not supported.
53 | */
54 | public Encryption(String passphrase) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException {
55 | byte[] bytesOfMessage = passphrase.getBytes("UTF-8");
56 | MessageDigest md = MessageDigest.getInstance("MD5");
57 | byte[] thedigest = md.digest(bytesOfMessage);
58 | skeySpec = new SecretKeySpec(thedigest, "AES");
59 | cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
60 | }
61 |
62 | /**
63 | * Encrypts the bytes.
64 | * @param plaintext The bytes to encrypt.
65 | * @return The encrypted value.
66 | * @throws EncryptionException On encryption exception.
67 | */
68 | @Override
69 | public byte[] encrypt(byte[] plaintext) throws EncryptionException {
70 | // returns byte array encrypted with key
71 | try {
72 | cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
73 | return cipher.doFinal(plaintext);
74 | } catch (InvalidKeyException e) {
75 | throw new EncryptionException(e);
76 | } catch (IllegalBlockSizeException e) {
77 | throw new EncryptionException(e);
78 | } catch (BadPaddingException e) {
79 | throw new EncryptionException(e);
80 | }
81 | }
82 |
83 | /**
84 | * Decrypes the cypher.
85 | * @param ciphertext The data to decrypt.
86 | * @return The decrypted value.
87 | * @throws EncryptionException On ecryption exception.
88 | */
89 | @Override
90 | public byte[] decrypt(byte[] ciphertext) throws EncryptionException {
91 | // returns byte array decrypted with key
92 | try {
93 | cipher.init(Cipher.DECRYPT_MODE, skeySpec);
94 | return cipher.doFinal(ciphertext);
95 | } catch (InvalidKeyException e) {
96 | throw new EncryptionException(e);
97 | } catch (IllegalBlockSizeException e) {
98 | throw new EncryptionException(e);
99 | } catch (BadPaddingException e) {
100 | throw new EncryptionException(e);
101 | }
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/secure-preferences/src/test/java/com/github/kovmarci86/android/secure/preferences/SecureFactoryTest.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences;
2 |
3 | import static org.easymock.EasyMock.anyObject;
4 | import static org.easymock.EasyMock.eq;
5 | import static org.easymock.EasyMock.expect;
6 | import static org.junit.Assert.assertEquals;
7 | import static org.junit.Assert.assertNotSame;
8 | import static org.junit.Assert.assertTrue;
9 |
10 | import org.easymock.EasyMockSupport;
11 | import org.junit.Before;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.powermock.api.easymock.PowerMock;
15 | import org.powermock.core.classloader.annotations.PrepareForTest;
16 | import org.powermock.modules.junit4.PowerMockRunner;
17 |
18 | import android.content.Context;
19 | import android.content.SharedPreferences;
20 |
21 | import com.github.kovmarci86.android.secure.preferences.encryption.EncryptionAlgorithm;
22 | import com.github.kovmarci86.android.secure.preferences.util.SecureUtils;
23 |
24 | import edu.gmu.tec.scout.utilities.Encryption;
25 |
26 | @RunWith(PowerMockRunner.class)
27 | @PrepareForTest({SecureUtils.class, SecureFactory.class})
28 | public class SecureFactoryTest extends EasyMockSupport {
29 |
30 | @Before
31 | public void setUp() throws Exception {
32 | }
33 |
34 | @Test
35 | public void testGetPreferencesWithSecurePreferencesCurrentVersion() {
36 | // GIVEN
37 | int currentVersion = SecureFactory.LATEST_VERSION;
38 | SharedPreferences original = createMock(SecureSharedPreferences.class);
39 | EncryptionAlgorithm encryption = createMock(EncryptionAlgorithm.class);
40 | PowerMock.mockStatic(SecureUtils.class);
41 | // EXPECT
42 | expect(SecureUtils.getVersion(original)).andReturn(currentVersion);
43 | PowerMock.replay(SecureUtils.class);
44 | replayAll();
45 | // WHEN
46 | SecureSharedPreferences preferences = SecureFactory.getPreferences(original, encryption);
47 | // THEN
48 | assertEquals("Must not modify already migrated", original, preferences);
49 | PowerMock.verifyAll();
50 | verifyAll();
51 | }
52 |
53 | @Test
54 | public void testGetPreferencesWithSecurePreferencesZeroVersion() {
55 | // GIVEN
56 | int currentVersion = 0;
57 | SharedPreferences original = createMock(SecureSharedPreferences.class);
58 | EncryptionAlgorithm encryption = createMock(EncryptionAlgorithm.class);
59 | PowerMock.mockStatic(SecureUtils.class);
60 | // EXPECT
61 | expect(SecureUtils.getVersion(original)).andReturn(currentVersion);
62 | SecureUtils.migrateData(original, original, SecureFactory.VERSION_1);
63 | PowerMock.replay(SecureUtils.class);
64 | replayAll();
65 | // WHEN
66 | SecureSharedPreferences preferences = SecureFactory.getPreferences(original, encryption);
67 | // THEN
68 | assertEquals("Must not modify already migrated", original, preferences);
69 | PowerMock.verifyAll();
70 | verifyAll();
71 | }
72 |
73 | @Test
74 | public void testGetPreferencesWithSharedPreferencesZeroVersion() throws Exception {
75 | // GIVEN
76 | int currentVersion = 0;
77 | SharedPreferences original = createMock(SharedPreferences.class);
78 | EncryptionAlgorithm encryption = createMock(EncryptionAlgorithm.class);
79 | PowerMock.mockStatic(SecureUtils.class);
80 | // EXPECT
81 | expect(SecureUtils.getVersion(anyObject(SecureSharedPreferences.class))).andReturn(currentVersion);
82 | SecureUtils.migrateData(eq(original), anyObject(SecureSharedPreferences.class), eq(SecureFactory.VERSION_1));
83 | PowerMock.replay(SecureUtils.class);
84 | replayAll();
85 | // WHEN
86 | SharedPreferences preferences = SecureFactory.getPreferences(original, encryption);
87 | // THEN
88 | assertNotSame("Must migrate at first use", original, preferences);
89 | assertTrue("Must be instanceof SecureSharedPreferences", preferences instanceof SecureSharedPreferences);
90 | PowerMock.verify(SecureUtils.class);
91 | verifyAll();
92 | }
93 |
94 | @Test
95 | public void testGetPreferencesWithSecurePreferencesAndPasswordCurrentVersion() throws Exception {
96 | // GIVEN
97 | String password = "fooPass";
98 | int currentVersion = SecureFactory.LATEST_VERSION;
99 | SharedPreferences original = createMock(SecureSharedPreferences.class);
100 | Encryption encryption = createMock(Encryption.class);
101 | PowerMock.mockStatic(SecureUtils.class);
102 | // EXPECT
103 | PowerMock.expectNew(Encryption.class, password).andReturn(encryption);
104 | expect(SecureUtils.getVersion(original)).andReturn(currentVersion);
105 | PowerMock.replay(SecureUtils.class, Encryption.class);
106 | replayAll();
107 | // WHEN
108 | SecureSharedPreferences preferences = SecureFactory.getPreferences(original, password);
109 | // THEN
110 | assertEquals("Must not modify already migrated", original, preferences);
111 | PowerMock.verifyAll();
112 | verifyAll();
113 | }
114 |
115 | @Test
116 | public void testGetPreferencesWithSecurePreferencesAndPrefNameAndPasswordCurrentVersion() throws Exception {
117 | // GIVEN
118 | Context context = createMock(Context.class);
119 | String prefName = "fooPrefName";
120 | String password = "fooPass";
121 | int currentVersion = SecureFactory.LATEST_VERSION;
122 | SharedPreferences original = createMock(SecureSharedPreferences.class);
123 | Encryption encryption = createMock(Encryption.class);
124 | PowerMock.mockStatic(SecureUtils.class);
125 | // EXPECT
126 | PowerMock.expectNew(Encryption.class, password).andReturn(encryption);
127 | expect(context.getSharedPreferences(prefName, Context.MODE_PRIVATE)).andReturn(original);
128 | expect(SecureUtils.getVersion(original)).andReturn(currentVersion);
129 | PowerMock.replay(SecureUtils.class, Encryption.class);
130 | replayAll();
131 | // WHEN
132 | SecureSharedPreferences preferences = SecureFactory.getPreferences(context, prefName, password);
133 | // THEN
134 | assertEquals("Must not modify already migrated", original, preferences);
135 | PowerMock.verifyAll();
136 | verifyAll();
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/secure-preferences/src/test/java/com/github/kovmarci86/android/secure/preferences/SecureSharedPreferencesTest.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences;
2 |
3 | import static org.easymock.EasyMock.expect;
4 | import static org.junit.Assert.assertEquals;
5 | import static org.junit.Assert.assertNotNull;
6 | import static org.junit.Assert.assertTrue;
7 |
8 | import java.util.Arrays;
9 | import java.util.HashSet;
10 | import java.util.Map;
11 | import java.util.Set;
12 |
13 | import org.easymock.EasyMockSupport;
14 | import org.junit.Before;
15 | import org.junit.Test;
16 |
17 | import android.content.SharedPreferences;
18 | import android.content.SharedPreferences.Editor;
19 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
20 |
21 | import com.github.kovmarci86.android.secure.preferences.encryption.EncryptionHelper;
22 |
23 | import edu.gmu.tec.scout.utilities.Encryption;
24 |
25 | /**
26 | * JUnit test for {@link SecureSharedPreferences}.
27 | * @author NoTiCe
28 | */
29 | public class SecureSharedPreferencesTest extends EasyMockSupport {
30 | private static final float DELTA = 0.1f;
31 | private SharedPreferences preferences = createMock(SecureSharedPreferences.class);
32 | private Encryption encryption = createMock(Encryption.class);
33 | private SecureSharedPreferences sut = new SecureSharedPreferences(preferences, encryption);
34 | private String key = "fooKey";
35 |
36 | @Before
37 | public void setUp() throws Exception {
38 | resetAll();
39 | }
40 |
41 | @Test
42 | public void testSecureSharedPreferences() {
43 | // GIVEN
44 | // EXPECT
45 | replayAll();
46 | // WHEN
47 | // THEN
48 | verifyAll();
49 | assertEquals("Preferences must be the same", preferences, sut.getPrefs());
50 | assertEquals("Encryption must be the same", encryption, sut.getEncryption());
51 | }
52 |
53 | @Test
54 | public void testContains() {
55 | // GIVEN
56 | boolean fooContains = true;
57 | // EXPECT
58 | expect(preferences.contains(key)).andReturn(fooContains);
59 | replayAll();
60 | // WHEN
61 | boolean contains = sut.contains(key);
62 | verifyAll();
63 | assertEquals("Must be true", fooContains, contains);
64 | }
65 |
66 | @Test
67 | public void testEdit() {
68 | // GIVEN
69 | Editor fooEditor = createMock(SecuredEditor.class);
70 | // EXPECT
71 | expect(preferences.edit()).andReturn(fooEditor);
72 | replayAll();
73 | // WHEN
74 | Editor edit = sut.edit();
75 | // THEN
76 | verifyAll();
77 | assertNotNull("Editor must not be null", edit);
78 | assertEquals("Editor must be SecuredEditor.", SecuredEditor.class, edit.getClass());
79 | }
80 |
81 | @SuppressWarnings({"rawtypes", "unchecked"})
82 | @Test
83 | public void testGetAll() {
84 | // GIVEN
85 | Map fooAll = createMock(Map.class);
86 | // EXPECT
87 | expect(preferences.getAll()).andReturn(fooAll);
88 | replayAll();
89 | // WHEN
90 | Map all = sut.getAll();
91 | // THEN
92 | verifyAll();
93 | assertEquals("Wrong map returned", fooAll, all);
94 | }
95 |
96 | @Test
97 | public void testGetBoolean() {
98 | // GIVEN
99 | EncryptionHelper helper = createMock(EncryptionHelper.class);
100 | boolean defValue = false;
101 | boolean fooResult = true;
102 | String fooKey = "fooKey";
103 | // EXPECT
104 | expect(helper.readAndDecodeTemplate(preferences, key, defValue)).andReturn(fooResult);
105 | replayAll();
106 | // WHEN
107 | sut.setHelper(helper);
108 | boolean result = sut.getBoolean(fooKey, defValue);
109 | // THEN
110 | verifyAll();
111 | assertEquals("Wrong result", fooResult, result);
112 | }
113 |
114 | @Test
115 | public void testGetFloat() {
116 | // GIVEN
117 | EncryptionHelper helper = createMock(EncryptionHelper.class);
118 | float defValue = 1.0f;
119 | float fooResult = 2.0f;
120 | String fooKey = "fooKey";
121 | // EXPECT
122 | expect(helper.readAndDecodeTemplate(preferences, key, defValue)).andReturn(fooResult);
123 | replayAll();
124 | // WHEN
125 | sut.setHelper(helper);
126 | float result = sut.getFloat(fooKey, defValue);
127 | // THEN
128 | verifyAll();
129 | assertTrue("Wrong result", Math.abs(fooResult - result) < DELTA);
130 | }
131 |
132 | @Test
133 | public void testGetInt() {
134 | // GIVEN
135 | EncryptionHelper helper = createMock(EncryptionHelper.class);
136 | int defValue = 1;
137 | int fooResult = 2;
138 | String fooKey = "fooKey";
139 | // EXPECT
140 | expect(helper.readAndDecodeTemplate(preferences, key, defValue)).andReturn(fooResult);
141 | replayAll();
142 | // WHEN
143 | sut.setHelper(helper);
144 | int result = sut.getInt(fooKey, defValue);
145 | // THEN
146 | verifyAll();
147 | assertEquals("Wrong result", fooResult, result);
148 | }
149 |
150 | @Test
151 | public void testGetLong() {
152 | // GIVEN
153 | EncryptionHelper helper = createMock(EncryptionHelper.class);
154 | long defValue = 1l;
155 | long fooResult = 2l;
156 | String fooKey = "fooKey";
157 | // EXPECT
158 | expect(helper.readAndDecodeTemplate(preferences, key, defValue)).andReturn(fooResult);
159 | replayAll();
160 | // WHEN
161 | sut.setHelper(helper);
162 | long result = sut.getLong(fooKey, defValue);
163 | // THEN
164 | verifyAll();
165 | assertEquals("Wrong result", fooResult, result);
166 | }
167 |
168 | @Test
169 | public void testGetString() {
170 | // GIVEN
171 | EncryptionHelper helper = createMock(EncryptionHelper.class);
172 | String defValue = "1f";
173 | String fooResult = "2f";
174 | String fooKey = "fooKey";
175 | // EXPECT
176 | expect(helper.readAndDecodeTemplate(preferences, key, defValue)).andReturn(fooResult);
177 | replayAll();
178 | // WHEN
179 | sut.setHelper(helper);
180 | String result = sut.getString(fooKey, defValue);
181 | // THEN
182 | verifyAll();
183 | assertEquals("Wrong result", fooResult, result);
184 | }
185 |
186 | @Test
187 | public void testGetStringSet() {
188 | // GIVEN
189 | EncryptionHelper helper = createMock(EncryptionHelper.class);
190 | Set defValue = new HashSet();
191 | Set fooResult = new HashSet(Arrays.asList(new String[]{"1f"}));
192 | String fooKey = "fooKey";
193 | // EXPECT
194 | expect(helper.readAndDecodeTemplate(preferences, key, defValue)).andReturn(fooResult);
195 | replayAll();
196 | // WHEN
197 | sut.setHelper(helper);
198 | Set result = sut.getStringSet(fooKey, defValue);
199 | // THEN
200 | verifyAll();
201 | assertEquals("Wrong result", fooResult, result);
202 | }
203 |
204 | @Test
205 | public void testRegisterOnSharedPreferenceChangeListener() {
206 | // GIVEN
207 | OnSharedPreferenceChangeListener listener = createMock(OnSharedPreferenceChangeListener.class);
208 | // EXPECT
209 | preferences.registerOnSharedPreferenceChangeListener(listener);
210 | replayAll();
211 | // WHEN
212 | sut.registerOnSharedPreferenceChangeListener(listener);
213 | // THEN
214 | verifyAll();
215 | }
216 |
217 | @Test
218 | public void testUnregisterOnSharedPreferenceChangeListener() {
219 | // GIVEN
220 | OnSharedPreferenceChangeListener listener = createMock(OnSharedPreferenceChangeListener.class);
221 | // EXPECT
222 | preferences.unregisterOnSharedPreferenceChangeListener(listener);
223 | replayAll();
224 | // WHEN
225 | sut.unregisterOnSharedPreferenceChangeListener(listener);
226 | // THEN
227 | verifyAll();
228 | }
229 |
230 | }
231 |
--------------------------------------------------------------------------------
/secure-preferences/src/test/java/com/github/kovmarci86/android/secure/preferences/SecuredEditorTest.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences;
2 |
3 | import static org.easymock.EasyMock.expect;
4 | import static org.junit.Assert.assertEquals;
5 |
6 | import java.util.HashSet;
7 | import java.util.Set;
8 |
9 | import org.easymock.EasyMockSupport;
10 | import org.junit.Before;
11 | import org.junit.Test;
12 |
13 | import android.content.SharedPreferences.Editor;
14 | import android.os.Build;
15 |
16 | import com.github.kovmarci86.android.secure.preferences.encryption.EncryptionHelper;
17 |
18 | /**
19 | * JUnit test for {@link SecuredEditor}.
20 | * @author NoTiCe
21 | */
22 | public class SecuredEditorTest extends EasyMockSupport {
23 | private static final String FOO_KEY = "fooKey";
24 | private static final String FOO_ENCODED_STRING = "encodedString";
25 | private Editor editor;
26 | private EncryptionHelper helper;
27 |
28 | @Before
29 | public void setUp() throws Exception {
30 | editor = createMock(Editor.class);
31 | helper = createMock(EncryptionHelper.class);
32 | }
33 |
34 | @Test
35 | public void testPutString() {
36 | // GIVEN
37 | String fooValue = "fooString";
38 | // EXPECT
39 | expect(helper.encode(fooValue)).andReturn(FOO_ENCODED_STRING);
40 | expect(editor.putString(FOO_KEY, FOO_ENCODED_STRING)).andReturn(editor);
41 | replayAll();
42 | // WHEN
43 | SecuredEditor sut = new SecuredEditor(helper, editor);
44 | Editor result = sut.putString(FOO_KEY, fooValue);
45 | // THEN
46 | verifyAll();
47 | assertEquals("Must return self", sut, result);
48 | }
49 |
50 | @Test
51 | public void testPutStringSet() {
52 | // GIVEN
53 | Set fooValue = new HashSet();
54 | fooValue.add("fooString");
55 | // EXPECT
56 | expect(helper.encode(fooValue)).andReturn(FOO_ENCODED_STRING);
57 | expect(editor.putString(FOO_KEY, FOO_ENCODED_STRING)).andReturn(editor);
58 | replayAll();
59 | // WHEN
60 | SecuredEditor sut = new SecuredEditor(helper, editor);
61 | Editor result = sut.putStringSet(FOO_KEY, fooValue);
62 | // THEN
63 | verifyAll();
64 | assertEquals("Must return self", sut, result);
65 | }
66 |
67 | @Test
68 | public void testPutInt() {
69 | // GIVEN
70 | int fooValue = 2394578;
71 | // EXPECT
72 | expect(helper.encode(fooValue)).andReturn(FOO_ENCODED_STRING);
73 | expect(editor.putString(FOO_KEY, FOO_ENCODED_STRING)).andReturn(editor);
74 | replayAll();
75 | // WHEN
76 | SecuredEditor sut = new SecuredEditor(helper, editor);
77 | Editor result = sut.putInt(FOO_KEY, fooValue);
78 | // THEN
79 | verifyAll();
80 | assertEquals("Must return self", sut, result);
81 | }
82 |
83 | @Test
84 | public void testPutLong() {
85 | // GIVEN
86 | long fooValue = 2394578L;
87 | // EXPECT
88 | expect(helper.encode(fooValue)).andReturn(FOO_ENCODED_STRING);
89 | expect(editor.putString(FOO_KEY, FOO_ENCODED_STRING)).andReturn(editor);
90 | replayAll();
91 | // WHEN
92 | SecuredEditor sut = new SecuredEditor(helper, editor);
93 | Editor result = sut.putLong(FOO_KEY, fooValue);
94 | // THEN
95 | verifyAll();
96 | assertEquals("Must return self", sut, result);
97 | }
98 |
99 | @Test
100 | public void testPutFloat() {
101 | // GIVEN
102 | float fooValue = 2394578f;
103 | // EXPECT
104 | expect(helper.encode(fooValue)).andReturn(FOO_ENCODED_STRING);
105 | expect(editor.putString(FOO_KEY, FOO_ENCODED_STRING)).andReturn(editor);
106 | replayAll();
107 | // WHEN
108 | SecuredEditor sut = new SecuredEditor(helper, editor);
109 | Editor result = sut.putFloat(FOO_KEY, fooValue);
110 | // THEN
111 | verifyAll();
112 | assertEquals("Must return self", sut, result);
113 | }
114 |
115 | @Test
116 | public void testPutBoolean() {
117 | // GIVEN
118 | boolean fooValue = true;
119 | // EXPECT
120 | expect(helper.encode(fooValue)).andReturn(FOO_ENCODED_STRING);
121 | expect(editor.putString(FOO_KEY, FOO_ENCODED_STRING)).andReturn(editor);
122 | replayAll();
123 | // WHEN
124 | SecuredEditor sut = new SecuredEditor(helper, editor);
125 | Editor result = sut.putBoolean(FOO_KEY, fooValue);
126 | // THEN
127 | verifyAll();
128 | assertEquals("Must return self", sut, result);
129 | }
130 |
131 | @Test
132 | public void testRemove() {
133 | // GIVEN
134 | // EXPECT
135 | expect(editor.remove(FOO_KEY)).andReturn(editor);
136 | replayAll();
137 | // WHEN
138 | SecuredEditor sut = new SecuredEditor(helper, editor);
139 | Editor result = sut.remove(FOO_KEY);
140 | // THEN
141 | verifyAll();
142 | assertEquals("Must return self", sut, result);
143 | }
144 |
145 | @Test
146 | public void testClear() {
147 | // GIVEN
148 | // EXPECT
149 | expect(editor.clear()).andReturn(editor);
150 | replayAll();
151 | // WHEN
152 | SecuredEditor sut = new SecuredEditor(helper, editor);
153 | Editor result = sut.clear();
154 | // THEN
155 | verifyAll();
156 | assertEquals("Must return self", sut, result);
157 | }
158 |
159 | @Test
160 | public void testCommit() {
161 | // GIVEN
162 | Boolean fooResult = true;
163 | // EXPECT
164 | expect(editor.commit()).andReturn(fooResult);
165 | replayAll();
166 | // WHEN
167 | SecuredEditor sut = new SecuredEditor(helper, editor);
168 | boolean result = sut.commit();
169 | // THEN
170 | verifyAll();
171 | assertEquals("Must return self", fooResult, result);
172 | }
173 |
174 | @Test
175 | public void testApply() {
176 | // GIVEN
177 | // EXPECT
178 | editor.apply();
179 | replayAll();
180 | // WHEN
181 | SecuredEditor sut = new SecuredEditor(helper, editor);
182 | sut.apply();
183 | // THEN
184 | verifyAll();
185 | }
186 |
187 | @Test
188 | public void testSave() {
189 | // GIVEN
190 | // EXPECT - expectation may vary with build environment
191 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
192 | editor.apply();
193 | } else {
194 | expect(editor.commit()).andReturn(true);
195 | }
196 | replayAll();
197 | // WHEN
198 | SecuredEditor sut = new SecuredEditor(helper, editor);
199 | sut.save();
200 | // THEN
201 | verifyAll();
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/secure-preferences/src/test/java/com/github/kovmarci86/android/secure/preferences/encryption/EncryptionExceptionTest.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences.encryption;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import org.junit.Test;
6 |
7 | /**
8 | * JUnit test for {@link EncryptionException}.
9 | * @author NoTiCe
10 | *
11 | */
12 | public class EncryptionExceptionTest {
13 |
14 | @Test
15 | public void testEncryptionException() {
16 | // +1 green line in the report
17 | new EncryptionException();
18 | }
19 |
20 | @Test
21 | public void testEncryptionExceptionStringThrowable() {
22 | String message = "foo";
23 | Throwable throwable = new Throwable();
24 | EncryptionException encryptionException = new EncryptionException(message, throwable);
25 | assertEquals("Must be the same", message, encryptionException.getMessage());
26 | assertEquals("Must be the same", throwable, encryptionException.getCause());
27 | }
28 |
29 | @Test
30 | public void testEncryptionExceptionString() {
31 | String message = "foo";
32 | EncryptionException encryptionException = new EncryptionException(message);
33 | assertEquals("Must be the same", message, encryptionException.getMessage());
34 |
35 | }
36 |
37 | @Test
38 | public void testEncryptionExceptionThrowable() {
39 | Throwable throwable = new Throwable();
40 | EncryptionException encryptionException = new EncryptionException(throwable);
41 | assertEquals("Must be the same", throwable, encryptionException.getCause());
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/secure-preferences/src/test/java/com/github/kovmarci86/android/secure/preferences/encryption/EncryptionHelperTest.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences.encryption;
2 |
3 | import static org.easymock.EasyMock.expect;
4 | import static org.junit.Assert.assertEquals;
5 |
6 | import java.io.ByteArrayInputStream;
7 | import java.io.ByteArrayOutputStream;
8 | import java.io.IOException;
9 | import java.io.ObjectInputStream;
10 | import java.io.ObjectOutputStream;
11 |
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.powermock.api.easymock.PowerMock;
15 | import org.powermock.core.classloader.annotations.PrepareForTest;
16 | import org.powermock.modules.junit4.PowerMockRunner;
17 |
18 | import android.content.SharedPreferences;
19 | import android.util.Base64;
20 |
21 | /**
22 | * JUnit test for {@link EncryptionHelper} class.
23 | * @author NoTiCe
24 | *
25 | */
26 | @RunWith(PowerMockRunner.class)
27 | @PrepareForTest({EncryptionHelper.class, Base64.class})
28 | public class EncryptionHelperTest extends PowerMock {
29 |
30 | @Test
31 | public void testReadAndDecodeTemplate() throws Exception {
32 | // GIVEN
33 | byte[] mockBytaArray = new byte[]{};
34 | byte[] mockEncryptedBytaArray = new byte[]{};
35 | EncryptionAlgorithm mockEncryption = createMock(EncryptionAlgorithm.class);
36 | ByteArrayInputStream mockBais = createMock(ByteArrayInputStream.class);
37 | ObjectInputStream mockOis = createMock(ObjectInputStream.class);
38 | mockStatic(Base64.class);
39 | Object fooResultString = "fooResultString";
40 | String mockString = "fooString";
41 | SharedPreferences prefs = createMock(SharedPreferences.class);
42 | String key = "fooKey";
43 | String defValue = "defValue";
44 |
45 | // EXPECT
46 | expect(prefs.getString(key, null)).andReturn(mockString);
47 | expect(Base64.decode(mockString, Base64.DEFAULT)).andReturn(mockEncryptedBytaArray);
48 | expect(mockEncryption.decrypt(mockEncryptedBytaArray)).andReturn(mockBytaArray);
49 | expectNew(ByteArrayInputStream.class, mockBytaArray).andReturn(mockBais);
50 | expectNew(ObjectInputStream.class, mockBais).andReturn(mockOis);
51 | expect(mockOis.readObject()).andReturn(fooResultString);
52 | replayAll();
53 | // WHEN
54 | EncryptionHelper helper = new EncryptionHelper(mockEncryption);
55 | String decoded = helper.readAndDecodeTemplate(prefs, key, defValue);
56 |
57 | // THEN
58 | assertEquals("Must equal", fooResultString, decoded);
59 | verifyAll();
60 | }
61 |
62 | @Test
63 | public void testReadAndDecodeTemplateGetDefaultValue() throws Exception {
64 | // GIVEN
65 | EncryptionAlgorithm mockEncryption = createMock(EncryptionAlgorithm.class);
66 | mockStatic(Base64.class);
67 | SharedPreferences prefs = createMock(SharedPreferences.class);
68 | String key = "fooKey";
69 | String defValue = "defValue";
70 |
71 | // EXPECT
72 | expect(prefs.getString(key, null)).andReturn(null);
73 | replayAll();
74 | // WHEN
75 | EncryptionHelper helper = new EncryptionHelper(mockEncryption);
76 | String decoded = helper.readAndDecodeTemplate(prefs, key, defValue);
77 |
78 | // THEN
79 | assertEquals("Must equal", defValue, decoded);
80 | verifyAll();
81 | }
82 |
83 | @Test
84 | public void testReadAndDecodeTemplateGetDefaultValueOnIOException() throws Exception {
85 | // GIVEN
86 | byte[] mockBytaArray = new byte[]{};
87 | byte[] mockEncryptedBytaArray = new byte[]{};
88 | EncryptionAlgorithm mockEncryption = createMock(EncryptionAlgorithm.class);
89 | ByteArrayInputStream mockBais = createMock(ByteArrayInputStream.class);
90 | ObjectInputStream mockOis = createMock(ObjectInputStream.class);
91 | mockStatic(Base64.class);
92 | String mockString = "fooString";
93 | SharedPreferences prefs = createMock(SharedPreferences.class);
94 | String key = "fooKey";
95 | String defValue = "defValue";
96 | IOException ioException = new IOException();
97 |
98 | // EXPECT
99 | expect(prefs.getString(key, null)).andReturn(mockString);
100 | expect(Base64.decode(mockString, Base64.DEFAULT)).andReturn(mockEncryptedBytaArray);
101 | expect(mockEncryption.decrypt(mockEncryptedBytaArray)).andReturn(mockBytaArray);
102 | expectNew(ByteArrayInputStream.class, mockBytaArray).andReturn(mockBais);
103 | expectNew(ObjectInputStream.class, mockBais).andReturn(mockOis);
104 | expect(mockOis.readObject()).andThrow(ioException);
105 | replayAll();
106 | // WHEN
107 | EncryptionHelper helper = new EncryptionHelper(mockEncryption);
108 | String decoded = helper.readAndDecodeTemplate(prefs, key, defValue);
109 |
110 | // THEN
111 | assertEquals("Must equal", defValue, decoded);
112 | verifyAll();
113 | }
114 |
115 | @Test
116 | public void testReadAndDecodeTemplateGetDefaultValueOnClassNotFoundException() throws Exception {
117 | // GIVEN
118 | byte[] mockBytaArray = new byte[]{};
119 | byte[] mockEncryptedBytaArray = new byte[]{};
120 | EncryptionAlgorithm mockEncryption = createMock(EncryptionAlgorithm.class);
121 | ByteArrayInputStream mockBais = createMock(ByteArrayInputStream.class);
122 | ObjectInputStream mockOis = createMock(ObjectInputStream.class);
123 | mockStatic(Base64.class);
124 | String mockString = "fooString";
125 | SharedPreferences prefs = createMock(SharedPreferences.class);
126 | String key = "fooKey";
127 | String defValue = "defValue";
128 | ClassNotFoundException classNotFoundException = new ClassNotFoundException();
129 |
130 | // EXPECT
131 | expect(prefs.getString(key, null)).andReturn(mockString);
132 | expect(Base64.decode(mockString, Base64.DEFAULT)).andReturn(mockEncryptedBytaArray);
133 | expect(mockEncryption.decrypt(mockEncryptedBytaArray)).andReturn(mockBytaArray);
134 | expectNew(ByteArrayInputStream.class, mockBytaArray).andReturn(mockBais);
135 | expectNew(ObjectInputStream.class, mockBais).andReturn(mockOis);
136 | expect(mockOis.readObject()).andThrow(classNotFoundException);
137 | replayAll();
138 | // WHEN
139 | EncryptionHelper helper = new EncryptionHelper(mockEncryption);
140 | String decoded = helper.readAndDecodeTemplate(prefs, key, defValue);
141 |
142 | // THEN
143 | assertEquals("Must equal", defValue, decoded);
144 | verifyAll();
145 | }
146 |
147 | @Test
148 | public void testEncode() throws Exception {
149 | // GIVEN
150 | byte[] mockBytaArray = new byte[]{};
151 | byte[] mockEncryptedBytaArray = new byte[]{};
152 | String mockString = "fooString";
153 | Object value = createMock(Object.class);
154 | EncryptionAlgorithm mockEncryption = createMock(EncryptionAlgorithm.class);
155 | ByteArrayOutputStream mockBaos = createMock(ByteArrayOutputStream.class);
156 | ObjectOutputStream mockOos = createMock(ObjectOutputStream.class);
157 | mockStatic(Base64.class);
158 |
159 | // EXPECT
160 | expectNew(ByteArrayOutputStream.class).andReturn(mockBaos);
161 | expectNew(ObjectOutputStream.class, mockBaos).andReturn(mockOos);
162 | mockOos.writeObject(value);
163 | expect(mockBaos.toByteArray()).andReturn(mockBytaArray);
164 | expect(mockEncryption.encrypt(mockBytaArray)).andReturn(mockEncryptedBytaArray);
165 | expect(Base64.encodeToString(mockEncryptedBytaArray, Base64.DEFAULT)).andReturn(mockString);
166 | replayAll();
167 | // WHEN
168 | EncryptionHelper helper = new EncryptionHelper(mockEncryption);
169 | String encoded = helper.encode(value);
170 |
171 | // THEN
172 | assertEquals("Must equal", mockString, encoded);
173 | verifyAll();
174 | }
175 |
176 | @Test
177 | public void testEncodeWontHarmOnEncryptionException() throws Exception {
178 | // GIVEN
179 | EncryptionException e = new EncryptionException();
180 | byte[] mockBytaArray = new byte[]{};
181 | Object value = createMock(Object.class);
182 | EncryptionAlgorithm mockEncryption = createMock(EncryptionAlgorithm.class);
183 | ByteArrayOutputStream mockBaos = createMock(ByteArrayOutputStream.class);
184 | ObjectOutputStream mockOos = createMock(ObjectOutputStream.class);
185 | mockStatic(Base64.class);
186 |
187 | // EXPECT
188 | expectNew(ByteArrayOutputStream.class).andReturn(mockBaos);
189 | expectNew(ObjectOutputStream.class, mockBaos).andReturn(mockOos);
190 | mockOos.writeObject(value);
191 | expect(mockBaos.toByteArray()).andReturn(mockBytaArray);
192 | expect(mockEncryption.encrypt(mockBytaArray)).andThrow(e);
193 | replayAll();
194 | // WHEN
195 | EncryptionHelper helper = new EncryptionHelper(mockEncryption);
196 | String encoded = helper.encode(value);
197 |
198 | // THEN
199 | assertEquals("Must equal null, was an error inside.", null, encoded);
200 | verifyAll();
201 | }
202 |
203 | @Test
204 | public void testEncodeWontHarmOnIOException() throws Exception {
205 | // GIVEN
206 | IOException e = new IOException();
207 | Object value = createMock(Object.class);
208 | EncryptionAlgorithm mockEncryption = createMock(EncryptionAlgorithm.class);
209 | ByteArrayOutputStream mockBaos = createMock(ByteArrayOutputStream.class);
210 | ObjectOutputStream mockOos = createMock(ObjectOutputStream.class);
211 | mockStatic(Base64.class);
212 |
213 | // EXPECT
214 | expectNew(ByteArrayOutputStream.class).andReturn(mockBaos);
215 | expectNew(ObjectOutputStream.class, mockBaos).andReturn(mockOos);
216 | mockOos.writeObject(value);
217 | expectLastCall().andThrow(e);
218 | replayAll();
219 | // WHEN
220 | EncryptionHelper helper = new EncryptionHelper(mockEncryption);
221 | String encoded = helper.encode(value);
222 |
223 | // THEN
224 | assertEquals("Must equal null, was an error inside.", null, encoded);
225 | verifyAll();
226 | }
227 |
228 | @Test
229 | public void testEncodeNullReturnsNull() throws Exception {
230 | // GIVEN
231 | String mockString = null;
232 | EncryptionAlgorithm mockEncryption = createMock(EncryptionAlgorithm.class);
233 |
234 | // EXPECT
235 | replayAll();
236 | // WHEN
237 | EncryptionHelper helper = new EncryptionHelper(mockEncryption);
238 | String encoded = helper.encode(mockString);
239 |
240 | // THEN
241 | assertEquals("Must equal", mockString, encoded);
242 | verifyAll();
243 | }
244 |
245 | }
246 |
--------------------------------------------------------------------------------
/secure-preferences/src/test/java/com/github/kovmarci86/android/secure/preferences/util/SecureUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences.util;
2 |
3 | import static org.easymock.EasyMock.expect;
4 | import static org.junit.Assert.assertEquals;
5 |
6 | import java.util.Arrays;
7 | import java.util.HashSet;
8 | import java.util.LinkedHashMap;
9 | import java.util.Map;
10 | import java.util.Set;
11 |
12 | import org.easymock.EasyMockSupport;
13 | import org.junit.Before;
14 | import org.junit.Test;
15 | import org.junit.runner.RunWith;
16 | import org.powermock.api.easymock.PowerMock;
17 | import org.powermock.core.classloader.annotations.PrepareForTest;
18 | import org.powermock.modules.junit4.PowerMockRunner;
19 |
20 | import android.content.SharedPreferences;
21 | import android.content.SharedPreferences.Editor;
22 |
23 | import com.github.kovmarci86.android.secure.preferences.SecuredEditor;
24 |
25 | @RunWith(PowerMockRunner.class)
26 | @PrepareForTest({SecuredEditor.class})
27 | public class SecureUtilsTest extends EasyMockSupport {
28 | private static final String STRING_SET_KEY = "stringSet";
29 | private static final Set STRING_SET_VAULE = new HashSet(Arrays.asList(new String[]{"fooValue1", "fooValue2"}));
30 | private static final String BOOLEAN_KEY = "boolean";
31 | private static final boolean BOOLEAN_VALUE = true;
32 | private static final String STRING_KEY = "string";
33 | private static final String STRING_VALUE = "fooString";
34 | private static final String FLOAT_KEY = "float";
35 | private static final float FLOAT_VALUE = 12344.0f;
36 | private static final String INT_KEY = "int";
37 | private static final int INT_VALUE = 34234;
38 | private static final String LONG_KEY = "long";
39 | private static final long LONG_VALUE = 3984;
40 | private static final String VERSION_KEY = "SecurePreferences_version";
41 |
42 | @Before
43 | public void setUp() throws Exception {
44 | }
45 |
46 | @SuppressWarnings("unchecked")
47 | @Test
48 | public void testMigrateData() {
49 | // GIVEN
50 | PowerMock.mockStatic(SecuredEditor.class);
51 | SharedPreferences sourcePreferences = createMock(SharedPreferences.class);
52 | SharedPreferences targetPreference = createMock(SharedPreferences.class);
53 | Editor targetEditor = createMock(Editor.class);
54 | int fooVersion = 8549734;
55 | Map prefs = new LinkedHashMap();
56 | prefs.put(STRING_SET_KEY, STRING_SET_VAULE);
57 | prefs.put(BOOLEAN_KEY, BOOLEAN_VALUE);
58 | prefs.put(STRING_KEY, STRING_VALUE);
59 | prefs.put(FLOAT_KEY, FLOAT_VALUE);
60 | prefs.put(INT_KEY, INT_VALUE);
61 | prefs.put(LONG_KEY, LONG_VALUE);
62 | // EXPECT
63 | expect((Map) sourcePreferences.getAll()).andReturn(prefs);
64 | expect(targetPreference.edit()).andReturn(targetEditor);
65 | expect(targetEditor.putStringSet(STRING_SET_KEY, STRING_SET_VAULE)).andReturn(targetEditor);
66 | expect(targetEditor.putBoolean(BOOLEAN_KEY, BOOLEAN_VALUE)).andReturn(targetEditor);
67 | expect(targetEditor.putString(STRING_KEY, STRING_VALUE)).andReturn(targetEditor);
68 | expect(targetEditor.putFloat(FLOAT_KEY, FLOAT_VALUE)).andReturn(targetEditor);
69 | expect(targetEditor.putInt(INT_KEY, INT_VALUE)).andReturn(targetEditor);
70 | expect(targetEditor.putLong(LONG_KEY, LONG_VALUE)).andReturn(targetEditor);
71 | expect(targetEditor.putInt(VERSION_KEY, fooVersion)).andReturn(targetEditor);
72 | SecuredEditor.compatilitySave(targetEditor);
73 | replayAll();
74 | PowerMock.replay(SecuredEditor.class);
75 | // WHEN
76 | SecureUtils.migrateData(sourcePreferences, targetPreference, fooVersion);
77 | //THEN
78 | verifyAll();
79 | PowerMock.verify(SecuredEditor.class);
80 | }
81 |
82 | @Test
83 | public void testGetVersion() {
84 | // GIVEN
85 | SharedPreferences preferences = createMock(SharedPreferences.class);
86 | int fooVersion = 8549734;
87 | // EXPECT
88 | expect(preferences.getInt(VERSION_KEY, -1)).andReturn(fooVersion);
89 | replayAll();
90 | // WHEN
91 | int version = SecureUtils.getVersion(preferences);
92 | //THEN
93 | verifyAll();
94 | assertEquals("Wrong version result", fooVersion, version);
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/secure-storage-it/.gitignore:
--------------------------------------------------------------------------------
1 | /*.iml
2 |
--------------------------------------------------------------------------------
/secure-storage-it/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/secure-storage-it/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | com.github.kovmarci86.android.secure
7 | secure-storage-parent
8 | 0.0.2-SNAPSHOT
9 |
10 |
11 | secure-storage-it
12 | apk
13 | Integration tests
14 |
15 |
16 |
18 |
19 |
20 |
21 |
22 |
23 | secure-preferences
24 | com.github.kovmarci86.android.secure
25 |
26 |
27 |
28 | com.google.android
29 | android-test
30 | provided
31 |
32 |
33 |
34 | com.github.kovmarci86.android.secure
35 | secure-storage-sample-app
36 | apk
37 | ${zipaligned-classifier}
38 | provided
39 |
40 |
41 | com.github.kovmarci86.android.secure
42 | secure-storage-sample-app
43 | jar
44 | provided
45 |
46 |
47 | junit
48 | junit
49 |
50 |
51 |
52 |
53 |
54 |
55 | com.jayway.maven.plugins.android.generation2
56 | android-maven-plugin
57 | true
58 |
59 |
60 |
61 |
62 |
63 |
64 | release
65 |
66 |
68 | aligned
69 |
70 |
71 |
72 |
73 | maven-jarsigner-plugin
74 |
75 |
76 | sign-application-it-apk
77 | package
78 |
79 | sign
80 |
81 |
82 |
83 |
84 |
85 | com.jayway.maven.plugins.android.generation2
86 | android-maven-plugin
87 |
88 | true
89 |
90 | false
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | Integration tests using sample application
100 |
101 |
--------------------------------------------------------------------------------
/secure-storage-it/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-11
15 |
--------------------------------------------------------------------------------
/secure-storage-it/res/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kovmarci86/android-secure-preferences/017b4a01fd71b552c35f2e3b0981a91524b5fe05/secure-storage-it/res/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/secure-storage-it/res/drawable-ldpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kovmarci86/android-secure-preferences/017b4a01fd71b552c35f2e3b0981a91524b5fe05/secure-storage-it/res/drawable-ldpi/icon.png
--------------------------------------------------------------------------------
/secure-storage-it/res/drawable-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kovmarci86/android-secure-preferences/017b4a01fd71b552c35f2e3b0981a91524b5fe05/secure-storage-it/res/drawable-mdpi/icon.png
--------------------------------------------------------------------------------
/secure-storage-it/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/secure-storage-it/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello secure-storage-it!
4 | secure-storage-it - tests
5 |
6 |
--------------------------------------------------------------------------------
/secure-storage-it/src/main/java/com/github/kovmarci86/android/secure/preferences/SecureFactoryFunctionalTest.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences;
2 |
3 | import java.util.Arrays;
4 | import java.util.HashSet;
5 | import java.util.Set;
6 |
7 | import com.github.kovmarci86.android.secure.preferences.SecureFactory;
8 | import com.github.kovmarci86.android.secure.preferences.SecureSharedPreferences;
9 |
10 | import android.content.Context;
11 | import android.content.SharedPreferences;
12 | import android.content.SharedPreferences.Editor;
13 | import android.test.InstrumentationTestCase;
14 |
15 | /**
16 | * Functional test for {@link SecureFactory}.
17 | * @author NoTiCe
18 | */
19 | public class SecureFactoryFunctionalTest extends InstrumentationTestCase {
20 |
21 | private static final String SHARED_PREFERENCES_NAME = "fooPreferences";
22 | private static final String STRING_SET_KEY = "stringSet";
23 | private static final Set STRING_SET_VAULE = new HashSet(Arrays.asList(new String[]{"fooValue1", "fooValue2"}));
24 | private static final String BOOLEAN_KEY = "boolean";
25 | private static final boolean BOOLEAN_VALUE = true;
26 | private static final String STRING_KEY = "string";
27 | private static final String STRING_VALUE = "fooString";
28 | private static final String FLOAT_KEY = "float";
29 | private static final float FLOAT_VALUE = 12344.0f;
30 | private static final String INT_KEY = "int";
31 | private static final int INT_VALUE = 34234;
32 | private static final String LONG_KEY = "long";
33 | private static final long LONG_VALUE = 3984;
34 | private static final String PASSWORD = "fooPassword";
35 |
36 | private SharedPreferences unencryptedPreferences;
37 |
38 | public void setUp() throws Exception {
39 | super.tearDown();
40 | unencryptedPreferences = getInstrumentation().getContext().getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
41 | Editor edit = unencryptedPreferences.edit();
42 | edit.putStringSet(STRING_SET_KEY, STRING_SET_VAULE);
43 | edit.putBoolean(BOOLEAN_KEY, BOOLEAN_VALUE);
44 | edit.putString(STRING_KEY, STRING_VALUE);
45 | edit.putFloat(FLOAT_KEY, FLOAT_VALUE);
46 | edit.putInt(INT_KEY, INT_VALUE);
47 | edit.putLong(LONG_KEY, LONG_VALUE);
48 | edit.commit();
49 | }
50 |
51 | @Override
52 | protected void tearDown() throws Exception {
53 | super.tearDown();
54 | unencryptedPreferences.edit().clear().commit();
55 | }
56 |
57 | /**
58 | * Tries to read back values after doing the migration from an unencrypted {@link SharedPreferences}.
59 | */
60 | public void testMigrationWorks() {
61 | SharedPreferences preferences = SecureFactory.getPreferences(unencryptedPreferences, PASSWORD);
62 | assertEquals(FLOAT_KEY + " not found.", preferences.getFloat(FLOAT_KEY, 0), FLOAT_VALUE);
63 | assertEquals(BOOLEAN_KEY + " not found.", preferences.getBoolean(BOOLEAN_KEY, false), BOOLEAN_VALUE);
64 | assertEquals(INT_KEY + " not found.", preferences.getInt(INT_KEY, 0), INT_VALUE);
65 | assertEquals(STRING_KEY + " not found.", preferences.getString(STRING_KEY, null), STRING_VALUE);
66 | assertEquals(LONG_KEY + " not found.", preferences.getLong(LONG_KEY, 0), LONG_VALUE);
67 | Arrays.equals(STRING_SET_VAULE.toArray(), preferences.getStringSet(STRING_SET_KEY, new HashSet()).toArray());
68 | }
69 |
70 | /**
71 | * Tries to access the encrypted {@link SecureSharedPreferences} with another {@link SharedPreferences}.
72 | */
73 | public void testMigrationEncrypts() {
74 | SharedPreferences preferences = SecureFactory.getPreferences(unencryptedPreferences, PASSWORD);
75 | try {
76 | preferences.getFloat(FLOAT_KEY, 0);
77 | fail("not encrypted.");
78 | } catch (Throwable e) {
79 | // success
80 | }
81 | }
82 |
83 | /**
84 | * Tries to access an already encrypted {@link SecureSharedPreferences} again.
85 | */
86 | public void testNoDoubleMigration() {
87 | testMigrationWorks();
88 | testMigrationWorks();
89 | testMigrationWorks();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/secure-storage-it/src/main/java/com/github/kovmarci86/android/secure/preferences/SecureSharedPreferencesFunctionalTest.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.preferences;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.content.SharedPreferences.Editor;
6 | import android.test.InstrumentationTestCase;
7 |
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | import com.github.kovmarci86.android.secure.preferences.SecureSharedPreferences;
12 | import com.github.kovmarci86.android.secure.preferences.SecuredEditor;
13 |
14 | import java.util.LinkedHashSet;
15 | import java.util.Set;
16 |
17 | import edu.gmu.tec.scout.utilities.Encryption;
18 |
19 | /**
20 | * Tests the {@link SecureSharedPreferences} in action.
21 | * @author NoTiCe
22 | */
23 | public class SecureSharedPreferencesFunctionalTest extends InstrumentationTestCase {
24 | private static final Logger LOGGER = LoggerFactory.getLogger(SecureSharedPreferencesFunctionalTest.class);
25 | private static final String FOO_PREFERENCES_NAME = "fooPreferencesName";
26 | private static final String FOO_PASSWORD = "fooPassword";
27 | private static final String STRING_VALUE = "testString";
28 | private static final Set STRING_SET_VALUE = new LinkedHashSet();
29 | // test values
30 | private static final String WRONG_VALUE_MESSAGE = "Wrong value";
31 | private static final String STRING_KEY = "string";
32 | private static final String STRING_SET_KEY = "set";
33 | private static final String LONG_KEY = "long";
34 | private static final String INT_KEY = "int";
35 | private static final String FLOAT_KEY = "float";
36 | private static final String BOOLEAN_KEY = "boolean";
37 | private static final long LONG_VALUE = 1L;
38 | private static final int INT_VALUE = 1;
39 | private static final float FLOAT_VALUE = 1.0f;
40 | private static final boolean BOOLEAN_VALUE = true;
41 | private SecureSharedPreferences sut;
42 |
43 | public SecureSharedPreferencesFunctionalTest() {
44 | STRING_SET_VALUE.add("item1");
45 | STRING_SET_VALUE.add("item2");
46 | STRING_SET_VALUE.add("item3");
47 | }
48 |
49 | /**
50 | * Creates the {@link Encryption} and the {@link SecureSharedPreferences}
51 | * instances for testing.
52 | * @throws Exception In unexpected cases.
53 | */
54 | @Override
55 | protected void setUp() throws Exception {
56 | super.setUp();
57 |
58 | SharedPreferences preferences = getInstrumentation().getTargetContext().getSharedPreferences(FOO_PREFERENCES_NAME, Context.MODE_PRIVATE);
59 | Encryption encryption = new Encryption(FOO_PASSWORD);
60 | sut = new SecureSharedPreferences(preferences, encryption);
61 | }
62 |
63 | /**
64 | * Tests if {@link SecureSharedPreferences} can read back it's stored
65 | * values.
66 | */
67 | public void testSecureSharedPreferencesWorks() throws Exception {
68 | // Put values in it
69 | SecuredEditor edit = sut.edit();
70 | edit.putBoolean(BOOLEAN_KEY, BOOLEAN_VALUE);
71 | edit.putFloat(FLOAT_KEY, FLOAT_VALUE);
72 | edit.putInt(INT_KEY, INT_VALUE);
73 | edit.putLong(LONG_KEY, LONG_VALUE);
74 | edit.putString(STRING_KEY, STRING_VALUE);
75 | edit.putStringSet(STRING_SET_KEY, STRING_SET_VALUE);
76 | edit.commit();
77 |
78 | // read back the values with new instance of SharedPreferences
79 | setUp();
80 |
81 | assertEquals(WRONG_VALUE_MESSAGE, BOOLEAN_VALUE, sut.getBoolean(BOOLEAN_KEY, false));
82 | assertEquals(WRONG_VALUE_MESSAGE, FLOAT_VALUE, sut.getFloat(FLOAT_KEY, 0f));
83 | assertEquals(WRONG_VALUE_MESSAGE, INT_VALUE, sut.getInt(INT_KEY, 0));
84 | assertEquals(WRONG_VALUE_MESSAGE, LONG_VALUE, sut.getLong(LONG_KEY, 0L));
85 | assertEquals(WRONG_VALUE_MESSAGE, STRING_VALUE, sut.getString(STRING_KEY, null));
86 | assertEquals(WRONG_VALUE_MESSAGE, STRING_SET_VALUE, sut.getStringSet(STRING_SET_KEY, null));
87 | }
88 |
89 | /**
90 | * Tests if {@link SecureSharedPreferences} can read back it's stored
91 | * values.
92 | */
93 | public void testSecureSharedPreferencesFluentAPIWorks() throws Exception {
94 | // Put values in it
95 | sut.edit().putBoolean(BOOLEAN_KEY, BOOLEAN_VALUE).putFloat(FLOAT_KEY, FLOAT_VALUE).putInt(INT_KEY, INT_VALUE).putLong(LONG_KEY, LONG_VALUE)
96 | .putString(STRING_KEY, STRING_VALUE).putStringSet(STRING_SET_KEY, STRING_SET_VALUE).commit();
97 |
98 | // read back the values with new instance of SharedPreferences
99 | setUp();
100 |
101 | assertEquals(WRONG_VALUE_MESSAGE, BOOLEAN_VALUE, sut.getBoolean(BOOLEAN_KEY, false));
102 | assertEquals(WRONG_VALUE_MESSAGE, FLOAT_VALUE, sut.getFloat(FLOAT_KEY, 0f));
103 | assertEquals(WRONG_VALUE_MESSAGE, INT_VALUE, sut.getInt(INT_KEY, 0));
104 | assertEquals(WRONG_VALUE_MESSAGE, LONG_VALUE, sut.getLong(LONG_KEY, 0L));
105 | assertEquals(WRONG_VALUE_MESSAGE, STRING_VALUE, sut.getString(STRING_KEY, null));
106 | assertEquals(WRONG_VALUE_MESSAGE, STRING_SET_VALUE, sut.getStringSet(STRING_SET_KEY, null));
107 | }
108 |
109 | /**
110 | * Tests if {@link SecureSharedPreferences} can read back it's stored
111 | * values.
112 | */
113 | public void testSecureSharedPreferencesApply() throws Exception {
114 | // Put values in it
115 | sut.edit().putBoolean(BOOLEAN_KEY, BOOLEAN_VALUE).putFloat(FLOAT_KEY, FLOAT_VALUE).putInt(INT_KEY, INT_VALUE).putLong(LONG_KEY, LONG_VALUE)
116 | .putString(STRING_KEY, STRING_VALUE).putStringSet(STRING_SET_KEY, STRING_SET_VALUE).apply();
117 |
118 | // read back the values with new instance of SharedPreferences
119 | assertEquals(WRONG_VALUE_MESSAGE, BOOLEAN_VALUE, sut.getBoolean(BOOLEAN_KEY, false));
120 | assertEquals(WRONG_VALUE_MESSAGE, FLOAT_VALUE, sut.getFloat(FLOAT_KEY, 0f));
121 | assertEquals(WRONG_VALUE_MESSAGE, INT_VALUE, sut.getInt(INT_KEY, 0));
122 | assertEquals(WRONG_VALUE_MESSAGE, LONG_VALUE, sut.getLong(LONG_KEY, 0L));
123 | assertEquals(WRONG_VALUE_MESSAGE, STRING_VALUE, sut.getString(STRING_KEY, null));
124 | assertEquals(WRONG_VALUE_MESSAGE, STRING_SET_VALUE, sut.getStringSet(STRING_SET_KEY, null));
125 | }
126 |
127 | /**
128 | * Writes values to an encoded {@link SecureSharedPreferences}, then tries
129 | * to read it back using a non encoded {@link SharedPreferences}. These
130 | * reads must fail and will trigger a {@link ClassCastException}. String
131 | * reads will return their encoded value.
132 | */
133 | public void testSecureSharedPreferencesIsEncrypted() {
134 | // Put values in it
135 | Editor edit = sut.edit();
136 | edit.putBoolean(BOOLEAN_KEY, BOOLEAN_VALUE);
137 | edit.putFloat(FLOAT_KEY, FLOAT_VALUE);
138 | edit.putInt(INT_KEY, INT_VALUE);
139 | edit.putLong(LONG_KEY, LONG_VALUE);
140 | edit.putString(STRING_KEY, STRING_VALUE);
141 | edit.commit();
142 | // read back the values from simple SharedPreferences instance
143 | SharedPreferences nonSecure = getInstrumentation().getTargetContext().getSharedPreferences(FOO_PREFERENCES_NAME, Context.MODE_PRIVATE);
144 | try {
145 | nonSecure.getBoolean(BOOLEAN_KEY, false);
146 | fail("This command must not work!");
147 | } catch (Throwable e) {
148 | LOGGER.info("Read did not work, this is normal.");
149 | }
150 | try {
151 | nonSecure.getFloat(FLOAT_KEY, 0f);
152 | fail("This command must not work!");
153 | } catch (Throwable e) {
154 | LOGGER.info("Read did not work, this is normal.");
155 | }
156 | try {
157 | nonSecure.getInt(INT_KEY, 0);
158 | fail("This command must not work!");
159 | } catch (Throwable e) {
160 | LOGGER.info("Read did not work, this is normal.");
161 | }
162 | try {
163 | nonSecure.getLong(LONG_KEY, 0L);
164 | fail("This command must not work!");
165 | } catch (Throwable e) {
166 | LOGGER.info("Read did not work, this is normal.");
167 | }
168 | try {
169 | String result = nonSecure.getString(STRING_KEY, null);
170 | assertFalse("The resulting string may not be empty.", STRING_VALUE.equals(result));
171 | } catch (Throwable e) {
172 | LOGGER.info("Read did not work, this is normal.");
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/secure-storage-sample-app/.gitignore:
--------------------------------------------------------------------------------
1 | /*.iml
2 |
--------------------------------------------------------------------------------
/secure-storage-sample-app/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
14 |
18 |
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/secure-storage-sample-app/androlog/androlog.properties:
--------------------------------------------------------------------------------
1 | # Androlog configuration
2 | # this file must be pushed to /sdcard/androlog.properties:
3 | # adb push androlog.properties /sdcard/androlog.properties
4 | # more detail on: https://github.com/akquinet/androlog/wiki/Androlog-configuration
5 | androlog.active = true
6 |
7 | #Configure the default log level:
8 | #androlog.default.level=INFO
9 |
10 | #Configure specific loggers
11 | #com.mkovacs.android.secure.HelloAndroidActivity=INFO
12 |
13 | #Enable the reporting
14 | #More info on: https://github.com/akquinet/androlog/wiki/Androlog-reporting
15 | #androlog.report.active=true
16 | #androlog.report.reporters=de.akquinet.android.androlog.reporter.MailReporter
17 | #androlog.reporter.mail.address=me@my.company.com
18 | #androlog.report.default.level=WARN
19 | #androlog.report.log.items=40
20 | #androlog.report.trigger.level=ERROR
--------------------------------------------------------------------------------
/secure-storage-sample-app/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | com.github.kovmarci86.android.secure
7 | secure-storage-parent
8 | 0.0.2-SNAPSHOT
9 |
10 |
11 | secure-storage-sample-app
12 | apk
13 | Secure storage sample app
14 |
15 |
16 |
17 | com.google.android
18 | android
19 |
20 |
21 | de.akquinet.android.androlog
22 | androlog
23 |
24 |
25 | com.github.kovmarci86.android.secure
26 | secure-preferences
27 |
28 |
29 |
30 |
31 |
32 |
33 | com.jayway.maven.plugins.android.generation2
34 | android-maven-plugin
35 | true
36 |
37 |
38 |
39 |
40 |
41 |
42 | release
43 |
44 |
45 |
46 | com.pyx4me
47 | proguard-maven-plugin
48 |
49 |
50 | process-classes-with-proguard
51 | process-classes
52 |
53 | proguard
54 |
55 |
56 | 4.4
57 | 256m
58 | classes
59 |
60 | ${rt.jar.path}
61 | ${jsse.jar.path}
62 |
63 | true
64 | false
65 | ${project.basedir}/proguard.conf
66 |
67 |
68 |
69 |
70 |
71 | net.sf.proguard
72 | proguard
73 | 4.4
74 | runtime
75 |
76 |
77 |
78 |
79 | maven-jarsigner-plugin
80 |
81 |
82 | sign-application-apk
83 | package
84 |
85 | sign
86 | verify
87 |
88 |
89 |
90 |
91 |
92 | com.jayway.maven.plugins.android.generation2
93 | android-maven-plugin
94 |
95 |
96 | zipalign-application-apk
97 | package
98 |
99 | zipalign
100 |
101 |
102 |
103 |
104 | true
105 |
106 | true
107 | ${project.build.directory}/${project.artifactId}-${project.version}.apk
108 | ${project.build.directory}/${project.artifactId}-${project.version}-signed-aligned.apk
109 |
110 |
111 | false
112 |
113 |
114 |
115 |
116 | org.codehaus.mojo
117 | build-helper-maven-plugin
118 |
119 |
120 |
121 | ${project.build.directory}/proguard_map.txt
122 | map
123 | release
124 |
125 |
126 |
127 |
128 |
129 | attach-signed-aligned
130 | package
131 |
132 | attach-artifact
133 |
134 |
135 |
136 |
137 |
138 | org.apache.maven.plugins
139 | maven-compiler-plugin
140 |
141 | 1.6
142 | 1.6
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 | Sample Android application using the SecureSharedPreferences.
151 |
152 |
--------------------------------------------------------------------------------
/secure-storage-sample-app/proguard.conf:
--------------------------------------------------------------------------------
1 | # Configuration for ProGuard
2 | # From http://proguard.sourceforge.net/index.html#/manual/examples.html#androidapplication
3 |
4 | -dontpreverify
5 | -repackageclasses ''
6 | -optimizations !code/simplification/arithmetic
7 | -keepattributes *Annotation*
8 |
9 | -keep public class * extends android.app.Activity
10 | -keep public class * extends android.app.Application
11 | -keep public class * extends android.app.Service
12 | -keep public class * extends android.content.BroadcastReceiver
13 | -keep public class * extends android.content.ContentProvider
14 |
15 | -keep public class * extends android.view.View {
16 | public (android.content.Context);
17 | public (android.content.Context, android.util.AttributeSet);
18 | public (android.content.Context, android.util.AttributeSet, int);
19 | public void set*(...);
20 | }
21 |
22 | -keepclasseswithmembers class * {
23 | public (android.content.Context, android.util.AttributeSet);
24 | }
25 |
26 | -keepclasseswithmembers class * {
27 | public (android.content.Context, android.util.AttributeSet, int);
28 | }
29 |
30 | -keepclassmembers class * implements android.os.Parcelable {
31 | static android.os.Parcelable$Creator CREATOR;
32 | }
33 |
34 | -keepclassmembers class **.R$* {
35 | public static ;
36 | }
37 |
38 | -keep public interface com.android.vending.licensing.ILicensingService
39 |
40 | -keepclasseswithmembernames class * {
41 | native ;
42 | }
43 |
44 | -keepclassmembers class * extends java.lang.Enum {
45 | public static **[] values();
46 | public static ** valueOf(java.lang.String);
47 | }
48 |
49 | # Removes all calls to Log. Delete the methods you want to keep.
50 | -assumenosideeffects class android.util.Log {
51 | public static int v(...);
52 | public static int d(...);
53 | public static int i(...);
54 | public static int w(...);
55 | public static int e(...);
56 | public static int wtf(...);
57 | }
58 |
--------------------------------------------------------------------------------
/secure-storage-sample-app/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-11
15 |
--------------------------------------------------------------------------------
/secure-storage-sample-app/res/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kovmarci86/android-secure-preferences/017b4a01fd71b552c35f2e3b0981a91524b5fe05/secure-storage-sample-app/res/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/secure-storage-sample-app/res/drawable-ldpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kovmarci86/android-secure-preferences/017b4a01fd71b552c35f2e3b0981a91524b5fe05/secure-storage-sample-app/res/drawable-ldpi/icon.png
--------------------------------------------------------------------------------
/secure-storage-sample-app/res/drawable-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kovmarci86/android-secure-preferences/017b4a01fd71b552c35f2e3b0981a91524b5fe05/secure-storage-sample-app/res/drawable-mdpi/icon.png
--------------------------------------------------------------------------------
/secure-storage-sample-app/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
14 |
15 |
--------------------------------------------------------------------------------
/secure-storage-sample-app/res/layout/activity_secure_login.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
15 |
16 |
21 |
22 |
29 |
30 |
31 |
32 |
33 |
37 |
38 |
42 |
43 |
51 |
52 |
63 |
64 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/secure-storage-sample-app/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Secure Prefs
5 | Hello world!
6 | Settings
7 | Secure Prefs
8 |
9 |
--------------------------------------------------------------------------------
/secure-storage-sample-app/res/values/strings_activity_secure_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Email
5 | Password
6 | Sign in or register
7 | Sign in
8 | Recover lost password
9 | Signing in…
10 | This email address is invalid
11 | This password is too short
12 | This password is incorrect
13 | This field is required
14 |
15 |
--------------------------------------------------------------------------------
/secure-storage-sample-app/src/main/java/com/github/kovmarci86/android/secure/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import android.app.Activity;
7 | import android.content.Intent;
8 | import android.os.Bundle;
9 | import android.view.View;
10 | import android.view.View.OnClickListener;
11 | import android.widget.Button;
12 |
13 | import com.github.kovmarci86.android.secure.service.settings.UserDataServce;
14 | import com.github.kovmarci86.android.secure.R;
15 |
16 | /**
17 | * Main activity for the secure sample app.
18 | * The activities and resources generated with ADT and Eclipse.
19 | * @author NoTiCe
20 | */
21 | public class MainActivity extends Activity {
22 | private static final String INITIALIZATION_ERROR = "Can not initialize secured shared prefs";
23 | private static final Logger LOGGER = LoggerFactory.getLogger(MainActivity.class);
24 | private Button loginButton;
25 | private UserDataServce dataServce;
26 |
27 | @Override
28 | protected void onCreate(Bundle savedInstanceState) {
29 | super.onCreate(savedInstanceState);
30 | try {
31 | dataServce = new UserDataServce(getApplicationContext());
32 | } catch (Exception e) {
33 | finish();
34 | LOGGER.error(INITIALIZATION_ERROR, e);
35 | return;
36 | }
37 | setContentView(R.layout.activity_main);
38 | loginButton = (Button) findViewById(R.id.login_button);
39 | loginButton.setOnClickListener(clickListener);
40 | }
41 |
42 | private void openLoginWindow() {
43 | String username = dataServce.getUsername();
44 | Intent intent = new Intent(getApplicationContext(), SecureLogin.class);
45 | intent.putExtra(SecureLogin.EXTRA_EMAIL, username);
46 | startActivity(intent);
47 | }
48 |
49 | private OnClickListener clickListener = new OnClickListener() {
50 | public void onClick(View v) {
51 | if (loginButton.equals(v)) {
52 | openLoginWindow();
53 | }
54 | }
55 | };
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/secure-storage-sample-app/src/main/java/com/github/kovmarci86/android/secure/SecureLogin.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import android.app.Activity;
7 | import android.os.AsyncTask;
8 | import android.os.Bundle;
9 | import android.text.TextUtils;
10 | import android.view.KeyEvent;
11 | import android.view.View;
12 | import android.view.inputmethod.EditorInfo;
13 | import android.widget.EditText;
14 | import android.widget.TextView;
15 | import android.widget.Toast;
16 |
17 | import com.github.kovmarci86.android.secure.service.settings.UserDataServce;
18 | import com.github.kovmarci86.android.secure.R;
19 |
20 | /**
21 | * Activity which displays a login screen to the user, offering registration as
22 | * well.
23 | */
24 | public class SecureLogin extends Activity {
25 | private static final int MIN_PASSWORD_LENGTH = 4;
26 | private static final int LOGIN_TASK_TIME = 2000;
27 |
28 | private static final Logger LOGGER = LoggerFactory.getLogger(SecureLogin.class);
29 |
30 | /**
31 | * Login result types.
32 | * @author NoTiCe
33 | *
34 | */
35 | public enum LoginResult {
36 | /**
37 | * Successful.
38 | */
39 | SUCCES(1),
40 | /**
41 | * Failed.
42 | */
43 | FAILED(2),
44 | /**
45 | * Just registered.
46 | */
47 | REGISTRED(3),
48 | /**
49 | * Error with login.
50 | */
51 | ERROR(4);
52 |
53 | private int value;
54 |
55 | /**
56 | * Initializes with the enum value.
57 | * @param value The value of the enum.
58 | */
59 | private LoginResult(int value) {
60 | this.value = value;
61 | }
62 |
63 | public int getValue() {
64 | return value;
65 | }
66 | }
67 |
68 | /**
69 | * The default email to populate the email field with.
70 | */
71 | public static final String EXTRA_EMAIL = "com.example.android.authenticatordemo.extra.EMAIL";
72 |
73 | /**
74 | * Keep track of the login task to ensure we can cancel it if requested.
75 | */
76 | private UserLoginTask mAuthTask = null;
77 |
78 | // Values for email and password at the time of the login attempt.
79 | private String mEmail;
80 | private String mPassword;
81 |
82 | // UI references.
83 | private EditText mEmailView;
84 | private EditText mPasswordView;
85 | // private View mLoginFormView;
86 | // private View mLoginStatusView;
87 | private TextView mLoginStatusMessageView;
88 |
89 | // Added code.
90 | private UserDataServce dataServce;
91 |
92 | private Toast toast;
93 |
94 | // Added code end.
95 |
96 | @Override
97 | protected void onCreate(Bundle savedInstanceState) {
98 | super.onCreate(savedInstanceState);
99 | // Added code.
100 | try {
101 | dataServce = new UserDataServce(getApplicationContext());
102 | } catch (Exception e) {
103 | finish();
104 | LOGGER.error("Can not initialize secured shared prefs", e);
105 | return;
106 | }
107 | // Added code end.
108 |
109 | setContentView(R.layout.activity_secure_login);
110 |
111 | // Set up the login form.
112 | mEmail = getIntent().getStringExtra(EXTRA_EMAIL);
113 | mEmailView = (EditText) findViewById(R.id.email);
114 | mEmailView.setText(mEmail);
115 |
116 | mPasswordView = (EditText) findViewById(R.id.password);
117 | mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
118 | public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
119 | if (id == R.id.login || id == EditorInfo.IME_NULL) {
120 | attemptLogin();
121 | return true;
122 | }
123 | return false;
124 | }
125 | });
126 |
127 | // mLoginFormView = findViewById(R.id.login_form);
128 | // mLoginStatusView = findViewById(R.id.login_status);
129 | mLoginStatusMessageView = (TextView) findViewById(R.id.login_status_message);
130 |
131 | findViewById(R.id.sign_in_button).setOnClickListener(new View.OnClickListener() {
132 | public void onClick(View view) {
133 | attemptLogin();
134 | }
135 | });
136 | }
137 |
138 | /**
139 | * Attempts to sign in or register the account specified by the login form.
140 | * If there are form errors (invalid email, missing fields, etc.), the
141 | * errors are presented and no actual login attempt is made.
142 | */
143 | public void attemptLogin() {
144 | if (mAuthTask != null) {
145 | return;
146 | }
147 |
148 | // Reset errors.
149 | mEmailView.setError(null);
150 | mPasswordView.setError(null);
151 |
152 | // Store values at the time of the login attempt.
153 | mEmail = mEmailView.getText().toString();
154 | mPassword = mPasswordView.getText().toString();
155 |
156 | boolean cancel = false;
157 | View focusView = null;
158 |
159 | // Check for a valid password.
160 | if (TextUtils.isEmpty(mPassword)) {
161 | mPasswordView.setError(getString(R.string.error_field_required));
162 | focusView = mPasswordView;
163 | cancel = true;
164 | } else if (mPassword.length() < MIN_PASSWORD_LENGTH) {
165 | mPasswordView.setError(getString(R.string.error_invalid_password));
166 | focusView = mPasswordView;
167 | cancel = true;
168 | }
169 |
170 | // Check for a valid email address.
171 | if (TextUtils.isEmpty(mEmail)) {
172 | mEmailView.setError(getString(R.string.error_field_required));
173 | focusView = mEmailView;
174 | cancel = true;
175 | } else if (!mEmail.contains("@")) {
176 | mEmailView.setError(getString(R.string.error_invalid_email));
177 | focusView = mEmailView;
178 | cancel = true;
179 | }
180 |
181 | if (cancel) {
182 | // There was an error; don't attempt login and focus the first
183 | // form field with an error.
184 | focusView.requestFocus();
185 | } else {
186 | // Show a progress spinner, and kick off a background task to
187 | // perform the user login attempt.
188 | mLoginStatusMessageView.setText(R.string.login_progress_signing_in);
189 | showProgress(true);
190 | mAuthTask = new UserLoginTask();
191 | mAuthTask.execute((Void) null);
192 | }
193 | }
194 |
195 | private void showProgress(boolean b) {
196 | if (toast == null) {
197 | toast = Toast.makeText(getApplicationContext(), "Progress", Toast.LENGTH_SHORT);
198 | }
199 | if (b) {
200 | toast.show();
201 | } else {
202 | toast.cancel();
203 | }
204 | }
205 |
206 | /**
207 | * Represents an asynchronous login/registration task used to authenticate
208 | * the user.
209 | */
210 | public class UserLoginTask extends AsyncTask {
211 |
212 | @Override
213 | protected LoginResult doInBackground(Void... params) {
214 | // simulating an attempt authentication against a network service.
215 | try {
216 | // Simulate network access.
217 | Thread.sleep(LOGIN_TASK_TIME);
218 | } catch (InterruptedException e) {
219 | return LoginResult.ERROR;
220 | }
221 |
222 | String username = dataServce.getUsername();
223 | String password = dataServce.getPassword();
224 |
225 | LoginResult result;
226 | if (username != null && password != null) {
227 | if (username.equals(mEmail) && password.equals(mPassword)) {
228 | result = LoginResult.SUCCES;
229 | } else {
230 | result = LoginResult.FAILED;
231 | }
232 | } else {
233 | dataServce.setUsername(mEmail);
234 | dataServce.setPassword(mPassword);
235 | result = LoginResult.REGISTRED;
236 | }
237 |
238 | return result;
239 | }
240 |
241 | @Override
242 | protected void onPostExecute(final LoginResult success) {
243 | mAuthTask = null;
244 | showProgress(false);
245 |
246 | if (LoginResult.SUCCES.equals(success)) {
247 | finish();
248 | } else if (LoginResult.FAILED.equals(success)) {
249 | mPasswordView.setError(getString(R.string.error_incorrect_password));
250 | mPasswordView.requestFocus();
251 | } else if (LoginResult.REGISTRED.equals(success)) {
252 | Toast.makeText(getApplicationContext(), "Registred", Toast.LENGTH_SHORT).show();
253 | finish();
254 | } else if (LoginResult.ERROR.equals(success)) {
255 | Toast.makeText(getApplicationContext(), "Error", Toast.LENGTH_SHORT).show();
256 | finish();
257 | }
258 | }
259 |
260 | @Override
261 | protected void onCancelled() {
262 | mAuthTask = null;
263 | showProgress(false);
264 | }
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/secure-storage-sample-app/src/main/java/com/github/kovmarci86/android/secure/service/settings/UserDataServce.java:
--------------------------------------------------------------------------------
1 | package com.github.kovmarci86.android.secure.service.settings;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.content.SharedPreferences.Editor;
6 |
7 | import com.github.kovmarci86.android.secure.preferences.SecureFactory;
8 | import com.github.kovmarci86.android.secure.preferences.SecureSharedPreferences;
9 |
10 | /**
11 | * A sample Data Access Object, which is using {@link SecureSharedPreferences}.
12 | * @author NoTiCe
13 | */
14 | public class UserDataServce {
15 | private static final String PREFS_NAME = "secure_user_data.prefs";
16 | private static final String KEY_USERNAME = "username";
17 | private static final String KEY_PASSWORD = "password";
18 | private static final String PASS = "turosretes";
19 |
20 | private SharedPreferences prefs;
21 |
22 | /**
23 | * Initializes the serivice with a {@link Context}.
24 | * @param context The current {@link Context}
25 | * @throws Exception If the initialization fails.
26 | */
27 | public UserDataServce(Context context) throws Exception {
28 | // prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
29 | prefs = SecureFactory.getPreferences(context, PREFS_NAME, PASS);
30 | }
31 |
32 | /**
33 | * Reads back the username.
34 | * @return The username.
35 | */
36 | public String getUsername() {
37 | return prefs.getString(KEY_USERNAME, null);
38 | }
39 |
40 | /**
41 | * Sets the username.
42 | * @param username The username.
43 | */
44 | public void setUsername(String username) {
45 | Editor edit = prefs.edit();
46 | edit.putString(KEY_USERNAME, username);
47 | edit.commit();
48 | }
49 |
50 | /**
51 | * Gets the user's password.
52 | * @return The user's password.
53 | */
54 | public String getPassword() {
55 | return prefs.getString(KEY_PASSWORD, null);
56 | }
57 |
58 | /**
59 | * Sets the user's password.
60 | * @param password The user's password.
61 | */
62 | public void setPassword(String password) {
63 | Editor edit = prefs.edit();
64 | edit.putString(KEY_PASSWORD, password);
65 | edit.commit();
66 | }
67 |
68 | /**
69 | * Removes stored data.
70 | */
71 | public void reset() {
72 | prefs.edit().clear().commit();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/test-key.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kovmarci86/android-secure-preferences/017b4a01fd71b552c35f2e3b0981a91524b5fe05/test-key.keystore
--------------------------------------------------------------------------------