├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── mayank │ │ └── securechat │ │ ├── Constants.java │ │ ├── FingerprintActivity.java │ │ ├── FingerprintHandler.java │ │ ├── FriendlyMessage.java │ │ ├── MainActivity.java │ │ ├── MessageActivity.java │ │ ├── MessageAdapter.java │ │ ├── User.java │ │ └── UsersAdapter.java │ └── res │ ├── drawable │ ├── button_selector.xml │ ├── fingerprint.png │ └── securechat.png │ ├── layout │ ├── activity_fingerprint.xml │ ├── activity_main.xml │ ├── activity_message.xml │ ├── item_message.xml │ └── item_user.xml │ ├── menu │ └── main_menu.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .gradle 4 | local.properties 5 | build 6 | app/google-services.json 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2015 Google Inc 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | All code in any directories or sub-directories that end with *.html or 205 | *.css is licensed under the Creative Commons Attribution International 206 | 4.0 License, which full text can be found here: 207 | https://creativecommons.org/licenses/by/4.0/legalcode. 208 | 209 | As an exception to this license, all html or css that is generated by 210 | the software at the direction of the user is copyright the user. The 211 | user has full ownership and control over such content, including 212 | whether and how they wish to license it. 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SecureChat 2 | 3 | ## Overview 4 | 5 | SecureChat is an app that allows users to send and receive text and photos in realtime across platforms with End to End Encryption. 6 | 7 | This code is inspired from FriendlyChat project in the [Firebase in a Weekend: Android by Google](https://www.udacity.com/course/firebase-in-a-weekend-by-google-android--ud0352) Udacity course. 8 | 9 | 10 | ## Features 11 | - Fingerprint Lock 12 | - End to End Encryption 13 | - Send and Receive Text and Photos 14 | 15 | ## Setup 16 | 17 | Setup requires creating a Firebase project. See https://firebase.google.com/ for more information. 18 | 19 | ## License 20 | See [LICENSE](LICENSE) 21 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | repositories { 4 | mavenLocal() 5 | flatDir { 6 | dirs 'libs' 7 | } 8 | } 9 | 10 | android { 11 | compileSdkVersion 28 12 | buildToolsVersion '28.0.3' 13 | 14 | defaultConfig { 15 | applicationId "com.mayank.securechat" 16 | minSdkVersion 19 17 | targetSdkVersion 28 18 | versionCode 1 19 | versionName "1.0" 20 | } 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | packagingOptions { 28 | exclude 'META-INF/LICENSE' 29 | exclude 'META-INF/LICENSE-FIREBASE.txt' 30 | exclude 'META-INF/NOTICE' 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 36 | implementation fileTree(include: ['*.jar'], dir: 'libs') 37 | testImplementation 'junit:junit:4.12' 38 | implementation 'com.android.support:design:28.0.0' 39 | implementation 'com.android.support:customtabs:28.0.0' 40 | implementation 'com.android.support:appcompat-v7:28.0.0' 41 | // Displaying images 42 | implementation 'com.google.code.gson:gson:2.8.5' 43 | implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' 44 | 45 | implementation 'com.github.bumptech.glide:glide:4.2.0' 46 | implementation 'com.google.firebase:firebase-database:16.1.0' 47 | implementation 'com.google.firebase:firebase-auth:16.1.0' 48 | implementation 'com.firebaseui:firebase-ui-auth:4.3.1' 49 | implementation 'com.google.firebase:firebase-database:16.1.0' 50 | } 51 | apply plugin: 'com.google.gms.google-services' 52 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in ~/android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayank/securechat/Constants.java: -------------------------------------------------------------------------------- 1 | package com.mayank.securechat; 2 | 3 | public class Constants { 4 | public static String PRIVATE_KEY=null; 5 | 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayank/securechat/FingerprintActivity.java: -------------------------------------------------------------------------------- 1 | package com.mayank.securechat; 2 | 3 | import android.Manifest; 4 | import android.app.KeyguardManager; 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.hardware.fingerprint.FingerprintManager; 8 | import android.os.Build; 9 | import android.os.Bundle; 10 | import android.security.keystore.KeyGenParameterSpec; 11 | import android.security.keystore.KeyPermanentlyInvalidatedException; 12 | import android.security.keystore.KeyProperties; 13 | import android.support.annotation.RequiresApi; 14 | import android.support.v4.app.ActivityCompat; 15 | import android.support.v7.app.AppCompatActivity; 16 | import android.util.Log; 17 | import android.widget.ImageView; 18 | import android.widget.TextView; 19 | import android.widget.Toast; 20 | 21 | import com.google.firebase.udacity.friendlychat.R; 22 | 23 | import java.io.IOException; 24 | import java.security.InvalidAlgorithmParameterException; 25 | import java.security.InvalidKeyException; 26 | import java.security.KeyStore; 27 | import java.security.KeyStoreException; 28 | import java.security.NoSuchAlgorithmException; 29 | import java.security.NoSuchProviderException; 30 | import java.security.UnrecoverableKeyException; 31 | import java.security.cert.CertificateException; 32 | 33 | import javax.crypto.Cipher; 34 | import javax.crypto.KeyGenerator; 35 | import javax.crypto.NoSuchPaddingException; 36 | import javax.crypto.SecretKey; 37 | 38 | public class FingerprintActivity extends AppCompatActivity { 39 | 40 | private static final String KEY_NAME = "secretKey"; 41 | private Cipher cipher; 42 | private KeyStore keyStore; 43 | private KeyGenerator keyGenerator; 44 | private TextView textView; 45 | private FingerprintManager.CryptoObject cryptoObject; 46 | private FingerprintManager fingerprintManager; 47 | private KeyguardManager keyguardManager; 48 | ImageView iv_fingerprint; 49 | 50 | @RequiresApi(api = Build.VERSION_CODES.M) 51 | @Override 52 | protected void onCreate(Bundle savedInstanceState) { 53 | super.onCreate(savedInstanceState); 54 | setContentView(R.layout.activity_fingerprint); 55 | Log.v("fingerprint_reader","on create"); 56 | 57 | iv_fingerprint=findViewById(R.id.imageView); 58 | 59 | 60 | } 61 | 62 | @Override 63 | protected void onResume() { 64 | super.onResume(); 65 | initFingerprint(); 66 | } 67 | 68 | public void initFingerprint(){ 69 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 70 | Log.d("androidversion",">m"); 71 | //Get an instance of KeyguardManager and FingerprintManager// 72 | keyguardManager = 73 | (KeyguardManager) getSystemService(KEYGUARD_SERVICE); 74 | fingerprintManager = 75 | (FingerprintManager) getSystemService(FINGERPRINT_SERVICE); 76 | 77 | Log.v("fingerprint_reader", "SDK > M"); 78 | 79 | //Check whether the device has a fingerprint sensor// 80 | if (!fingerprintManager.isHardwareDetected()) { 81 | startMainActivity(); 82 | Toast.makeText(getApplicationContext(), "Your device doesn't support fingerprint authentication", Toast.LENGTH_SHORT).show(); 83 | // If a fingerprint sensor isn’t available, then inform the user that they’ll be unable to use your app’s fingerprint functionality// 84 | } 85 | //Check whether the user has granted your app the USE_FINGERPRINT permission// 86 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { 87 | // If your app doesn't have this permission, then display the following text// 88 | Toast.makeText(getApplicationContext(), "Please enable the fingerprint permission", Toast.LENGTH_SHORT).show(); 89 | } 90 | 91 | //Check that the user has registered at least one fingerprint// 92 | if (!fingerprintManager.hasEnrolledFingerprints()) { 93 | // If the user hasn’t configured any fingerprints, then display the following message// 94 | Toast.makeText(getApplicationContext(), "No fingerprint configured.", Toast.LENGTH_SHORT).show(); 95 | } 96 | 97 | //Check that the lockscreen is secured// 98 | if (!keyguardManager.isKeyguardSecure()) { 99 | // If the user hasn’t secured their lockscreen with a PIN password or pattern, then display the following text// 100 | Toast.makeText(getApplicationContext(), "Please enable lockscreen security in your device's Settings", Toast.LENGTH_SHORT).show(); 101 | startMainActivity(); 102 | } 103 | 104 | //Log.v("fingerprint_reader", "finished if"); 105 | else{ 106 | //Log.v("fingerprint_reader","else entered"); 107 | try { 108 | generateKey(); 109 | } catch (FingerprintException e) { 110 | e.printStackTrace(); 111 | } catch (Exception e){ 112 | e.printStackTrace(); 113 | } 114 | 115 | if (initCipher()) { 116 | //If the cipher is initialized successfully, then create a CryptoObject instance// 117 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 118 | cryptoObject = new FingerprintManager.CryptoObject(cipher); 119 | } 120 | 121 | // Here, I’m referencing the FingerprintHandler class that we’ll create in the next section. This class will be responsible 122 | // for starting the authentication process (via the startAuth method) and processing the authentication process events// 123 | FingerprintHandler helper = new FingerprintHandler(this); 124 | helper.startAuth(fingerprintManager, cryptoObject); 125 | Log.v("fingerprint_reader", "startauth"); 126 | } 127 | } 128 | }else { 129 | startMainActivity(); 130 | } 131 | } 132 | 133 | @RequiresApi(api = Build.VERSION_CODES.M) 134 | private void generateKey() throws FingerprintException { 135 | try { 136 | // Obtain a reference to the Keystore using the standard Android keystore container identifier (“AndroidKeystore”)// 137 | keyStore = KeyStore.getInstance("AndroidKeyStore"); 138 | 139 | //Generate the key// 140 | keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); 141 | 142 | //Initialize an empty KeyStore// 143 | keyStore.load(null); 144 | 145 | //Initialize the KeyGenerator// 146 | keyGenerator.init(new 147 | 148 | //Specify the operation(s) this key can be used for// 149 | KeyGenParameterSpec.Builder(KEY_NAME, 150 | KeyProperties.PURPOSE_ENCRYPT | 151 | KeyProperties.PURPOSE_DECRYPT) 152 | .setBlockModes(KeyProperties.BLOCK_MODE_CBC) 153 | 154 | //Configure this key so that the user has to confirm their identity with a fingerprint each time they want to use it// 155 | .setUserAuthenticationRequired(true) 156 | .setEncryptionPaddings( 157 | KeyProperties.ENCRYPTION_PADDING_PKCS7) 158 | .build()); 159 | 160 | //Generate the key// 161 | keyGenerator.generateKey(); 162 | 163 | } catch (KeyStoreException 164 | | NoSuchAlgorithmException 165 | | NoSuchProviderException 166 | | InvalidAlgorithmParameterException 167 | | CertificateException 168 | | IOException exc) { 169 | exc.printStackTrace(); 170 | //throw new FingerprintException(exc); 171 | } 172 | } 173 | 174 | @RequiresApi(api = Build.VERSION_CODES.M) 175 | public boolean initCipher() { 176 | try { 177 | //Obtain a cipher instance and configure it with the properties required for fingerprint authentication// 178 | cipher = Cipher.getInstance( 179 | KeyProperties.KEY_ALGORITHM_AES + "/" 180 | + KeyProperties.BLOCK_MODE_CBC + "/" 181 | + KeyProperties.ENCRYPTION_PADDING_PKCS7); 182 | } catch (NoSuchAlgorithmException | 183 | NoSuchPaddingException e) { 184 | throw new RuntimeException("Failed to get Cipher", e); 185 | } 186 | 187 | try { 188 | keyStore.load(null); 189 | SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME, 190 | null); 191 | cipher.init(Cipher.ENCRYPT_MODE, key); 192 | //Return true if the cipher has been initialized successfully// 193 | return true; 194 | } catch (KeyPermanentlyInvalidatedException e) { 195 | 196 | //Return false if cipher initialization failed// 197 | return false; 198 | } catch (KeyStoreException | CertificateException 199 | | UnrecoverableKeyException | IOException 200 | | NoSuchAlgorithmException | InvalidKeyException e) { 201 | throw new RuntimeException("Failed to init Cipher", e); 202 | } 203 | } 204 | 205 | public void startMainActivity(){ 206 | Intent intent=new Intent(this,MainActivity.class); 207 | startActivity(intent); 208 | finish(); 209 | } 210 | 211 | private class FingerprintException extends Exception { 212 | public FingerprintException(Exception e) { 213 | super(e); 214 | } 215 | } 216 | 217 | } 218 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayank/securechat/FingerprintHandler.java: -------------------------------------------------------------------------------- 1 | package com.mayank.securechat; 2 | 3 | import android.Manifest; 4 | import android.annotation.TargetApi; 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.hardware.fingerprint.FingerprintManager; 8 | import android.os.Build; 9 | import android.os.CancellationSignal; 10 | import android.support.v4.app.ActivityCompat; 11 | import android.widget.Toast; 12 | 13 | @TargetApi(Build.VERSION_CODES.M) 14 | public class FingerprintHandler extends FingerprintManager.AuthenticationCallback { 15 | 16 | // You should use the CancellationSignal method whenever your app can no longer process user input, for example when your app goes 17 | // into the background. If you don’t use this method, then other apps will be unable to access the touch sensor, including the lockscreen!// 18 | 19 | private CancellationSignal cancellationSignal; 20 | private FingerprintActivity context; 21 | 22 | public FingerprintHandler(FingerprintActivity mContext) { 23 | context = mContext; 24 | } 25 | 26 | //Implement the startAuth method, which is responsible for starting the fingerprint authentication process// 27 | 28 | public void startAuth(FingerprintManager manager, FingerprintManager.CryptoObject cryptoObject) { 29 | 30 | ///startMainActivity(); 31 | 32 | cancellationSignal = new CancellationSignal(); 33 | if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { 34 | return; 35 | } 36 | manager.authenticate(cryptoObject, cancellationSignal, 0, this, null); 37 | } 38 | 39 | @Override 40 | //onAuthenticationError is called when a fatal error has occurred. It provides the error code and error message as its parameters// 41 | 42 | public void onAuthenticationError(int errMsgId, CharSequence errString) { 43 | 44 | //I’m going to display the results of fingerprint authentication as a series of toasts. 45 | //Here, I’m creating the message that’ll be displayed if an error occurs// 46 | 47 | Toast.makeText(context, "Authentication error\n" + errString, Toast.LENGTH_LONG).show(); 48 | context.iv_fingerprint.setColorFilter(context.getResources().getColor(android.R.color.holo_red_light)); 49 | } 50 | 51 | @Override 52 | 53 | //onAuthenticationFailed is called when the fingerprint doesn’t match with any of the fingerprints registered on the device// 54 | 55 | public void onAuthenticationFailed() { 56 | Toast.makeText(context, "Authentication failed", Toast.LENGTH_LONG).show(); 57 | context.iv_fingerprint.setColorFilter(context.getResources().getColor(android.R.color.holo_red_light)); 58 | } 59 | 60 | @Override 61 | 62 | //onAuthenticationHelp is called when a non-fatal error has occurred. This method provides additional information about the error, 63 | //so to provide the user with as much feedback as possible I’m incorporating this information into my toast// 64 | public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { 65 | Toast.makeText(context, "Authentication help\n" + helpString, Toast.LENGTH_LONG).show(); 66 | }@Override 67 | 68 | //onAuthenticationSucceeded is called when a fingerprint has been successfully matched to one of the fingerprints stored on the user’s device// 69 | public void onAuthenticationSucceeded( 70 | FingerprintManager.AuthenticationResult result) { 71 | 72 | Toast.makeText(context, "Success!", Toast.LENGTH_LONG).show(); 73 | context.iv_fingerprint.setColorFilter(context.getResources().getColor(android.R.color.holo_green_light)); 74 | startMainActivity(); 75 | } 76 | 77 | public void startMainActivity(){ 78 | Intent intent=new Intent(context,MainActivity.class); 79 | context.startActivity(intent); 80 | context.finish(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayank/securechat/FriendlyMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.mayank.securechat; 17 | 18 | public class FriendlyMessage { 19 | 20 | private String text; 21 | private String name; 22 | private String photoUrl; 23 | 24 | public FriendlyMessage() { 25 | } 26 | 27 | public FriendlyMessage(String text, String name, String photoUrl) { 28 | this.text = text; 29 | this.name = name; 30 | this.photoUrl = photoUrl; 31 | } 32 | 33 | public String getText() { 34 | return text; 35 | } 36 | 37 | public void setText(String text) { 38 | this.text = text; 39 | } 40 | 41 | public String getName() { 42 | return name; 43 | } 44 | 45 | public void setName(String name) { 46 | this.name = name; 47 | } 48 | 49 | public String getPhotoUrl() { 50 | return photoUrl; 51 | } 52 | 53 | public void setPhotoUrl(String photoUrl) { 54 | this.photoUrl = photoUrl; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayank/securechat/MainActivity.java: -------------------------------------------------------------------------------- 1 | 2 | package com.mayank.securechat; 3 | 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.text.TextUtils; 11 | import android.util.Base64; 12 | import android.util.Log; 13 | import android.view.Menu; 14 | import android.view.MenuInflater; 15 | import android.view.MenuItem; 16 | import android.view.View; 17 | import android.widget.AdapterView; 18 | import android.widget.ListView; 19 | import android.widget.Toast; 20 | 21 | import com.firebase.ui.auth.AuthUI; 22 | import com.google.android.gms.tasks.OnCompleteListener; 23 | import com.google.android.gms.tasks.Task; 24 | import com.google.firebase.auth.FirebaseAuth; 25 | import com.google.firebase.auth.FirebaseUser; 26 | import com.google.firebase.database.ChildEventListener; 27 | import com.google.firebase.database.DataSnapshot; 28 | import com.google.firebase.database.DatabaseError; 29 | import com.google.firebase.database.DatabaseReference; 30 | import com.google.firebase.database.FirebaseDatabase; 31 | import com.google.firebase.database.ValueEventListener; 32 | import com.google.firebase.udacity.friendlychat.R; 33 | import com.google.gson.Gson; 34 | 35 | import java.security.GeneralSecurityException; 36 | import java.security.KeyPair; 37 | import java.security.KeyPairGenerator; 38 | import java.util.ArrayList; 39 | import java.util.Arrays; 40 | import java.util.Iterator; 41 | import java.util.List; 42 | 43 | public class MainActivity extends AppCompatActivity { 44 | 45 | private static final String TAG = "MainActivity"; 46 | 47 | public static final String ANONYMOUS = "anonymous"; 48 | public static final int DEFAULT_MSG_LENGTH_LIMIT = 1000; 49 | public static final int RC_SIGN_IN=1; 50 | 51 | 52 | private ListView mUserListView; 53 | private MessageAdapter mMessageAdapter; 54 | private UsersAdapter mUserAdapter; 55 | List users=new ArrayList<>(); 56 | 57 | 58 | private String mUsername; 59 | private FirebaseDatabase mFirebaseDatabase; 60 | 61 | private DatabaseReference mUsersDatabaseReference; 62 | private DatabaseReference mUsersref; 63 | private ChildEventListener mChildEventListener; 64 | private FirebaseAuth mFirebaseAuth; 65 | private FirebaseAuth.AuthStateListener mAuthStateListener; 66 | 67 | @Override 68 | protected void onCreate(Bundle savedInstanceState) { 69 | super.onCreate(savedInstanceState); 70 | setContentView(R.layout.activity_main); 71 | 72 | mUsername = ANONYMOUS; 73 | 74 | mFirebaseDatabase=FirebaseDatabase.getInstance(); 75 | mFirebaseAuth=FirebaseAuth.getInstance(); 76 | 77 | mUsersDatabaseReference=mFirebaseDatabase.getReference().child("users"); 78 | // Initialize references to views 79 | 80 | mUserListView = (ListView) findViewById(R.id.userListView); 81 | 82 | 83 | 84 | 85 | 86 | mAuthStateListener=new FirebaseAuth.AuthStateListener() { 87 | @Override 88 | public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { 89 | 90 | final FirebaseUser user=firebaseAuth.getCurrentUser(); 91 | 92 | 93 | if(user!=null){ 94 | 95 | 96 | mUsersDatabaseReference.addListenerForSingleValueEvent(new ValueEventListener() { 97 | @Override 98 | public void onDataChange(DataSnapshot dataSnapshot) { 99 | if(!dataSnapshot.hasChild(user.getUid())){ 100 | generateKeyPairs(user); 101 | }else { 102 | loadUserKey(); 103 | } 104 | } 105 | 106 | @Override 107 | public void onCancelled(DatabaseError databaseError) { 108 | 109 | } 110 | }); 111 | 112 | onSignedInInitialize(user.getDisplayName()); 113 | Toast.makeText(getApplicationContext(),"You are now signed in",Toast.LENGTH_SHORT).show(); 114 | getAllUsersFromFirebase(); 115 | }else { 116 | //signed out 117 | 118 | onSignedOutCleanUp(); 119 | startActivityForResult( 120 | AuthUI.getInstance() 121 | .createSignInIntentBuilder() 122 | .setIsSmartLockEnabled(false) 123 | .setAvailableProviders(Arrays.asList( 124 | new AuthUI.IdpConfig.EmailBuilder().build(), 125 | new AuthUI.IdpConfig.GoogleBuilder().build() 126 | ) 127 | ) 128 | .setLogo(R.drawable.securechat) 129 | .build(), 130 | RC_SIGN_IN); 131 | } 132 | } 133 | 134 | }; 135 | 136 | mFirebaseAuth.addAuthStateListener(mAuthStateListener); 137 | } 138 | 139 | private void generateKeyPairs(FirebaseUser user){ 140 | KeyPair keyPair = generateKeys(); 141 | 142 | byte[] publicKey = keyPair.getPublic().getEncoded(); 143 | byte[] privateKey = keyPair.getPrivate().getEncoded(); 144 | String private_key=Base64.encodeToString(privateKey, Base64.NO_WRAP); 145 | String public_key=Base64.encodeToString(publicKey, Base64.NO_WRAP); 146 | Log.d("private_key_gen","key-"+keyPair.getPrivate()); 147 | Log.d("public_key_gen","key-"+keyPair.getPublic()); 148 | savePrivateKey(private_key); 149 | 150 | 151 | 152 | Log.d( "private_key_base64",Base64.encodeToString(privateKey, Base64.NO_WRAP)); 153 | Log.d("public_key_base64", Base64.encodeToString(publicKey, Base64.NO_WRAP) ); 154 | 155 | User newUser=new User(user.getUid(),user.getDisplayName(),user.getEmail(),public_key); 156 | mUsersDatabaseReference 157 | .child(user.getUid()) 158 | .setValue(newUser) 159 | .addOnCompleteListener(new OnCompleteListener() { 160 | @Override 161 | public void onComplete(@NonNull Task task) { 162 | Log.d("user","successfully added"); 163 | } 164 | }); 165 | } 166 | 167 | private void loadUserKey() { 168 | SharedPreferences pref=getSharedPreferences("CHAT_PREF",MODE_PRIVATE); 169 | Constants.PRIVATE_KEY=pref.getString("private_key",null); 170 | if(Constants.PRIVATE_KEY == null) 171 | generateKeyPairs(FirebaseAuth.getInstance().getCurrentUser()); 172 | } 173 | 174 | private void savePrivateKey(String private_key) { 175 | Constants.PRIVATE_KEY=private_key; 176 | SharedPreferences.Editor editor=getSharedPreferences("CHAT_PREF",MODE_PRIVATE).edit(); 177 | editor.putString("private_key",private_key); 178 | editor.apply(); 179 | } 180 | 181 | public KeyPair generateKeys() { 182 | KeyPair keyPair = null; 183 | try { 184 | // get instance of rsa cipher 185 | KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); 186 | keyGen.initialize(2048); // initialize key generator 187 | keyPair = keyGen.generateKeyPair(); // generate pair of keys 188 | } catch(GeneralSecurityException e) { 189 | System.out.println(e); 190 | } 191 | return keyPair; 192 | } 193 | 194 | @Override 195 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 196 | super.onActivityResult(requestCode, resultCode, data); 197 | if(requestCode==RC_SIGN_IN) { 198 | if (resultCode == RESULT_OK) { 199 | Toast.makeText(MainActivity.this, "signed in", Toast.LENGTH_SHORT).show(); 200 | } else if (resultCode == RESULT_CANCELED){ 201 | Toast.makeText(MainActivity.this,"signed in cancelled",Toast.LENGTH_SHORT).show(); 202 | finish(); 203 | } 204 | } 205 | } 206 | 207 | public void getAllUsersFromFirebase() { 208 | final String[] myPublic = {null}; 209 | 210 | /* 211 | mUsersDatabaseReference 212 | .addListenerForSingleValueEvent(new ValueEventListener() { 213 | @Override 214 | public void onDataChange(DataSnapshot dataSnapshot) { 215 | Iterator dataSnapshots = dataSnapshot.getChildren() 216 | .iterator(); 217 | while (dataSnapshots.hasNext()) { 218 | DataSnapshot dataSnapshotChild = dataSnapshots.next(); 219 | User user = dataSnapshotChild.getValue(User.class); 220 | if (!TextUtils.equals(user.uid, 221 | FirebaseAuth.getInstance().getCurrentUser().getUid())) { 222 | if(!users.contains(user)) { 223 | Log.d("onDataChange","usersList-"+(new Gson().toJson(users))+"\nsize-"+users.size()); 224 | users.add(user); 225 | } 226 | }else { 227 | myPublic[0] =user.getPublicKey(); 228 | } 229 | } 230 | 231 | mUserAdapter = new UsersAdapter(MainActivity.this, R.layout.item_user, users); 232 | mUserListView.setAdapter(mUserAdapter); 233 | 234 | Log.d("userslist","list-"+users.toString()); 235 | // All users are retrieved except the one who is currently logged 236 | // in device. 237 | } 238 | 239 | @Override 240 | public void onCancelled(DatabaseError databaseError) { 241 | // Unable to retrieve the users. 242 | } 243 | }); 244 | */ 245 | 246 | mUsersDatabaseReference.addChildEventListener(new ChildEventListener() { 247 | @Override 248 | public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) { 249 | User user = dataSnapshot.getValue(User.class); 250 | if (!TextUtils.equals(user.uid, 251 | FirebaseAuth.getInstance().getCurrentUser().getUid())) { 252 | if(!users.contains(user)) { 253 | Log.d("childAdded","usersList-"+(new Gson().toJson(users))+"\nsize-"+users.size()); 254 | users.add(user); 255 | } 256 | }else { 257 | myPublic[0] =user.getPublicKey(); 258 | } 259 | runOnUiThread(new Runnable() { 260 | @Override 261 | public void run() { 262 | mUserAdapter = new UsersAdapter(MainActivity.this, R.layout.item_user, users); 263 | mUserListView.setAdapter(mUserAdapter); 264 | } 265 | }); 266 | 267 | } 268 | 269 | @Override 270 | public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) { 271 | 272 | } 273 | 274 | @Override 275 | public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) { 276 | User user = dataSnapshot.getValue(User.class); 277 | if(users.contains(user)) { 278 | users.remove(user); 279 | runOnUiThread(new Runnable() { 280 | @Override 281 | public void run() { 282 | if(mUserAdapter!=null) 283 | mUserAdapter.notifyDataSetChanged(); 284 | } 285 | }); 286 | } 287 | } 288 | 289 | @Override 290 | public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) { 291 | 292 | } 293 | 294 | @Override 295 | public void onCancelled(@NonNull DatabaseError databaseError) { 296 | 297 | } 298 | }); 299 | 300 | mUserListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 301 | @Override 302 | public void onItemClick(AdapterView parent, View view, int position, long id) { 303 | User user=mUserAdapter.getItem(position); 304 | 305 | Intent intent=new Intent(MainActivity.this,MessageActivity.class); 306 | Bundle bundle=new Bundle(); 307 | bundle.putString("sender",FirebaseAuth.getInstance().getCurrentUser().getUid()); 308 | bundle.putString("receiver",user.getUid()); 309 | bundle.putString("public_key",user.getPublicKey()); 310 | intent.putExtra("data",bundle); 311 | startActivity(intent); 312 | 313 | } 314 | }); 315 | } 316 | 317 | @Override 318 | public boolean onCreateOptionsMenu(Menu menu) { 319 | MenuInflater inflater = getMenuInflater(); 320 | inflater.inflate(R.menu.main_menu, menu); 321 | return true; 322 | } 323 | 324 | @Override 325 | public boolean onOptionsItemSelected(MenuItem item) { 326 | switch (item.getItemId()){ 327 | case R.id.sign_out_menu: 328 | mUsersDatabaseReference.child(FirebaseAuth.getInstance().getUid()).removeValue(); 329 | AuthUI.getInstance().signOut(this); 330 | return true; 331 | } 332 | return super.onOptionsItemSelected(item); 333 | } 334 | 335 | @Override 336 | protected void onResume() { 337 | super.onResume(); 338 | 339 | } 340 | 341 | @Override 342 | protected void onPause() { 343 | super.onPause(); 344 | 345 | 346 | } 347 | 348 | private void onSignedInInitialize(String username){ 349 | mUsername=username; 350 | 351 | } 352 | 353 | private void onSignedOutCleanUp(){ 354 | 355 | } 356 | 357 | @Override 358 | protected void onDestroy() { 359 | super.onDestroy(); 360 | mFirebaseAuth.removeAuthStateListener(mAuthStateListener); 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayank/securechat/MessageActivity.java: -------------------------------------------------------------------------------- 1 | package com.mayank.securechat; 2 | 3 | import android.content.Intent; 4 | import android.content.SharedPreferences; 5 | import android.support.annotation.NonNull; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.os.Bundle; 8 | import android.text.Editable; 9 | import android.text.InputFilter; 10 | import android.text.TextWatcher; 11 | import android.util.Base64; 12 | import android.util.Log; 13 | import android.view.View; 14 | import android.widget.Button; 15 | import android.widget.EditText; 16 | import android.widget.ImageButton; 17 | import android.widget.ListView; 18 | import android.widget.ProgressBar; 19 | 20 | import com.google.firebase.auth.FirebaseAuth; 21 | import com.google.firebase.database.ChildEventListener; 22 | import com.google.firebase.database.DataSnapshot; 23 | import com.google.firebase.database.DatabaseError; 24 | import com.google.firebase.database.DatabaseReference; 25 | import com.google.firebase.database.FirebaseDatabase; 26 | import com.google.firebase.database.ValueEventListener; 27 | import com.google.firebase.udacity.friendlychat.R; 28 | import com.google.gson.Gson; 29 | import com.google.gson.reflect.TypeToken; 30 | 31 | import java.io.FileWriter; 32 | import java.security.InvalidKeyException; 33 | import java.security.Key; 34 | import java.security.KeyFactory; 35 | import java.security.NoSuchAlgorithmException; 36 | import java.security.spec.InvalidKeySpecException; 37 | import java.security.spec.PKCS8EncodedKeySpec; 38 | import java.security.spec.X509EncodedKeySpec; 39 | import java.util.ArrayList; 40 | import java.util.Date; 41 | import java.util.HashMap; 42 | import java.util.Iterator; 43 | import java.util.List; 44 | import java.util.Map; 45 | import java.util.TreeMap; 46 | 47 | import javax.crypto.BadPaddingException; 48 | import javax.crypto.Cipher; 49 | import javax.crypto.IllegalBlockSizeException; 50 | import javax.crypto.NoSuchPaddingException; 51 | 52 | import static com.mayank.securechat.MainActivity.DEFAULT_MSG_LENGTH_LIMIT; 53 | 54 | public class MessageActivity extends AppCompatActivity { 55 | 56 | private ListView mMessageListView; 57 | private MessageAdapter mMessageAdapter; 58 | private ProgressBar mProgressBar; 59 | private ImageButton mPhotoPickerButton; 60 | private EditText mMessageEditText; 61 | private Button mSendButton; 62 | public static String mUsername; 63 | private String senderUid; 64 | private String receiverUid; 65 | String public_key; 66 | HashMap messageHashMap=new HashMap<>(); 67 | ArrayList messageArrayList=new ArrayList<>(); 68 | List friendlyMessages; 69 | 70 | 71 | private FirebaseDatabase mFirebaseDatabase; 72 | private DatabaseReference mMessagesDatabaseReference; 73 | private ChildEventListener mChildEventListener; 74 | 75 | @Override 76 | protected void onCreate(Bundle savedInstanceState) { 77 | super.onCreate(savedInstanceState); 78 | setContentView(R.layout.activity_message); 79 | 80 | Intent intent=getIntent(); 81 | Bundle bundle=intent.getBundleExtra("data"); 82 | senderUid=bundle.getString("sender"); 83 | receiverUid=bundle.getString("receiver"); 84 | public_key=bundle.getString("public_key"); 85 | mUsername=FirebaseAuth.getInstance().getCurrentUser().getDisplayName(); 86 | //mUsername="anonymous"; 87 | 88 | mFirebaseDatabase=FirebaseDatabase.getInstance(); 89 | mMessagesDatabaseReference=mFirebaseDatabase.getReference().child("messages"); 90 | // Initialize references to views 91 | mProgressBar = (ProgressBar) findViewById(R.id.progressBar); 92 | mMessageListView = (ListView) findViewById(R.id.messageListView); 93 | mPhotoPickerButton = (ImageButton) findViewById(R.id.photoPickerButton); 94 | mMessageEditText = (EditText) findViewById(R.id.messageEditText); 95 | mSendButton = (Button) findViewById(R.id.sendButton); 96 | 97 | // Initialize message ListView and its adapter 98 | friendlyMessages= new ArrayList<>(); 99 | loadMessages(); 100 | mMessageAdapter = new MessageAdapter(this, R.layout.item_message, friendlyMessages); 101 | mMessageListView.setAdapter(mMessageAdapter); 102 | 103 | // Initialize progress bar 104 | mProgressBar.setVisibility(ProgressBar.INVISIBLE); 105 | 106 | // ImagePickerButton shows an image picker to upload a image for a message 107 | mPhotoPickerButton.setOnClickListener(new View.OnClickListener() { 108 | @Override 109 | public void onClick(View view) { 110 | // TODO: Fire an intent to show an image picker 111 | } 112 | }); 113 | 114 | // Enable Send button when there's text to send 115 | mMessageEditText.addTextChangedListener(new TextWatcher() { 116 | @Override 117 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 118 | } 119 | 120 | @Override 121 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 122 | if (charSequence.toString().trim().length() > 0) { 123 | mSendButton.setEnabled(true); 124 | } else { 125 | mSendButton.setEnabled(false); 126 | } 127 | } 128 | 129 | @Override 130 | public void afterTextChanged(Editable editable) { 131 | } 132 | }); 133 | mMessageEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(DEFAULT_MSG_LENGTH_LIMIT)}); 134 | 135 | // Send button sends a message and clears the EditText 136 | mSendButton.setOnClickListener(new View.OnClickListener() { 137 | @Override 138 | public void onClick(View view) { 139 | // TODO: Send messages on click 140 | 141 | String msg=mMessageEditText.getText().toString(); 142 | FriendlyMessage friendlyMessage=new FriendlyMessage(msg,mUsername,null); 143 | mMessageAdapter.add(friendlyMessage); 144 | mMessageAdapter.notifyDataSetChanged(); 145 | String timeStamp=new Date().toString(); 146 | messageHashMap.put(timeStamp,friendlyMessage); 147 | String encrypted_msg=encrypt(msg); 148 | friendlyMessage=new FriendlyMessage(encrypted_msg,mUsername,null); 149 | //mMessagesDatabaseReference.push().setValue(friendlyMessage); 150 | // Clear input box 151 | 152 | //messageArrayList.add(friendlyMessage); 153 | sendMessageToFirebaseUser(timeStamp,friendlyMessage); 154 | mMessageEditText.setText(""); 155 | } 156 | }); 157 | 158 | } 159 | 160 | private void loadMessages() { 161 | SharedPreferences preferences=getSharedPreferences("CHAT_PREF",MODE_PRIVATE); 162 | String messages=preferences.getString(receiverUid,null); 163 | if(messages!=null){ 164 | Gson gson=new Gson(); 165 | messageHashMap=gson.fromJson(messages,new TypeToken>(){}.getType()); 166 | Map map = new TreeMap<>(messageHashMap); 167 | friendlyMessages.addAll(map.values()); 168 | 169 | } 170 | } 171 | 172 | public String encrypt(String msg){ 173 | Cipher cipher = null; 174 | String encrypted=null; 175 | try { 176 | 177 | 178 | 179 | byte[] keyBytes = Base64.decode(public_key,Base64.NO_WRAP); 180 | 181 | X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); 182 | KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 183 | Key publicKey = null; 184 | 185 | 186 | publicKey = keyFactory.generatePublic(x509KeySpec); 187 | 188 | Log.d("public_key_enc","key-"+publicKey); 189 | cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); 190 | 191 | cipher.init(Cipher.ENCRYPT_MODE, publicKey); 192 | 193 | //byte[] encode=Base64.encode(msg.getBytes(),Base64.NO_WRAP); 194 | byte[] encryptedBytes = cipher.doFinal(msg.getBytes()); 195 | /* 196 | Log.d("encrypted_bytes","val-"+encryptedBytes); 197 | Log.d("encrypted_default","val-"+Base64.encodeToString(encryptedBytes, Base64.NO_WRAP)); 198 | */ 199 | encrypted = Base64.encodeToString(encryptedBytes, Base64.NO_WRAP); 200 | 201 | //encrypted=byte2hex(encryptedBytes); 202 | System.out.println("EEncrypted?????" + encrypted); 203 | } catch (InvalidKeyException e) { 204 | e.printStackTrace(); 205 | } catch (BadPaddingException e) { 206 | e.printStackTrace(); 207 | } catch (IllegalBlockSizeException e) { 208 | e.printStackTrace(); 209 | } catch (NoSuchPaddingException e) { 210 | e.printStackTrace(); 211 | } catch (NoSuchAlgorithmException e) { 212 | e.printStackTrace(); 213 | } catch (InvalidKeySpecException e) { 214 | e.printStackTrace(); 215 | } 216 | 217 | Log.d("encrypt","msg-"+msg+" encrypted-"+encrypted); 218 | return encrypted; 219 | } 220 | 221 | 222 | public String decrypt(String msg){ 223 | Cipher cipher = null; 224 | String decrypted=null; 225 | try { 226 | 227 | byte[] keyBytes = Base64.decode(Constants.PRIVATE_KEY,Base64.NO_WRAP); 228 | 229 | PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); 230 | KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 231 | Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec); 232 | 233 | Log.d("private_key_decrypt","key-"+privateKey); 234 | cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); 235 | cipher.init(Cipher.DECRYPT_MODE, privateKey); 236 | 237 | 238 | byte[] decode=Base64.decode(msg,Base64.NO_WRAP); 239 | //byte[] decode = hex2byte(msg.getBytes()); 240 | byte[] decryptedBytes = cipher.doFinal(decode); 241 | decrypted = new String(decryptedBytes); 242 | //decrypted=byte2hex(decryptedBytes); 243 | 244 | } catch (InvalidKeyException e) { 245 | e.printStackTrace(); 246 | } catch (BadPaddingException e) { 247 | e.printStackTrace(); 248 | } catch (IllegalBlockSizeException e) { 249 | e.printStackTrace(); 250 | } catch (NoSuchPaddingException e) { 251 | e.printStackTrace(); 252 | } catch (NoSuchAlgorithmException e) { 253 | e.printStackTrace(); 254 | } catch (InvalidKeySpecException e) { 255 | e.printStackTrace(); 256 | } 257 | Log.d("decrypt","msg-"+msg+" decrypted-"+decrypted); 258 | return decrypted; 259 | } 260 | 261 | ValueEventListener valueEventListener=new ValueEventListener() { 262 | @Override 263 | public void onDataChange(@NonNull DataSnapshot dataSnapshot) { 264 | if(messageHashMap!=null) { 265 | 266 | Iterator it = messageHashMap.entrySet().iterator(); 267 | while (it.hasNext()) { 268 | Map.Entry msg = (Map.Entry)it.next(); 269 | 270 | if (!dataSnapshot.child(msg.getKey()).exists()) { 271 | FriendlyMessage chat1=msg.getValue(); 272 | it.remove(); 273 | friendlyMessages.remove(chat1); 274 | Log.d("Messagekey", "not exists"); 275 | //do ur stuff 276 | } else { 277 | Log.d("Messagekey", "exists"); 278 | //do something if not exists 279 | } 280 | } 281 | if(mMessageAdapter!=null){ 282 | runOnUiThread(new Runnable() { 283 | @Override 284 | public void run() { 285 | mMessageAdapter.notifyDataSetChanged(); 286 | } 287 | }); 288 | } 289 | 290 | } 291 | } 292 | 293 | @Override 294 | public void onCancelled(@NonNull DatabaseError databaseError) { 295 | 296 | } 297 | }; 298 | 299 | ChildEventListener messageListener=new ChildEventListener() { 300 | @Override 301 | public void onChildAdded(final DataSnapshot dataSnapshot, String s) { 302 | // Chat message is retreived. 303 | Log.d("firebaseMessage","onchildadded-"+(new Gson().toJson(dataSnapshot.toString()))); 304 | final FriendlyMessage chat = dataSnapshot.getValue(FriendlyMessage.class); 305 | Log.d("firebaseMessage","onchildaddedChat-"+(new Gson().toJson(chat))); 306 | if(!messageHashMap.containsKey(dataSnapshot.getKey())) { 307 | Log.d("messages","key not present"); 308 | final String decrypted=decrypt(chat.getText()); 309 | if(decrypted!=null) { 310 | runOnUiThread(new Runnable() { 311 | @Override 312 | public void run() { 313 | chat.setText(decrypted); 314 | messageHashMap.put(dataSnapshot.getKey(), chat); 315 | mMessageAdapter.add(chat); 316 | mMessageAdapter.notifyDataSetChanged(); 317 | } 318 | }); 319 | 320 | } 321 | }else { 322 | Log.d("messages","key present-"+(new Gson().toJson(messageHashMap.get(dataSnapshot.getKey())))); 323 | } 324 | for (FriendlyMessage msg:messageHashMap.values() 325 | ) { 326 | Log.d("messages","val-"+msg.getText()); 327 | } 328 | } 329 | 330 | @Override 331 | public void onChildChanged(DataSnapshot dataSnapshot, String s) { 332 | Log.d("firebaseMessage","onchildChnaged-"+(new Gson().toJson(dataSnapshot.toString()))); 333 | } 334 | 335 | @Override 336 | public void onChildRemoved(final DataSnapshot dataSnapshot) { 337 | Log.d("firebaseMessage","onchildremoved-"+(new Gson().toJson(dataSnapshot.toString()))); 338 | if(messageHashMap.containsKey(dataSnapshot.getKey())) { 339 | runOnUiThread(new Runnable() { 340 | @Override 341 | public void run() { 342 | 343 | FriendlyMessage chat1=messageHashMap.remove(dataSnapshot.getKey()); 344 | boolean b=friendlyMessages.remove(chat1); 345 | mMessageAdapter.notifyDataSetChanged(); 346 | } 347 | }); 348 | 349 | } 350 | } 351 | 352 | @Override 353 | public void onChildMoved(DataSnapshot dataSnapshot, String s) { 354 | Log.d("firebaseMessage","onchildmoved-"+(new Gson().toJson(dataSnapshot.toString()))); 355 | } 356 | 357 | @Override 358 | public void onCancelled(DatabaseError databaseError) { 359 | Log.d("firebaseMessage","oncancelled-"+databaseError.getMessage()); 360 | // Unable to get message. 361 | } 362 | 363 | }; 364 | 365 | public void sendMessageToFirebaseUser(final String timestamp,final FriendlyMessage chat) { 366 | final String room_type_1 = senderUid + "_" + receiverUid; 367 | final String room_type_2 = receiverUid + "_" + senderUid; 368 | 369 | final DatabaseReference databaseReference = FirebaseDatabase.getInstance() 370 | .getReference(); 371 | 372 | databaseReference.child("messages") 373 | .getRef() 374 | .addListenerForSingleValueEvent(new ValueEventListener() { 375 | 376 | @Override 377 | public void onDataChange(DataSnapshot dataSnapshot) { 378 | if (dataSnapshot.hasChild(room_type_1)) { 379 | Log.e("messages", "sendMessageToFirebaseUser: " + room_type_1 + " exists"); 380 | databaseReference.child("messages") 381 | .child(room_type_1) 382 | .child(timestamp) 383 | .setValue(chat); 384 | } else if (dataSnapshot.hasChild(room_type_2)) { 385 | Log.e("messages", "sendMessageToFirebaseUser: " + room_type_2 + " exists"); 386 | databaseReference.child("messages") 387 | .child(room_type_2) 388 | .child(timestamp) 389 | .setValue(chat); 390 | } else { 391 | Log.e("messages", "sendMessageToFirebaseUser: success"); 392 | databaseReference.child("messages") 393 | .child(room_type_1) 394 | .child(timestamp) 395 | .setValue(chat); 396 | } 397 | } 398 | 399 | @Override 400 | public void onCancelled(DatabaseError databaseError) { 401 | // Unable to send message. 402 | } 403 | }); 404 | } 405 | 406 | public void getMessageFromFirebaseUser() { 407 | final String room_type_1 = senderUid + "_" + receiverUid; 408 | final String room_type_2 = receiverUid + "_" + senderUid; 409 | 410 | 411 | 412 | mMessagesDatabaseReference 413 | .addListenerForSingleValueEvent(new ValueEventListener() { 414 | @Override 415 | public void onDataChange(DataSnapshot dataSnapshot) { 416 | if (dataSnapshot.hasChild(room_type_1)) { 417 | Log.e("message", "getMessageFromFirebaseUser: " + room_type_1 + " exists"); 418 | mMessagesDatabaseReference 419 | .child(room_type_1) 420 | .addChildEventListener(messageListener); 421 | mMessagesDatabaseReference 422 | .child(room_type_1) 423 | .addListenerForSingleValueEvent(valueEventListener); 424 | 425 | } else if (dataSnapshot.hasChild(room_type_2)) { 426 | Log.e("message", "getMessageFromFirebaseUser: " + room_type_2 + " exists"); 427 | mMessagesDatabaseReference 428 | .child(room_type_2) 429 | .addChildEventListener(messageListener); 430 | mMessagesDatabaseReference 431 | .child(room_type_2) 432 | .addListenerForSingleValueEvent(valueEventListener); 433 | } else { 434 | Log.e("message", "getMessageFromFirebaseUser: no such room available"); 435 | } 436 | } 437 | 438 | @Override 439 | public void onCancelled(DatabaseError databaseError) { 440 | // Unable to get message 441 | } 442 | }); 443 | 444 | 445 | } 446 | 447 | @Override 448 | protected void onResume() { 449 | super.onResume(); 450 | getMessageFromFirebaseUser(); 451 | } 452 | 453 | 454 | private void DetachReadListener(){ 455 | if(mChildEventListener!=null){ 456 | mMessagesDatabaseReference.removeEventListener(mChildEventListener); 457 | } 458 | if(mMessageAdapter!=null) 459 | mMessageAdapter.clear(); 460 | } 461 | 462 | 463 | @Override 464 | protected void onStop() { 465 | super.onStop(); 466 | DetachReadListener(); 467 | saveMessages(); 468 | } 469 | 470 | private void saveMessages() { 471 | SharedPreferences.Editor editor=getSharedPreferences("CHAT_PREF",MODE_PRIVATE).edit(); 472 | try { 473 | Gson gson=new Gson(); 474 | String data=gson.toJson(messageHashMap); 475 | editor.putString(receiverUid,data); 476 | editor.apply(); 477 | Log.d("messageListsave","list-"+(new Gson().toJson(messageHashMap))); 478 | 479 | } catch (Exception e) { 480 | e.printStackTrace(); 481 | } 482 | 483 | } 484 | 485 | @Override 486 | protected void onDestroy() { 487 | super.onDestroy(); 488 | messageHashMap.clear(); 489 | friendlyMessages.clear(); 490 | messageArrayList.clear(); 491 | 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayank/securechat/MessageAdapter.java: -------------------------------------------------------------------------------- 1 | package com.mayank.securechat; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.view.Gravity; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.ArrayAdapter; 9 | import android.widget.ImageView; 10 | import android.widget.LinearLayout; 11 | import android.widget.TextView; 12 | 13 | import com.bumptech.glide.Glide; 14 | import com.google.firebase.udacity.friendlychat.R; 15 | 16 | import java.util.List; 17 | 18 | public class MessageAdapter extends ArrayAdapter { 19 | public MessageAdapter(Context context, int resource, List objects) { 20 | super(context, resource, objects); 21 | } 22 | 23 | @Override 24 | public View getView(int position, View convertView, ViewGroup parent) { 25 | if (convertView == null) { 26 | convertView = ((Activity) getContext()).getLayoutInflater().inflate(R.layout.item_message, parent, false); 27 | } 28 | 29 | ImageView photoImageView = (ImageView) convertView.findViewById(R.id.photoImageView); 30 | TextView messageTextView = (TextView) convertView.findViewById(R.id.messageTextView); 31 | TextView authorTextView = (TextView) convertView.findViewById(R.id.nameTextView); 32 | 33 | FriendlyMessage message = getItem(position); 34 | 35 | LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( 36 | LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); 37 | if(message.getName().equals(MessageActivity.mUsername)) { 38 | params.leftMargin=50; 39 | params.gravity = Gravity.END; 40 | messageTextView.setLayoutParams(params); 41 | messageTextView.setGravity(Gravity.END); 42 | messageTextView.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_END); 43 | authorTextView.setLayoutParams(params); 44 | }else{ 45 | params.rightMargin=50; 46 | params.gravity = Gravity.START; 47 | messageTextView.setLayoutParams(params); 48 | messageTextView.setGravity(Gravity.START); 49 | messageTextView.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START); 50 | authorTextView.setLayoutParams(params); 51 | } 52 | boolean isPhoto = message.getPhotoUrl() != null; 53 | if (isPhoto) { 54 | messageTextView.setVisibility(View.GONE); 55 | photoImageView.setVisibility(View.VISIBLE); 56 | Glide.with(photoImageView.getContext()) 57 | .load(message.getPhotoUrl()) 58 | .into(photoImageView); 59 | } else { 60 | messageTextView.setVisibility(View.VISIBLE); 61 | photoImageView.setVisibility(View.GONE); 62 | 63 | messageTextView.setText(message.getText()); 64 | } 65 | authorTextView.setText(message.getName()); 66 | 67 | return convertView; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayank/securechat/User.java: -------------------------------------------------------------------------------- 1 | package com.mayank.securechat; 2 | 3 | public class User { 4 | String displayName; 5 | String uid; 6 | String emailId; 7 | String publicKey; 8 | 9 | 10 | public User(){ 11 | 12 | } 13 | 14 | public User(String uid, String name, String email,String key) { 15 | this.uid = uid; 16 | this.displayName = name; 17 | this.emailId = email; 18 | this.publicKey=key; 19 | } 20 | 21 | /* 22 | public User(String uid, String name, String email,String key) { 23 | this.uid = uid; 24 | this.displayName = name; 25 | this.emailId = email; 26 | this.publicKey=key; 27 | } 28 | */ 29 | 30 | 31 | public String getDisplayName() { 32 | return displayName; 33 | } 34 | 35 | public void setDisplayName(String displayName) { 36 | this.displayName = displayName; 37 | } 38 | 39 | public String getUid() { 40 | return uid; 41 | } 42 | 43 | public String getPublicKey() { 44 | return publicKey; 45 | } 46 | 47 | public void setUid(String uid) { 48 | this.uid = uid; 49 | } 50 | 51 | public String getEmailId() { 52 | return emailId; 53 | } 54 | 55 | public void setEmailId(String emailId) { 56 | this.emailId = emailId; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayank/securechat/UsersAdapter.java: -------------------------------------------------------------------------------- 1 | package com.mayank.securechat; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.support.annotation.Nullable; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.ArrayAdapter; 9 | import android.widget.ImageView; 10 | import android.widget.TextView; 11 | 12 | import com.amulyakhare.textdrawable.TextDrawable; 13 | import com.amulyakhare.textdrawable.util.ColorGenerator; 14 | import com.google.firebase.udacity.friendlychat.R; 15 | 16 | import java.util.List; 17 | 18 | public class UsersAdapter extends ArrayAdapter { 19 | public UsersAdapter(Context context, int resource, List objects) { 20 | super(context, resource, objects); 21 | } 22 | 23 | @Override 24 | public View getView(int position, View convertView, ViewGroup parent) { 25 | if (convertView == null) { 26 | convertView = ((Activity) getContext()).getLayoutInflater().inflate(R.layout.item_user, parent, false); 27 | } 28 | 29 | ColorGenerator generator = ColorGenerator.MATERIAL; 30 | 31 | 32 | ImageView imageView=convertView.findViewById(R.id.photoImageView); 33 | TextView nameTextView = (TextView) convertView.findViewById(R.id.nameTextView); 34 | 35 | User user = getItem(position); 36 | int color1 = generator.getColor(user.getDisplayName()); 37 | TextDrawable drawable = TextDrawable 38 | .builder() 39 | .beginConfig() 40 | .fontSize(75) 41 | .endConfig() 42 | .buildRound(user.getDisplayName().substring(0,1), color1); 43 | 44 | nameTextView.setText(user.getDisplayName()); 45 | imageView.setImageDrawable(drawable); 46 | 47 | return convertView; 48 | } 49 | 50 | @Nullable 51 | @Override 52 | public User getItem(int position) { 53 | return super.getItem(position); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/fingerprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey531/SecureChatApp/73577d6eced19ab489060dca8fc9ee35f4791c39/app/src/main/res/drawable/fingerprint.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/securechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey531/SecureChatApp/73577d6eced19ab489060dca8fc9ee35f4791c39/app/src/main/res/drawable/securechat.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_fingerprint.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | 28 | 29 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 21 | 22 | 23 | 31 | 32 | 37 | 38 | 44 | 45 |