├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── epam │ │ └── securestorage │ │ ├── CipherReadWriteInstrumentedTest.java │ │ └── ThemisReadWriteInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── epam │ │ │ └── securestorage │ │ │ ├── SecureStorage.java │ │ │ ├── core │ │ │ ├── KeyStoreHelper.java │ │ │ ├── SecureStorageCallback.java │ │ │ ├── SecureStorageException.java │ │ │ └── SecurityProvider.java │ │ │ └── providers │ │ │ ├── cipher │ │ │ └── CipherEncryptionProvider.java │ │ │ └── themis │ │ │ └── ThemisEncryptionProvider.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── epam │ └── securestorage │ └── ExampleUnitTest.java ├── build.gradle ├── demo ├── .gitignore ├── build.gradle ├── demoKeystore ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── epam │ │ └── demo │ │ └── MainActivity.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── gradle.properties ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keystore.properties ├── settings.gradle └── themis └── build.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | *.aab 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # IntelliJ 37 | .idea/ 38 | *.iml 39 | .idea/workspace.xml 40 | .idea/tasks.xml 41 | .idea/gradle.xml 42 | .idea/assetWizardSettings.xml 43 | .idea/dictionaries 44 | .idea/libraries 45 | .idea/caches 46 | 47 | # Keystore files 48 | # Uncomment the following lines if you do not want to check your keystore files in. 49 | #*.jks 50 | #*.keystore 51 | 52 | # External native build folder generated in Android Studio 2.2 and later 53 | .externalNativeBuild 54 | 55 | # Google Services (e.g. APIs or Firebase) 56 | # google-services.json 57 | 58 | # Freeline 59 | freeline.py 60 | freeline/ 61 | freeline_project_description.json 62 | 63 | # fastlane 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | fastlane/readme.md 69 | 70 | # Version control 71 | vcs.xml 72 | 73 | # lint 74 | lint/intermediates/ 75 | lint/generated/ 76 | lint/outputs/ 77 | lint/tmp/ 78 | # lint/reports/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Android-Secure-Storage 3 | **SecureStorage** is used to keep private information in a safe mode without requiring a password or a fingerprint. 4 | There are two types of encryption providers: CIPHER and [THEMIS](https://github.com/cossacklabs/themis). Which provide different level of encryption. 5 | *THEMIS is stronger then CIPHER and should be used to keep sensitive data like passwords and etc.* 6 | 7 | ## KOTLIN 8 | 9 | **1. In order to initialize the SecureStorage choose one of the following encryption providers:** 10 | 11 | **CIPHER** encryption: 12 | ```kotlin 13 | val storage = new SecureStorage(context, SecurityProvider.Type.CIPHER) 14 | ``` 15 | 16 | **THEMIS** encryption: 17 | ```kotlin 18 | val storage = new SecureStorage(context, SecurityProvider.Type.THEMIS) 19 | ``` 20 | 21 | **SecureStorage EVENTS**: 22 | To subscribe to SecureStorage Events, please initialize in the following way 23 | 24 | ```kotlin 25 | val storage = SecureStorage(context, SecurityProvider.Type.CIPHER, object : SecureStorageCallback { 26 | override fun onComplete(actionType: SecureStorageCallback.ActionType) { 27 | Log.d("CIPHER_PROVIDER", actionType.toString()) 28 | } 29 | override fun onError(actionType: SecureStorageCallback.ActionType, e: Exception) { 30 | Log.d("CIPHER_PROVIDER_ERROR", actionType.toString(), e) 31 | } 32 | }) 33 | ``` 34 | 35 | **2. Main methods to work with the SecureStorage** 36 | To **SAVE** data: 37 | 38 | ```kotlin 39 | storage.save(key, value) 40 | ``` 41 | To **GET** data: 42 | 43 | ```kotlin 44 | storage.get(key) 45 | ``` 46 | To **REMOVE** specific data: 47 | 48 | ```kotlin 49 | storage.remove(key) 50 | ``` 51 | To **ERASE** all data: 52 | 53 | ```kotlin 54 | storage.erase() 55 | ``` 56 | 57 | ## JAVA 58 | 59 | **1. In order to initialize the SecureStorage choose one of the following encryption providers:** 60 | 61 | **CIPHER** encryption: 62 | ```java 63 | SecureStorage storage = new SecureStorage(context, SecurityProvider.Type.CIPHER); 64 | ``` 65 | 66 | **THEMIS** encryption: 67 | ```java 68 | SecureStorage storage = new SecureStorage(context, SecurityProvider.Type.THEMIS); 69 | ``` 70 | 71 | **SecureStorage EVENTS**: 72 | To subscribe to SecureStorage Events, please initialize in the following way 73 | 74 | ```java 75 | SecureStorage storage = new SecureStorage(this, SecurityProvider.Type.CIPHER, new SecureStorageCallback() { 76 | @Override 77 | public void onComplete(ActionType actionType) { 78 | if(actionType == ActionType.SAVE){ 79 | Log.d("CIPHER_PROVIDER", actionType.toString()); 80 | } 81 | } 82 | 83 | @Override 84 | public void onError(ActionType actionType, Exception e) { 85 | if(actionType == ActionType.SAVE){ 86 | Log.d("CIPHER_PROVIDER_ERROR", actionType.toString(), e); 87 | } 88 | } 89 | }); 90 | ``` 91 | 92 | **2. Main methods to work with the SecureStorage** 93 | To **SAVE** data: 94 | 95 | ```java 96 | storage.save(key, value) 97 | ``` 98 | To **GET** data: 99 | 100 | ```java 101 | storage.get(key) 102 | ``` 103 | To **REMOVE** specific data: 104 | 105 | ```java 106 | storage.remove(key) 107 | ``` 108 | To **ERASE** all data: 109 | 110 | ```java 111 | storage.erase() 112 | ``` 113 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.jfrog.artifactory' 3 | apply plugin: 'maven-publish' 4 | 5 | def keystorePropertiesFile = rootProject.file("keystore.properties") 6 | /* 7 | you should create file keystore.properties and fields like this 8 | username=Andre_Gus@epam.com 9 | password="" 10 | */ 11 | def keystoreProperties = new Properties() 12 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 13 | android { 14 | compileSdkVersion 27 15 | defaultConfig { 16 | minSdkVersion 21 17 | targetSdkVersion 27 18 | versionCode 1 19 | versionName "1.0" 20 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 21 | } 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 26 | } 27 | } 28 | } 29 | publishing { 30 | publications { 31 | aar(MavenPublication) { 32 | def packageName = 'com.epam.securestorage' 33 | def libraryVersion = '1.0.0' 34 | groupId packageName 35 | version = libraryVersion 36 | artifactId project.getName() 37 | 38 | // Tell maven to prepare the generated "*.aar" file for publishing 39 | artifact("$buildDir/outputs/aar/${project.getName()}-release.aar") 40 | } 41 | } 42 | } 43 | 44 | artifactory { 45 | contextUrl = 'https://artifactory.epam.com/artifactory' 46 | publish { 47 | repository { 48 | // The Artifactory repository key to publish to 49 | repoKey = 'libs-release-local' 50 | 51 | username = keystoreProperties['username'] 52 | password = keystoreProperties['password'] 53 | } 54 | defaults { 55 | // Tell the Artifactory Plugin which artifacts should be published to Artifactory. 56 | publications('aar') 57 | publishArtifacts = true 58 | 59 | // Properties to be attached to the published artifacts. 60 | properties = ['qa.level': 'basic', 'dev.team': 'core'] 61 | // Publish generated POM files to Artifactory (true by default) 62 | publishPom = true 63 | } 64 | } 65 | } 66 | 67 | dependencies { 68 | implementation fileTree(include: ['*.jar'], dir: 'libs') 69 | implementation 'com.android.support:support-annotations:28.0.0' 70 | testImplementation 'junit:junit:4.12' 71 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 72 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 73 | 74 | //Themis 75 | implementation 'com.cossacklabs.com:themis:0.10.0' 76 | } 77 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/epam/securestorage/CipherReadWriteInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.epam.securestorage; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import com.epam.securestorage.core.SecurityProvider; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | 13 | import static org.junit.Assert.assertEquals; 14 | import static org.junit.Assert.assertNotEquals; 15 | import static org.junit.Assert.assertNull; 16 | 17 | /** 18 | * Instrumented test, which will execute on an Android device. 19 | * This test need launch in two devices for 18 - 22 and 23-27 version API 20 | * 21 | * @see Testing documentation 22 | */ 23 | @RunWith(AndroidJUnit4.class) 24 | public class CipherReadWriteInstrumentedTest { 25 | private SecureStorage storage; 26 | 27 | @Before 28 | public void before() { 29 | Context context = InstrumentationRegistry.getTargetContext(); 30 | storage = new SecureStorage(context, SecurityProvider.Type.CIPHER); 31 | } 32 | 33 | @Test 34 | public void shouldGetNullValueIfNotSet() { 35 | String value = storage.get("blabla"); 36 | assertNull(value); 37 | } 38 | 39 | @Test 40 | public void shouldSaveValue() { 41 | storage.save("key", "passWORD"); 42 | assertEquals("passWORD", storage.get("key")); 43 | } 44 | 45 | @Test 46 | public void shouldSaveOtherKeyValue() { 47 | storage.save("key1", "passWORD"); 48 | assertEquals("passWORD", storage.get("key1")); 49 | } 50 | 51 | @Test 52 | public void shouldSaveOtherKeyValue2() { 53 | storage.save("key1", "passWORD"); 54 | assertEquals("passWORD", storage.get("key1")); 55 | storage.save("key2", "passWORD"); 56 | assertEquals("passWORD", storage.get("key2")); 57 | assertEquals("passWORD", storage.get("key1")); 58 | storage.get("key1"); 59 | assertEquals("passWORD", storage.get("key2")); 60 | assertEquals("passWORD", storage.get("key1")); 61 | } 62 | 63 | @Test 64 | public void shouldClearStorage() { 65 | storage.save("key12", "1"); 66 | assertEquals("1", storage.get("key12")); 67 | storage.remove("key12"); 68 | assertNull(storage.get("key12")); 69 | } 70 | 71 | @Test 72 | public void shouldEraseValues() { 73 | storage.save("key123", "12093qqwoiejqow812312312123poqj[ 9wpe7nrpwiercwe9rucpn[w9e7rnc;lwiehr pb8ry"); 74 | assertEquals("12093qqwoiejqow812312312123poqj[ 9wpe7nrpwiercwe9rucpn[w9e7rnc;lwiehr pb8ry", storage.get("key123")); 75 | storage.erase(); 76 | assertNotEquals("12093qqwoiejqow812312312123poqj[ 9wpe7nrpwiercwe9rucpn[w9e7rnc;lwiehr pb8ry", storage.get("key123")); 77 | assertNull(storage.get("key123")); 78 | } 79 | 80 | @Test 81 | public void shouldReturnNullIfNoKeyWithWhitespaces() { 82 | assertNull(storage.get("bad key")); 83 | } 84 | 85 | @Test 86 | public void shouldSaveValueForKeyWithWhitespaces() { 87 | storage.save("KEY", "@"); 88 | assertNull(storage.get("bad key")); 89 | } 90 | 91 | @Test 92 | public void shouldClearForKey() { 93 | storage.save("KEY", "@"); 94 | storage.remove("KEY"); 95 | assertNull(storage.get("KEY")); 96 | } 97 | 98 | @Test 99 | public void shouldClearKeys() { 100 | storage.save("KEY", "1"); 101 | storage.save("KEY2", "2"); 102 | storage.remove("KEY"); 103 | assertEquals("2", storage.get("KEY2")); 104 | storage.erase(); 105 | assertNull(storage.get("KEY2")); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/epam/securestorage/ThemisReadWriteInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.epam.securestorage; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import com.epam.securestorage.core.SecurityProvider; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | 13 | import static org.junit.Assert.assertEquals; 14 | import static org.junit.Assert.assertNotEquals; 15 | import static org.junit.Assert.assertNull; 16 | 17 | /** 18 | * Instrumented test, which will execute on an Android device. 19 | * This test need launch in two devices for 18 - 22 and 23-27 version API 20 | * 21 | * @see Testing documentation 22 | */ 23 | @RunWith(AndroidJUnit4.class) 24 | public class ThemisReadWriteInstrumentedTest { 25 | private SecureStorage storage; 26 | 27 | @Before 28 | public void before() { 29 | Context context = InstrumentationRegistry.getTargetContext(); 30 | storage = new SecureStorage(context, SecurityProvider.Type.THEMIS); 31 | } 32 | 33 | @Test 34 | public void shouldGetNullValueIfNotSet() { 35 | String value = storage.get("blabla"); 36 | assertNull(value); 37 | } 38 | 39 | @Test 40 | public void shouldSaveValue() { 41 | storage.save("key", "passWORD"); 42 | assertEquals("passWORD", storage.get("key")); 43 | } 44 | 45 | @Test 46 | public void shouldSaveOtherKeyValue() { 47 | storage.save("key", "value"); 48 | storage.save("key", "value1"); 49 | storage.save("key", "value2"); 50 | assertEquals("value2", storage.get("key")); 51 | } 52 | 53 | @Test 54 | public void shouldSaveOtherKeyValue2() { 55 | storage.save("key1", "passWORD"); 56 | assertEquals("passWORD", storage.get("key1")); 57 | storage.save("key2", "passWORD"); 58 | assertEquals("passWORD", storage.get("key2")); 59 | assertEquals("passWORD", storage.get("key1")); 60 | storage.get("key1"); 61 | assertEquals("passWORD", storage.get("key2")); 62 | assertEquals("passWORD", storage.get("key1")); 63 | } 64 | 65 | @Test 66 | public void shouldClearStorage() { 67 | storage.save("key12", "1"); 68 | assertEquals("1", storage.get("key12")); 69 | storage.remove("key12"); 70 | assertNull(storage.get("key12")); 71 | storage.save("key13", "3456"); 72 | storage.save("key14", "abc"); 73 | storage.remove("key14"); 74 | assertNull(storage.get("key14")); 75 | assertEquals("3456", storage.get("key13")); 76 | } 77 | 78 | @Test 79 | public void shouldEraseValues() { 80 | storage.save("key123", "12093qqwoiejqow812312312123poqj[ 9wpe7nrpwiercwe9rucpn[w9e7rnc;lwiehr pb8ry"); 81 | assertEquals("12093qqwoiejqow812312312123poqj[ 9wpe7nrpwiercwe9rucpn[w9e7rnc;lwiehr pb8ry", storage.get("key123")); 82 | storage.erase(); 83 | assertNotEquals("12093qqwoiejqow812312312123poqj[ 9wpe7nrpwiercwe9rucpn[w9e7rnc;lwiehr pb8ry", storage.get("key123")); 84 | assertNull(storage.get("key123")); 85 | } 86 | 87 | @Test 88 | public void shouldReturnNullIfNoKeyWithWhitespaces() { 89 | assertNull(storage.get("bad key")); 90 | } 91 | 92 | @Test 93 | public void shouldSaveValueForKeyWithWhitespaces() { 94 | storage.save("KEY", "@"); 95 | assertNull(storage.get("bad key")); 96 | } 97 | 98 | @Test 99 | public void shouldClearForKey() { 100 | storage.save("KEY", "@"); 101 | storage.remove("KEY"); 102 | assertNull(storage.get("KEY")); 103 | } 104 | 105 | @Test 106 | public void shouldClearKeys() { 107 | storage.save("KEY", "1"); 108 | storage.save("KEY2", "2"); 109 | storage.save("KEY3", "3"); 110 | storage.save("KEY4", "4"); 111 | storage.remove("KEY"); 112 | assertEquals("2", storage.get("KEY2")); 113 | storage.erase(); 114 | assertNull(storage.get("KEY2")); 115 | assertNull(storage.get("KEY2")); 116 | assertNull(storage.get("KEY3")); 117 | assertNull(storage.get("KEY4")); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/epam/securestorage/SecureStorage.java: -------------------------------------------------------------------------------- 1 | package com.epam.securestorage; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | 6 | import com.epam.securestorage.core.SecureStorageCallback; 7 | import com.epam.securestorage.core.SecurityProvider; 8 | import com.epam.securestorage.core.SecurityProvider.Type; 9 | import com.epam.securestorage.providers.cipher.CipherEncryptionProvider; 10 | import com.epam.securestorage.providers.themis.ThemisEncryptionProvider; 11 | 12 | /** 13 | *

Main encryption manager class

14 | * Description: 15 | * The SecureStorage provides an ability to 16 | * encrypt/decrypt any data based on K, V logic. To instantiate 17 | * the class, Context and SecurityProviderType need to be provided. 18 | * There are two main SecurityProviders: Themis and Cipher. 19 | * 20 | * @author Denys Mokhrin 21 | */ 22 | public class SecureStorage { 23 | 24 | private SecurityProvider securityProvider; 25 | 26 | /** 27 | * Forbids default instance 28 | */ 29 | private SecureStorage() { 30 | } 31 | 32 | /** 33 | * Description: Main method to instantiate SecureStorage 34 | * 35 | * @param context provides app context 36 | * @param securityProviderType constant value, need to be 37 | * chosen from the enum 38 | * SecurityProvider.Type 39 | * @return SecureStore Instance 40 | */ 41 | public SecureStorage(@NonNull Context context, @NonNull Type securityProviderType) { 42 | initProvider(context, securityProviderType, null); 43 | } 44 | 45 | /** 46 | * Description: Main method to instantiate SecureStorage 47 | * with a operation status callback 48 | * 49 | * @param context provides app context 50 | * @param securityProviderType constant value, need to be 51 | * chosen from the enum 52 | * SecurityProvider.Type 53 | * @return SecureStore Instance 54 | */ 55 | public SecureStorage(@NonNull Context context, @NonNull Type securityProviderType, SecureStorageCallback callback) { 56 | initProvider(context, securityProviderType, callback); 57 | } 58 | 59 | private void initProvider(Context context, Type securityProviderType, SecureStorageCallback callback) { 60 | if (context != null && securityProviderType != null) { 61 | switch (securityProviderType) { 62 | case CIPHER: 63 | try { 64 | securityProvider = new CipherEncryptionProvider(context, callback); 65 | } catch (Exception e) { 66 | e.printStackTrace(); 67 | } 68 | break; 69 | case THEMIS: 70 | securityProvider = new ThemisEncryptionProvider(context, callback); 71 | break; 72 | } 73 | } 74 | } 75 | 76 | /** 77 | * Description: Saves data using an encryption algorithm 78 | * 79 | * @param key provides access to store data 80 | * @param value data that need to be encrypted 81 | * @return SecureStore Instance 82 | */ 83 | public void save(String key, String value) { 84 | securityProvider.save(key, value); 85 | } 86 | 87 | /** 88 | * Description: Returns decrypted data 89 | * 90 | * @param key is used to find encrypted data 91 | * @return Decrypted Data in a String format 92 | */ 93 | public String get(@NonNull String key) { 94 | return securityProvider.get(key); 95 | } 96 | 97 | /** 98 | * Description: Returns decrypted data 99 | * 100 | * @param key is used to find stored data for further removal 101 | */ 102 | public void remove(@NonNull String key) { 103 | securityProvider.remove(key); 104 | } 105 | 106 | /** 107 | * Description: Removes all data from storage 108 | */ 109 | public void erase() { 110 | securityProvider.erase(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/com/epam/securestorage/core/KeyStoreHelper.java: -------------------------------------------------------------------------------- 1 | package com.epam.securestorage.core; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.security.KeyPairGeneratorSpec; 6 | import android.security.keystore.KeyGenParameterSpec; 7 | import android.security.keystore.KeyProperties; 8 | import android.support.annotation.RequiresApi; 9 | 10 | import java.io.IOException; 11 | import java.math.BigInteger; 12 | import java.security.InvalidAlgorithmParameterException; 13 | import java.security.KeyPairGenerator; 14 | import java.security.KeyStore; 15 | import java.security.KeyStoreException; 16 | import java.security.NoSuchAlgorithmException; 17 | import java.security.NoSuchProviderException; 18 | import java.security.cert.CertificateException; 19 | import java.util.Calendar; 20 | 21 | import javax.crypto.KeyGenerator; 22 | import javax.crypto.SecretKey; 23 | import javax.security.auth.x500.X500Principal; 24 | 25 | /** 26 | *

Keystore untility class

27 | * Description: 28 | * Provides basic functions to work with 29 | * the KeyStore component 30 | * 31 | * @author Denys Mokhrin 32 | */ 33 | public class KeyStoreHelper { 34 | 35 | public static String ANDROID_KEY_STORE = "AndroidKeyStore"; 36 | public static String KEY_ALIAS = "aliaskeystore"; 37 | 38 | 39 | public static KeyStore getKeyStorePreM(Context context) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, InvalidAlgorithmParameterException { 40 | KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); 41 | keyStore.load(null); 42 | // Generate the RSA key pairs 43 | if (!keyStore.containsAlias(KEY_ALIAS)) { 44 | // Generate a key pair for encryption 45 | Calendar start = Calendar.getInstance(); 46 | Calendar end = Calendar.getInstance(); 47 | end.add(Calendar.YEAR, 1); 48 | KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context) 49 | .setAlias(KEY_ALIAS) 50 | .setSubject(new X500Principal("CN=" + KEY_ALIAS + ", O=Android Authority , C=COMPANY")) 51 | .setSerialNumber(BigInteger.TEN) 52 | .setStartDate(start.getTime()) 53 | .setEndDate(end.getTime()) 54 | .build(); 55 | KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", ANDROID_KEY_STORE); 56 | kpg.initialize(spec); 57 | kpg.generateKeyPair(); 58 | 59 | return keyStore; 60 | } 61 | return keyStore; 62 | } 63 | 64 | @RequiresApi(api = Build.VERSION_CODES.M) 65 | public static SecretKey generatorKey(String alias) throws Exception { 66 | KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec 67 | .Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) 68 | .setBlockModes(KeyProperties.BLOCK_MODE_CBC) 69 | .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) 70 | .build(); 71 | KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE); 72 | keyGenerator.init(keyGenParameterSpec); 73 | return keyGenerator.generateKey(); 74 | } 75 | 76 | @RequiresApi(api = Build.VERSION_CODES.M) 77 | public static SecretKey initSecretKey(String alias) throws Exception { 78 | if (getKeyStoreM().containsAlias(alias)) { 79 | KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) getKeyStoreM().getEntry(alias, null); 80 | return secretKeyEntry.getSecretKey(); 81 | } else { 82 | return generatorKey(alias); 83 | } 84 | } 85 | 86 | @RequiresApi(api = Build.VERSION_CODES.M) 87 | public static KeyStore getKeyStoreM() throws Exception { 88 | KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); 89 | keyStore.load(null); 90 | 91 | return keyStore; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/epam/securestorage/core/SecureStorageCallback.java: -------------------------------------------------------------------------------- 1 | package com.epam.securestorage.core; 2 | 3 | /** 4 | *

Operation status Callback

5 | * Description: 6 | * Informs subscribers about operations status 7 | * 8 | * @author Denys Mokhrin 9 | */ 10 | public interface SecureStorageCallback { 11 | 12 | enum ActionType {SAVE, GET, REMOVE, ERASE} 13 | 14 | void onComplete(ActionType actionType); 15 | 16 | void onError(ActionType actionType, Exception e); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/epam/securestorage/core/SecureStorageException.java: -------------------------------------------------------------------------------- 1 | package com.epam.securestorage.core; 2 | 3 | /** 4 | * Created by Andrei_Gusenkov on 3/13/2018. 5 | */ 6 | 7 | public class SecureStorageException extends Exception { 8 | public SecureStorageException() { 9 | super(); 10 | } 11 | 12 | public SecureStorageException(String message) { 13 | super(message); 14 | } 15 | 16 | public SecureStorageException(String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | 20 | public SecureStorageException(Throwable cause) { 21 | super(cause); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/epam/securestorage/core/SecurityProvider.java: -------------------------------------------------------------------------------- 1 | package com.epam.securestorage.core; 2 | 3 | /** 4 | *

Main encryption manager class

5 | * Description: 6 | * The main interface that generalizes any encryption 7 | * implementations. 8 | * 9 | * @author Denys Mokhrin 10 | */ 11 | public interface SecurityProvider { 12 | 13 | enum Type { 14 | CIPHER, 15 | THEMIS 16 | } 17 | 18 | void save(String key, String value); 19 | 20 | void remove(String key); 21 | 22 | void erase(); 23 | 24 | String get(String key); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/epam/securestorage/providers/cipher/CipherEncryptionProvider.java: -------------------------------------------------------------------------------- 1 | package com.epam.securestorage.providers.cipher; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.os.Build; 6 | import android.preference.PreferenceManager; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.support.annotation.RequiresApi; 10 | import android.util.Base64; 11 | 12 | import com.epam.securestorage.core.KeyStoreHelper; 13 | import com.epam.securestorage.core.SecureStorageCallback; 14 | import com.epam.securestorage.core.SecureStorageException; 15 | import com.epam.securestorage.core.SecurityProvider; 16 | 17 | import java.io.ByteArrayInputStream; 18 | import java.io.ByteArrayOutputStream; 19 | import java.io.IOException; 20 | import java.nio.charset.StandardCharsets; 21 | import java.security.InvalidAlgorithmParameterException; 22 | import java.security.InvalidKeyException; 23 | import java.security.KeyStore; 24 | import java.security.KeyStoreException; 25 | import java.security.NoSuchAlgorithmException; 26 | import java.security.NoSuchProviderException; 27 | import java.security.UnrecoverableEntryException; 28 | import java.security.cert.CertificateException; 29 | import java.util.ArrayList; 30 | import java.util.Arrays; 31 | 32 | import javax.crypto.BadPaddingException; 33 | import javax.crypto.Cipher; 34 | import javax.crypto.CipherInputStream; 35 | import javax.crypto.CipherOutputStream; 36 | import javax.crypto.IllegalBlockSizeException; 37 | import javax.crypto.NoSuchPaddingException; 38 | import javax.crypto.SecretKey; 39 | import javax.crypto.spec.IvParameterSpec; 40 | 41 | import static com.epam.securestorage.core.KeyStoreHelper.KEY_ALIAS; 42 | import static com.epam.securestorage.core.SecureStorageCallback.ActionType.ERASE; 43 | import static com.epam.securestorage.core.SecureStorageCallback.ActionType.GET; 44 | import static com.epam.securestorage.core.SecureStorageCallback.ActionType.REMOVE; 45 | import static com.epam.securestorage.core.SecureStorageCallback.ActionType.SAVE; 46 | 47 | /** 48 | *

Cipher encryption provider class

49 | * Description: 50 | * Encryption provider which logic is based on a Cipher 51 | * Java Cipher implementation. Encapsulates two realizations 52 | * for M and PreM Android OS versions 53 | */ 54 | public class CipherEncryptionProvider implements SecurityProvider { 55 | 56 | private SecurityProvider securityProvider; 57 | private SecureStorageCallback callback; 58 | 59 | public CipherEncryptionProvider(Context context, SecureStorageCallback callback) { 60 | if (callback != null) { 61 | this.callback = callback; 62 | } 63 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { 64 | securityProvider = new CipherM(context); 65 | } else { 66 | securityProvider = new CipherPreM(context); 67 | } 68 | } 69 | 70 | @Override 71 | public void save(String key, String value) { 72 | securityProvider.save(key, value); 73 | } 74 | 75 | @Override 76 | public void remove(String key) { 77 | securityProvider.remove(key); 78 | } 79 | 80 | @Override 81 | public void erase() { 82 | securityProvider.erase(); 83 | } 84 | 85 | @Override 86 | public String get(String key) { 87 | return securityProvider.get(key); 88 | } 89 | 90 | //Uniq key need to be provided to avoid Key collision in case if two providers 91 | //are used at the same app 92 | private String generateKeyWithPrefix(String key) { 93 | key.trim(); 94 | return Type.CIPHER.toString() + key; 95 | } 96 | 97 | class CipherPreM implements SecurityProvider { 98 | private KeyStore keyStore; 99 | private static final String CIPHER_TYPE = "RSA/ECB/PKCS1Padding"; 100 | private static final String CIPHER_PROVIDER = "AndroidOpenSSL"; 101 | 102 | private SharedPreferences preferences; 103 | 104 | CipherPreM(Context context) { 105 | preferences = PreferenceManager.getDefaultSharedPreferences(context); 106 | try { 107 | keyStore = KeyStoreHelper.getKeyStorePreM(context); 108 | } catch (InvalidAlgorithmParameterException | KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | NoSuchProviderException e) { 109 | e.printStackTrace(); 110 | } 111 | } 112 | 113 | @Override 114 | public void save(@NonNull String key, @NonNull String value) { 115 | if (key == null || value == null || key.isEmpty() || value.isEmpty()) { 116 | if (callback != null) { 117 | callback.onError(SAVE, new SecureStorageException("Key or Value can't be NULL or empty")); 118 | } 119 | return; 120 | } 121 | 122 | try { 123 | key = generateKeyWithPrefix(key); 124 | 125 | KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null); 126 | // Encrypt the text 127 | Cipher inputCipher = Cipher.getInstance(CIPHER_TYPE, CIPHER_PROVIDER); 128 | inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.getCertificate().getPublicKey()); 129 | 130 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 131 | CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, inputCipher); 132 | cipherOutputStream.write(value.getBytes(StandardCharsets.UTF_8)); 133 | cipherOutputStream.close(); 134 | 135 | byte[] cryptoText = outputStream.toByteArray(); 136 | String encryptedString = (Base64.encodeToString(cryptoText, Base64.DEFAULT)); 137 | putPref(key, encryptedString); 138 | outputStream.close(); 139 | 140 | if (callback != null) { 141 | callback.onComplete(SAVE); 142 | } 143 | } catch (NoSuchAlgorithmException | KeyStoreException | InvalidKeyException | IOException | NoSuchPaddingException | UnrecoverableEntryException | NoSuchProviderException e) { 144 | e.printStackTrace(); 145 | 146 | if (callback != null) { 147 | callback.onError(SAVE, e); 148 | } 149 | } 150 | } 151 | 152 | private void putPref(String key, String value) { 153 | preferences.edit().putString(key, value).apply(); 154 | } 155 | 156 | @Override 157 | public void remove(@NonNull String key) { 158 | if (key == null || key.isEmpty()) { 159 | if (callback != null) { 160 | callback.onError(REMOVE, new SecureStorageException("Key can't be NULL or empty")); 161 | } 162 | return; 163 | } 164 | 165 | key = generateKeyWithPrefix(key); 166 | preferences.edit().remove(key).apply(); 167 | if (callback != null) { 168 | callback.onComplete(REMOVE); 169 | } 170 | } 171 | 172 | @Override 173 | public void erase() { 174 | try { 175 | keyStore.deleteEntry(KEY_ALIAS); 176 | if (callback != null) { 177 | callback.onComplete(ERASE); 178 | } 179 | } catch (KeyStoreException e) { 180 | e.printStackTrace(); 181 | if (callback != null) { 182 | callback.onError(ERASE, e); 183 | } 184 | } 185 | } 186 | 187 | private String getPref(String key) { 188 | return preferences.getString(key, ""); 189 | } 190 | 191 | @Nullable 192 | @Override 193 | public String get(@NonNull String key) { 194 | if (key == null || key.isEmpty()) { 195 | if (callback != null) { 196 | callback.onError(GET, new SecureStorageException("Key or Value can't be NULL ot empty")); 197 | } 198 | return null; 199 | } 200 | 201 | key = generateKeyWithPrefix(key); 202 | KeyStore.PrivateKeyEntry privateKeyEntry; 203 | try { 204 | privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null); 205 | 206 | if (privateKeyEntry == null) return null; 207 | Cipher cipher = Cipher.getInstance(CIPHER_TYPE, CIPHER_PROVIDER); 208 | cipher.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey()); 209 | 210 | String value = getPref(key); 211 | if (value.isEmpty()) return null; 212 | byte[] bytes = getBytes(cipher, value); 213 | String result = new String(bytes, StandardCharsets.UTF_8); 214 | 215 | if (callback != null) { 216 | callback.onComplete(GET); 217 | } 218 | 219 | return result; 220 | } catch (NoSuchAlgorithmException | KeyStoreException | InvalidKeyException | IOException | NoSuchPaddingException | UnrecoverableEntryException | NoSuchProviderException e) { 221 | e.printStackTrace(); 222 | if (callback != null) { 223 | callback.onError(GET, e); 224 | } 225 | return null; 226 | } 227 | } 228 | 229 | private byte[] getBytes(Cipher cipher, String value) throws IOException { 230 | CipherInputStream cipherInputStream = new CipherInputStream( 231 | new ByteArrayInputStream((Base64.decode(value, Base64.DEFAULT))), cipher); 232 | ArrayList values = new ArrayList<>(); 233 | int nextByte; 234 | while ((nextByte = cipherInputStream.read()) != -1) { 235 | values.add((byte) nextByte); 236 | } 237 | 238 | byte[] bytes = new byte[values.size()]; 239 | for (int i = 0; i < bytes.length; i++) { 240 | bytes[i] = values.get(i); 241 | } 242 | return bytes; 243 | } 244 | } 245 | 246 | class CipherM implements SecurityProvider { 247 | 248 | private static final java.lang.String AESGCMNOPADDING = "AES/CBC/PKCS7Padding"; 249 | private static final String I_VECTOR = "valueV"; 250 | private SecretKey secretKey; 251 | private Cipher cipher; 252 | private SharedPreferences preferences; 253 | private KeyStore keyStore; 254 | 255 | @RequiresApi(api = Build.VERSION_CODES.M) 256 | CipherM(Context context) { 257 | try { 258 | cipher = Cipher.getInstance(AESGCMNOPADDING); 259 | keyStore = KeyStoreHelper.getKeyStoreM(); 260 | secretKey = KeyStoreHelper.initSecretKey(KEY_ALIAS); 261 | preferences = PreferenceManager.getDefaultSharedPreferences(context); 262 | } catch (Exception e) { 263 | e.printStackTrace(); 264 | } 265 | } 266 | 267 | @Override 268 | public void erase() { 269 | try { 270 | keyStore.deleteEntry(KEY_ALIAS); 271 | if (callback != null) { 272 | callback.onComplete(ERASE); 273 | } 274 | } catch (KeyStoreException e) { 275 | e.printStackTrace(); 276 | if (callback != null) { 277 | callback.onError(ERASE, e); 278 | } 279 | } 280 | } 281 | 282 | @Override 283 | public void save(@NonNull String key, @NonNull String value) { 284 | if (key == null || value == null || key.isEmpty() || value.isEmpty()) { 285 | if (callback != null) { 286 | callback.onError(SAVE, new SecureStorageException("Key or Value can't be NULL")); 287 | } 288 | return; 289 | } 290 | 291 | key = generateKeyWithPrefix(key); 292 | 293 | try { 294 | cipher.init(Cipher.ENCRYPT_MODE, secretKey); 295 | putPref(I_VECTOR + key, Arrays.toString(cipher.getIV())); 296 | byte[] encryption = cipher.doFinal(value.getBytes(StandardCharsets.UTF_8)); 297 | String encryptedBase64Encoded = Base64.encodeToString(encryption, Base64.DEFAULT); 298 | putPref(key, encryptedBase64Encoded); 299 | 300 | if (callback != null) { 301 | callback.onComplete(SAVE); 302 | } 303 | } catch (InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) { 304 | e.printStackTrace(); 305 | if (callback != null) { 306 | callback.onError(SAVE, e); 307 | } 308 | } 309 | } 310 | 311 | @Override 312 | public void remove(@NonNull String key) { 313 | if (key == null || key.isEmpty()) { 314 | if (callback != null) { 315 | callback.onError(REMOVE, new SecureStorageException("Key can't be NULL or empty")); 316 | } 317 | return; 318 | } 319 | 320 | key = generateKeyWithPrefix(key); 321 | preferences.edit().remove(key).apply(); 322 | if (callback != null) { 323 | callback.onComplete(REMOVE); 324 | } 325 | } 326 | 327 | @Nullable 328 | @RequiresApi(api = Build.VERSION_CODES.M) 329 | @Override 330 | public String get(@NonNull String key) { 331 | if (key == null || key.isEmpty()) { 332 | if (callback != null) { 333 | callback.onError(GET, new SecureStorageException("Key can't be NULL or empty")); 334 | } 335 | return null; 336 | } 337 | 338 | key = generateKeyWithPrefix(key); 339 | if (!isValueSet(I_VECTOR + key) || !isValueSet(key)) { 340 | return null; 341 | } 342 | 343 | try { 344 | String value = getPref(key); 345 | byte[] iv = getByteArray(getPref(I_VECTOR + key)); 346 | IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); 347 | KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(KEY_ALIAS, null); 348 | if (secretKeyEntry == null) return null; 349 | cipher.init(Cipher.DECRYPT_MODE, secretKeyEntry.getSecretKey(), ivParameterSpec); 350 | if (value.isEmpty()) return null; 351 | String result = new String(cipher.doFinal(Base64.decode(value, Base64.DEFAULT)), StandardCharsets.UTF_8); 352 | 353 | if (callback != null) { 354 | callback.onComplete(GET); 355 | } 356 | 357 | return result; 358 | } catch (InvalidKeyException | BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | UnrecoverableEntryException | KeyStoreException e) { 359 | e.printStackTrace(); 360 | 361 | if (callback != null) { 362 | callback.onError(GET, e); 363 | } 364 | return null; 365 | } 366 | } 367 | 368 | @Nullable 369 | private byte[] getByteArray(String stringArray) { 370 | if (stringArray != null) { 371 | String[] split = stringArray.substring(1, stringArray.length() - 1).split(", "); 372 | byte[] array = new byte[split.length]; 373 | for (int i = 0; i < split.length; i++) { 374 | array[i] = Byte.parseByte(split[i]); 375 | } 376 | return array; 377 | } else 378 | return null; 379 | } 380 | 381 | private boolean isValueSet(String key) { 382 | return preferences.contains(key); 383 | } 384 | 385 | private String getPref(String key) { 386 | return preferences.getString(key, null); 387 | } 388 | 389 | private void putPref(String key, String value) { 390 | preferences.edit().putString(key, value).apply(); 391 | } 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /app/src/main/java/com/epam/securestorage/providers/themis/ThemisEncryptionProvider.java: -------------------------------------------------------------------------------- 1 | package com.epam.securestorage.providers.themis; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | import android.util.Base64; 9 | 10 | import com.cossacklabs.themis.InvalidArgumentException; 11 | import com.cossacklabs.themis.NullArgumentException; 12 | import com.cossacklabs.themis.SecureCell; 13 | import com.cossacklabs.themis.SecureCellData; 14 | import com.cossacklabs.themis.SecureCellException; 15 | import com.epam.securestorage.core.SecureStorageCallback; 16 | import com.epam.securestorage.core.SecureStorageException; 17 | import com.epam.securestorage.core.SecurityProvider; 18 | 19 | import java.nio.charset.StandardCharsets; 20 | 21 | import static com.cossacklabs.themis.SecureCell.MODE_SEAL; 22 | import static com.epam.securestorage.core.SecureStorageCallback.ActionType.ERASE; 23 | import static com.epam.securestorage.core.SecureStorageCallback.ActionType.GET; 24 | import static com.epam.securestorage.core.SecureStorageCallback.ActionType.REMOVE; 25 | import static com.epam.securestorage.core.SecureStorageCallback.ActionType.SAVE; 26 | 27 | /** 28 | *

Themis encryption provider class

29 | * Description: 30 | * Encryption provider which logic is based on the Themis library 31 | * designed by CossackLabs 32 | * See GitHub CossackLabs 33 | * Java Cipher implementation. Encapsulates two realizations 34 | * for M and PreM Android OS versions 35 | * 36 | * @author Denys Mokhrin 37 | */ 38 | public class ThemisEncryptionProvider implements SecurityProvider { 39 | 40 | private SharedPreferences preferences; 41 | private SecureStorageCallback callback; 42 | 43 | public ThemisEncryptionProvider(@NonNull Context context) { 44 | this.preferences = PreferenceManager.getDefaultSharedPreferences(context); 45 | } 46 | 47 | public ThemisEncryptionProvider(@NonNull Context context, SecureStorageCallback callback) { 48 | this.callback = callback; 49 | this.preferences = PreferenceManager.getDefaultSharedPreferences(context); 50 | } 51 | 52 | @Override 53 | public void save(@NonNull String key, @NonNull String value) { 54 | if (key == null || value == null || key.isEmpty() || value.isEmpty()) { 55 | if (callback != null) { 56 | callback.onError(SAVE, new SecureStorageException("Key or Value can't be NULL")); 57 | } 58 | return; 59 | } 60 | if (key != null && value != null) { 61 | key = generateKeyWithPrefix(key); 62 | 63 | try { 64 | SecureCell sc = new SecureCell(key.getBytes(StandardCharsets.UTF_8), MODE_SEAL); 65 | SecureCellData protectedData = sc.protect(key.getBytes(StandardCharsets.UTF_8), value.getBytes(StandardCharsets.UTF_8)); 66 | String encodedString = Base64.encodeToString(protectedData.getProtectedData(), Base64.NO_WRAP); 67 | 68 | this.preferences.edit().putString(key, encodedString).apply(); 69 | 70 | if (callback != null) { 71 | callback.onComplete(SAVE); 72 | } 73 | } catch (InvalidArgumentException | NullArgumentException | SecureCellException e) { 74 | e.printStackTrace(); 75 | 76 | if (callback != null) { 77 | callback.onError(SAVE, e); 78 | } 79 | } 80 | } 81 | } 82 | 83 | @Nullable 84 | @Override 85 | public String get(@NonNull String key) { 86 | if (key == null || key.isEmpty()) { 87 | if (callback != null) { 88 | callback.onError(GET, new SecureStorageException("Key or Value can't be NULL")); 89 | } 90 | return null; 91 | } 92 | try { 93 | key = generateKeyWithPrefix(key); 94 | 95 | String encodedString = preferences.getString(key, null); 96 | String decryptedData; 97 | if (encodedString != null) { 98 | byte[] decodedString = Base64.decode(encodedString, Base64.NO_WRAP); 99 | SecureCell sc = new SecureCell(key.getBytes(StandardCharsets.UTF_8), MODE_SEAL); 100 | 101 | SecureCellData encryptedData = new SecureCellData(decodedString, null); 102 | 103 | byte[] unprotectedData = sc.unprotect(key.getBytes(StandardCharsets.UTF_8), encryptedData); 104 | decryptedData = new String(unprotectedData, StandardCharsets.UTF_8); 105 | 106 | if (callback != null) { 107 | callback.onComplete(GET); 108 | } 109 | 110 | return decryptedData; 111 | 112 | } else { 113 | if (callback != null) { 114 | callback.onError(GET, new SecureStorageException("No Such Value")); 115 | } 116 | return null; 117 | } 118 | 119 | } catch (InvalidArgumentException | NullArgumentException | SecureCellException e) { 120 | e.printStackTrace(); 121 | if (callback != null) { 122 | callback.onError(GET, e); 123 | } 124 | } 125 | return null; 126 | } 127 | 128 | @Override 129 | public void remove(@NonNull String key) { 130 | if (key == null || key.isEmpty()) { 131 | if (callback != null) { 132 | callback.onError(SAVE, new SecureStorageException("Key or Value can't be NULL or empty")); 133 | } 134 | return; 135 | } 136 | 137 | key = generateKeyWithPrefix(key); 138 | preferences.edit().remove(key).apply(); 139 | if (callback != null) { 140 | callback.onComplete(REMOVE); 141 | } 142 | } 143 | 144 | @Override 145 | public void erase() { 146 | preferences.edit().clear().apply(); 147 | 148 | if (callback != null) { 149 | callback.onComplete(ERASE); 150 | } 151 | } 152 | 153 | //Uniq key need to be provided to avoid Key collision in case if two providers 154 | //are used at the same app 155 | private String generateKeyWithPrefix(String key) { 156 | key.trim(); 157 | return Type.THEMIS.toString() + key; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epam/Android-Secure-Storage/28cd2edf5ef694a6f7eee98edf336902d66bf3f7/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epam/Android-Secure-Storage/28cd2edf5ef694a6f7eee98edf336902d66bf3f7/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epam/Android-Secure-Storage/28cd2edf5ef694a6f7eee98edf336902d66bf3f7/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epam/Android-Secure-Storage/28cd2edf5ef694a6f7eee98edf336902d66bf3f7/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epam/Android-Secure-Storage/28cd2edf5ef694a6f7eee98edf336902d66bf3f7/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epam/Android-Secure-Storage/28cd2edf5ef694a6f7eee98edf336902d66bf3f7/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epam/Android-Secure-Storage/28cd2edf5ef694a6f7eee98edf336902d66bf3f7/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epam/Android-Secure-Storage/28cd2edf5ef694a6f7eee98edf336902d66bf3f7/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epam/Android-Secure-Storage/28cd2edf5ef694a6f7eee98edf336902d66bf3f7/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epam/Android-Secure-Storage/28cd2edf5ef694a6f7eee98edf336902d66bf3f7/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | KeyStore 3 | 4 | -------------------------------------------------------------------------------- /app/src/test/java/com/epam/securestorage/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.epam.securestorage; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.3.0' 11 | classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.0.0' 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | maven { 23 | url 'https://artifactory.epam.com/artifactory/libs-release-local' 24 | } 25 | maven { 26 | url "https://dl.bintray.com/cossacklabs/maven/" 27 | } 28 | } 29 | } 30 | 31 | task clean(type: Delete) { 32 | delete rootProject.buildDir 33 | } 34 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | 6 | defaultConfig { 7 | applicationId "com.epam.securestorage.demo" 8 | minSdkVersion 23 9 | targetSdkVersion 26 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | 17 | signingConfigs { 18 | release { 19 | storeFile file('./demoKeystore') 20 | storePassword '12345678' 21 | keyAlias 'key0' 22 | keyPassword '12345678' 23 | } 24 | } 25 | 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 30 | signingConfig signingConfigs.release 31 | } 32 | } 33 | 34 | } 35 | 36 | dependencies { 37 | implementation project(path: ':app') 38 | implementation fileTree(include: ['*.jar'], dir: 'libs') 39 | implementation 'com.android.support:appcompat-v7:26.1.0' 40 | implementation 'com.android.support:design:26.1.0' 41 | implementation 'com.jakewharton:butterknife:8.8.1' 42 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' 43 | implementation 'com.android.support.constraint:constraint-layout:1.1.0' 44 | testImplementation 'junit:junit:4.12' 45 | androidTestImplementation('com.android.support.test:runner:1.0.2') { 46 | exclude group: 'com.android.support', module: 'support-annotations' 47 | } 48 | androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.2') { 49 | exclude group: 'com.android.support', module: 'support-annotations' 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /demo/demoKeystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epam/Android-Secure-Storage/28cd2edf5ef694a6f7eee98edf336902d66bf3f7/demo/demoKeystore -------------------------------------------------------------------------------- /demo/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /demo/src/main/java/com/epam/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.epam.demo; 2 | 3 | import android.content.SharedPreferences; 4 | import android.os.Bundle; 5 | import android.preference.PreferenceManager; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.util.Log; 8 | import android.view.View; 9 | import android.widget.EditText; 10 | import android.widget.TextView; 11 | import android.widget.Toast; 12 | 13 | import com.epam.securestorage.SecureStorage; 14 | import com.epam.securestorage.core.SecureStorageCallback; 15 | import com.epam.securestorage.core.SecurityProvider; 16 | 17 | /** 18 | *

Main Demo Activity

19 | * Description: 20 | * The class is used to demonstrate SecureStorage possibilities 21 | * 22 | * @author Denys Mokhrin 23 | */ 24 | public class MainActivity extends AppCompatActivity { 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_main); 30 | 31 | initCipherProvider(); 32 | initThemisProvider(); 33 | } 34 | 35 | private void initCipherProvider() { 36 | final SecureStorage storage = new SecureStorage(this, SecurityProvider.Type.CIPHER, new SecureStorageCallback() { 37 | @Override 38 | public void onComplete(ActionType actionType) { 39 | if (actionType == ActionType.SAVE) { 40 | Toast.makeText(getBaseContext(), "Has been saved", Toast.LENGTH_SHORT).show(); 41 | } 42 | Log.d("CIPHER_PROVIDER", actionType.toString()); 43 | } 44 | 45 | @Override 46 | public void onError(ActionType actionType, Exception e) { 47 | Toast.makeText(getBaseContext(), "Error on" + actionType.toString() + " " + e.getLocalizedMessage(), Toast.LENGTH_SHORT).show(); 48 | Log.d("CIPHER_PROVIDER", actionType.toString(), e); 49 | } 50 | }); 51 | 52 | final EditText valueKey = findViewById(R.id.value_key); 53 | final EditText valueStore = findViewById(R.id.value_store); 54 | final TextView anyValue = findViewById(R.id.tv_decrypted_value); 55 | 56 | findViewById(R.id.btn_save).setOnClickListener(new View.OnClickListener() { 57 | @Override 58 | public void onClick(View view) { 59 | storage.save(valueKey.getText().toString(), valueStore.getText().toString()); 60 | } 61 | }); 62 | 63 | findViewById(R.id.btn_get_decrypted_value).setOnClickListener(new View.OnClickListener() { 64 | @Override 65 | public void onClick(View view) { 66 | anyValue.setText(storage.get(valueKey.getText().toString())); 67 | } 68 | }); 69 | 70 | findViewById(R.id.btn_get_stored_value).setOnClickListener(new View.OnClickListener() { 71 | @Override 72 | public void onClick(View view) { 73 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(view.getContext()); 74 | anyValue.setText(prefs.getString(SecurityProvider.Type.CIPHER + valueKey.getText().toString(), "")); 75 | } 76 | }); 77 | } 78 | 79 | private void initThemisProvider() { 80 | final SecureStorage storage = new SecureStorage(this, SecurityProvider.Type.THEMIS, new SecureStorageCallback() { 81 | @Override 82 | public void onComplete(SecureStorageCallback.ActionType actionType) { 83 | Log.d("THEMIS_PROVIDER", actionType.toString()); 84 | } 85 | 86 | @Override 87 | public void onError(SecureStorageCallback.ActionType actionType, Exception e) { 88 | Log.d("THEMIS_PROVIDER", actionType.toString(), e); 89 | } 90 | }); 91 | 92 | final EditText valueKey = findViewById(R.id.value_key2); 93 | final EditText valueStore = findViewById(R.id.value_store2); 94 | final TextView anyValue = findViewById(R.id.tv_decrypted_value2); 95 | 96 | findViewById(R.id.btn_save2).setOnClickListener(new View.OnClickListener() { 97 | @Override 98 | public void onClick(View view) { 99 | storage.save(valueKey.getText().toString(), valueStore.getText().toString()); 100 | } 101 | }); 102 | 103 | findViewById(R.id.btn_get_decrypted_value2).setOnClickListener(new View.OnClickListener() { 104 | @Override 105 | public void onClick(View view) { 106 | anyValue.setText(storage.get(valueKey.getText().toString())); 107 | } 108 | }); 109 | 110 | findViewById(R.id.btn_get_stored_value2).setOnClickListener(new View.OnClickListener() { 111 | @Override 112 | public void onClick(View view) { 113 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(view.getContext()); 114 | anyValue.setText(prefs.getString(SecurityProvider.Type.THEMIS + valueKey.getText().toString(), "")); 115 | } 116 | }); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 22 | 23 | 24 | 31 | 32 | 38 | 39 | 46 | 47 | 48 | 53 | 54 | 60 | 61 | 68 | 69 | 75 | 76 | 83 | 84 | 85 | 90 | 91 |