├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── Android-PwdHash.zip ├── ChangeLog ├── LICENSE ├── README.md ├── TODO ├── app ├── build.gradle ├── lint.xml └── src │ ├── androidTest │ ├── java │ │ └── com │ │ │ └── uploadedlobster │ │ │ └── PwdHash │ │ │ ├── DomainExtractorTest.java │ │ │ ├── HashedPasswordTest.java │ │ │ ├── MainActivityTest.java │ │ │ ├── PreferencesTest.java │ │ │ └── utils │ │ │ └── ToastMatcher.java │ └── res │ │ ├── drawable │ │ └── icon.png │ │ └── values │ │ └── strings.xml │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── uploadedlobster │ │ └── PwdHash │ │ ├── activities │ │ └── PwdHashApp.kt │ │ ├── algorithm │ │ ├── DomainExtractor.kt │ │ └── HashedPassword.kt │ │ ├── storage │ │ ├── HistoryDataSource.kt │ │ ├── HistoryOpenHelper.kt │ │ └── UpdateHistoryTask.kt │ │ └── util │ │ ├── Constants.kt │ │ └── Preferences.kt │ └── res │ ├── drawable-hdpi │ └── icon.png │ ├── drawable-ldpi │ └── icon.png │ ├── drawable-mdpi │ └── icon.png │ ├── drawable-xhdpi │ └── icon.png │ ├── drawable-xxhdpi │ └── icon.png │ ├── drawable-xxxhdpi │ └── icon.png │ ├── layout │ └── main.xml │ ├── values-ar │ └── strings.xml │ ├── values-ca │ └── strings.xml │ ├── values-cs │ └── strings.xml │ ├── values-de │ └── strings.xml │ ├── values-el │ └── strings.xml │ ├── values-es │ └── strings.xml │ ├── values-fr │ └── strings.xml │ ├── values-hr │ └── strings.xml │ ├── values-it │ └── strings.xml │ ├── values-ja │ └── strings.xml │ ├── values-ko │ └── strings.xml │ ├── values-nb-rNO │ └── strings.xml │ ├── values-nl-rNL │ └── strings.xml │ ├── values-pl │ └── strings.xml │ ├── values-pt-rBR │ └── strings.xml │ ├── values-ro-rRO │ └── strings.xml │ ├── values-ru │ └── strings.xml │ ├── values-tr │ └── strings.xml │ ├── values-uk │ └── strings.xml │ ├── values-v11 │ ├── style.xml │ └── theme.xml │ ├── values-v21 │ ├── geometry.xml │ ├── style.xml │ └── theme.xml │ ├── values-vi │ └── strings.xml │ ├── values-zh-rCN │ └── strings.xml │ └── values │ ├── geometry.xml │ ├── strings.xml │ ├── style.xml │ └── theme.xml ├── build.gradle ├── design ├── feature-graphic.svg └── icon.svg ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | Thumbs.db 9 | build/ 10 | app/release/ 11 | /captures 12 | .externalNativeBuild 13 | *~ 14 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | xmlns:android 11 | 12 | ^$ 13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | xmlns:.* 22 | 23 | ^$ 24 | 25 | 26 | BY_NAME 27 | 28 |
29 |
30 | 31 | 32 | 33 | .*:id 34 | 35 | http://schemas.android.com/apk/res/android 36 | 37 | 38 | 39 |
40 |
41 | 42 | 43 | 44 | .*:name 45 | 46 | http://schemas.android.com/apk/res/android 47 | 48 | 49 | 50 |
51 |
52 | 53 | 54 | 55 | name 56 | 57 | ^$ 58 | 59 | 60 | 61 |
62 |
63 | 64 | 65 | 66 | style 67 | 68 | ^$ 69 | 70 | 71 | 72 |
73 |
74 | 75 | 76 | 77 | .* 78 | 79 | ^$ 80 | 81 | 82 | BY_NAME 83 | 84 |
85 |
86 | 87 | 88 | 89 | .* 90 | 91 | http://schemas.android.com/apk/res/android 92 | 93 | 94 | ANDROID_ATTRIBUTE_ORDER 95 | 96 |
97 |
98 | 99 | 100 | 101 | .* 102 | 103 | .* 104 | 105 | 106 | BY_NAME 107 | 108 |
109 |
110 |
111 |
112 |
113 |
-------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Android-PwdHash.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phw/Android-PwdHash/65d7cdf980b31eb05109699670cfefd74e475da3/Android-PwdHash.zip -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2019-09-27 Philipp Wolfer 2 | * Release 1.3.15 3 | * Tested with Android 9 and 10 4 | * Passwords should be invisible to the accessibility services (mohammadnaseri) 5 | 6 | 2018-05-03 Philipp Wolfer 7 | * Release 1.3.14 8 | * Tested with Android 8.1 9 | * Norwegian Bokmål translation (by Allan Nordhøy) 10 | * Italian translation 11 | * Use new clipboard API for Android API level >= 11 12 | * Disable personalized keyboard learning on password input, if supported (Pedro Veloso) 13 | * Migrated build to Android Studio 14 | 15 | 2015-04-16 Philipp Wolfer 16 | 17 | * Release 1.3.13 18 | * Greek translation (by Wasilis Mandratzis-Walz) 19 | * Ukrainian translation (by sorenabell) 20 | * Tested with Android 5.1 21 | 22 | 2014-11-05 Philipp Wolfer 23 | 24 | * Release 1.3.12 25 | * Fixed gradient in new app icon 26 | 27 | 2014-11-04 Philipp Wolfer 28 | 29 | * Release 1.3.11 30 | * Japanese translation (by Naofumi) 31 | * Updated Spanish translation 32 | * Tested with Android 5.0 33 | * Use Material theme on Android 5.0 34 | * Improved app icon 35 | 36 | 2014-04-11 Philipp Wolfer 37 | 38 | * Release 1.3.10 39 | * Setup Transifex synchronisation 40 | * Added Romanian translation (by ArianServ) 41 | * Added Turkish translation (by Muhammed İyi) 42 | * Updated French and Vietnamese translations 43 | * Partial Arabic translation 44 | 45 | 2013-12-27 Philipp Wolfer 46 | 47 | * Release 1.3.9 48 | * Added Croatian translation (by Marijan Smetko, Novska, Croatia) 49 | * Fixed another possible exception when launched from another app 50 | * Tested with Android 4.4 51 | 52 | 2013-09-09 Philipp Wolfer 53 | 54 | * Release 1.3.8 55 | * Fixed possible exception on start 56 | 57 | 2013-09-01 Philipp Wolfer 58 | 59 | * Release 1.3.7 60 | * Fixed occasional crash on some Android 4.3 devices 61 | * Dutch translation (by Kevin Morssink) 62 | 63 | 2013-07-25 Philipp Wolfer 64 | 65 | * Release 1.3.6 66 | * Keyboard should use no auto correction in URL input 67 | * Bugfix: Removed screenshot blocking in Android prior 68 | to 3.0. This security fix caused a distorted screen 69 | on some Samsung devices running Android 2.2 or 2.3. 70 | * Updated Czech translation 71 | * Tested with Android 4.3 72 | 73 | 2013-06-30 Philipp Wolfer 74 | 75 | * Release 1.3.5 76 | * French translation 77 | * Vietnamese translation (by Phan Anh) 78 | 79 | 2013-06-06 Philipp Wolfer 80 | 81 | * Release 1.3.4 82 | * Brazilian Portuguese translation (by Evandro) 83 | * Updated Korean translation (by Jongha Kim) 84 | 85 | 86 | 2013-05-18 Philipp Wolfer 87 | 88 | * Release 1.3.3 89 | * Korean translation (by Jongha Kim) 90 | * Czech translation (by Ondřej Vodáček) 91 | 92 | 2013-03-09 Philipp Wolfer 93 | 94 | * Release 1.3.2 95 | * Trim the URL to the domain part when "share" feature is used. 96 | * Use monospaced font for displaying the hashed password. 97 | 98 | 2012-11-07 Philipp Wolfer 99 | 100 | * Release 1.3.1 101 | * Security fix: Don't allow screenshots in recent apps menu 102 | 103 | 2012-07-09 Philipp Wolfer 104 | 105 | * Release 1.3.0 106 | * Auto completion for recently used domain names 107 | * Use Holo theme on Android 3.0 and newer 108 | * Added xhdpi icon 109 | * Enabled app on SD 110 | * Tested with Android 4.1 Jelly Bean 111 | 112 | 2012-05-18 Philipp Wolfer 113 | 114 | * Release 1.2.3 115 | * Remember last used site URL. 116 | * Added Chinese (China, simplified) translation. 117 | 118 | 2012-01-16 Philipp Wolfer 119 | 120 | * Release 1.2.2 121 | * Added Russian and Polish translations. 122 | 123 | 2011-09-28 Philipp Wolfer 124 | 125 | * Release 1.2.1 126 | * Added Spanish and Catalan translations. 127 | 128 | 2011-06-30 Philipp Wolfer 129 | 130 | * Release 1.2 131 | * Remove NULL bytes from end of hashed passwords (fixes issue #1) 132 | * Move all dimensions to resource file 133 | 134 | 2011-02-08 Philipp Wolfer 135 | 136 | * Release 1.1 137 | * Use density independent pixels 138 | * Set copy button inactive when unusable 139 | * Limit window width on large screens 140 | 141 | 2010-12-12 Philipp Wolfer 142 | 143 | * Initial release 1.0 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | PwdHash for Android is Copyright (c) 2010-2011 Philipp Wolfer 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 3. Neither the name of PwdHash for Android nor the names of the 12 | contributors may be used to endorse or promote products derived from 13 | this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 23 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android PwdHash 2 | =============== 3 | 4 | Description 5 | ----------- 6 | Lightweight tool to generate website specific, theft-resistant passwords. Just 7 | use the "Share page" option in the Android browser or open Password Hash 8 | directly. Based upon and compatible with pwdhash.com. 9 | 10 | License 11 | ------- 12 | Android PwdHash is free software published under a BSD style open source license. 13 | See LICENSE for details. 14 | 15 | Translations 16 | ------------ 17 | You can help translate this project into your language. Please visit the Android PwdHash 18 | localization project on https://translate.uploadedlobster.com/engage/android-pwdhash/ 19 | 20 | Known Issues 21 | ----------- 22 | * Android PwdHash does not allow empty passwords for generating the hash, while 23 | pwdhash.com does. 24 | 25 | Author 26 | ------ 27 | Philipp Wolfer 28 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Copy to clipboard and open browser 2 | * Allow cleartext password field 3 | * Better notify user about empty input (flash/animate the TextEdit) 4 | * Place Password Hash in notification area for quick access -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion 31 6 | buildToolsVersion '30.0.3' 7 | defaultConfig { 8 | applicationId "com.uploadedlobster.PwdHash" 9 | minSdkVersion 17 10 | targetSdkVersion 31 11 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 12 | versionCode 26 13 | versionName '1.3.15' 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 19 | } 20 | } 21 | return void 22 | } 23 | 24 | dependencies { 25 | testImplementation 'junit:junit:4.13.2' 26 | androidTestImplementation 'androidx.annotation:annotation:1.2.0' 27 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 28 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 29 | implementation "androidx.core:core-ktx:1.6.0" 30 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 31 | } 32 | -------------------------------------------------------------------------------- /app/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/uploadedlobster/PwdHash/DomainExtractorTest.java: -------------------------------------------------------------------------------- 1 | package com.uploadedlobster.PwdHash; 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4; 4 | 5 | import com.uploadedlobster.PwdHash.algorithm.DomainExtractor; 6 | 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | 16 | /** 17 | * @author Philipp Wolfer 18 | */ 19 | @RunWith(AndroidJUnit4.class) 20 | public class DomainExtractorTest { 21 | 22 | private static HashMap testSamples; 23 | 24 | @Before 25 | public void setUp() throws Exception { 26 | testSamples = new HashMap<>(); 27 | testSamples.put("example.com", "example.com"); 28 | testSamples.put("http://example.com", "example.com"); 29 | testSamples.put("http://example.com/aPath/test.html", "example.com"); 30 | testSamples.put("http://www.example.com", "example.com"); 31 | testSamples.put("https://www.example.com", "example.com"); 32 | testSamples.put("http://www.example.com/aPath/test.html", "example.com"); 33 | testSamples.put("http://login.test.example.com", "example.com"); 34 | testSamples.put("http://example.co.uk", "example.co.uk"); 35 | testSamples.put("http://login.example.co.uk", "example.co.uk"); 36 | testSamples.put("https://login.example.co.uk/test.htm", "example.co.uk"); 37 | } 38 | 39 | @Test 40 | public void testExtractDomain() throws Exception { 41 | for (Map.Entry t : testSamples.entrySet()) { 42 | assertEquals(t.getValue(), 43 | DomainExtractor.extractDomain(t.getKey())); 44 | } 45 | } 46 | 47 | @Test 48 | public void testExtractDomainWithEmptyStringInput() throws Exception { 49 | assertEquals("", DomainExtractor.extractDomain("")); 50 | } 51 | 52 | @Test(expected = IllegalArgumentException.class) 53 | public void testExtractDomainWithNullInput() throws Exception { 54 | DomainExtractor.extractDomain(null); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/uploadedlobster/PwdHash/HashedPasswordTest.java: -------------------------------------------------------------------------------- 1 | package com.uploadedlobster.PwdHash; 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4; 4 | 5 | import com.uploadedlobster.PwdHash.algorithm.HashedPassword; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | 12 | /** 13 | * @author Philipp Wolfer 14 | */ 15 | @RunWith(AndroidJUnit4.class) 16 | public class HashedPasswordTest { 17 | 18 | @Test 19 | public void testToString() { 20 | HashedPassword hashedPassword = HashedPassword.create("my53cret#", 21 | "example.com"); 22 | assertEquals("Bu6aSm+Zcsf", hashedPassword.toString()); 23 | } 24 | 25 | @Test 26 | public void testToStringWithNonAsciiChars() { 27 | HashedPassword hashedPassword = HashedPassword.create("mü53crét#", 28 | "example.com"); 29 | assertEquals("r9qeSjv+lwJ", hashedPassword.toString()); 30 | } 31 | 32 | @Test 33 | public void testToStringWithNonLatin1Chars() { 34 | HashedPassword hashedPassword = HashedPassword.create("中文العربي", 35 | "example.com"); 36 | assertEquals("AwMz3+BdMT", hashedPassword.toString()); 37 | } 38 | 39 | @Test 40 | public void testToStringWithoutNonAlphanumeric() { 41 | HashedPassword hashedPassword = HashedPassword.create("my53cret", 42 | "example.com"); 43 | assertEquals("CIUD4SCSgh", hashedPassword.toString()); 44 | } 45 | 46 | @Test 47 | public void testToStringWithShortSecret() { 48 | HashedPassword hashedPassword = HashedPassword.create("ab", 49 | "example.com"); 50 | assertEquals("0IKv", hashedPassword.toString()); 51 | } 52 | 53 | @Test 54 | public void testToStringWithShortestSecret() { 55 | HashedPassword hashedPassword = HashedPassword.create("a", 56 | "example.com"); 57 | assertEquals("9FBo", hashedPassword.toString()); 58 | } 59 | 60 | @Test 61 | public void testToStringWithLongSecret() { 62 | HashedPassword hashedPassword = HashedPassword.create( 63 | "abcdefghijklmnopqrstuvwxyz0123456789=", "example.com"); 64 | String result = hashedPassword.toString(); 65 | 66 | // The original algorithm appends NULL bytes at the end. 67 | // Those bytes should not be part of the output. 68 | // "XO3u58jVa1nd+8qd08SDIQ\0\0\0\0" 69 | assertEquals("XO3u58jVa1nd+8qd08SDIQ", result); 70 | } 71 | 72 | @Test(expected = IllegalArgumentException.class) 73 | public void testToStringWithEmptySecret() { 74 | HashedPassword hashedPassword = HashedPassword.create("", 75 | "example.com"); 76 | hashedPassword.toString(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/uploadedlobster/PwdHash/MainActivityTest.java: -------------------------------------------------------------------------------- 1 | package com.uploadedlobster.PwdHash; 2 | 3 | import android.content.ClipboardManager; 4 | import android.content.Context; 5 | import androidx.test.espresso.ViewInteraction; 6 | import androidx.test.rule.ActivityTestRule; 7 | import androidx.test.ext.junit.runners.AndroidJUnit4; 8 | 9 | import com.uploadedlobster.PwdHash.activities.PwdHashApp; 10 | 11 | import org.junit.Before; 12 | import org.junit.Rule; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | 16 | import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 17 | import static androidx.test.platform.app.InstrumentationRegistry.getTargetContext; 18 | import static androidx.test.espresso.Espresso.onView; 19 | import static androidx.test.espresso.action.ViewActions.clearText; 20 | import static androidx.test.espresso.action.ViewActions.click; 21 | import static androidx.test.espresso.action.ViewActions.replaceText; 22 | import static androidx.test.espresso.action.ViewActions.typeText; 23 | import static androidx.test.espresso.assertion.ViewAssertions.matches; 24 | import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; 25 | import static androidx.test.espresso.matcher.ViewMatchers.withId; 26 | import static androidx.test.espresso.matcher.ViewMatchers.withText; 27 | import static junit.framework.TestCase.assertEquals; 28 | import static org.hamcrest.CoreMatchers.not; 29 | 30 | /** 31 | * @author Philipp Wolfer 32 | */ 33 | @RunWith(AndroidJUnit4.class) 34 | public class MainActivityTest { 35 | private ViewInteraction siteAddressInput; 36 | private ViewInteraction passwordInput; 37 | private ViewInteraction copyBtn; 38 | 39 | @Rule 40 | public ActivityTestRule mActivityRule = new ActivityTestRule<>( 41 | PwdHashApp.class); 42 | 43 | @Before 44 | public void setUp() throws Exception { 45 | siteAddressInput = onView(withId(R.id.siteAddress)); 46 | passwordInput = onView(withId(R.id.password)); 47 | copyBtn = onView(withId(R.id.copyBtn)); 48 | } 49 | 50 | @Test 51 | public void testCopyButtonDisabledWhenAllInputsAreEmpty() { 52 | clearAllInputs(); 53 | copyBtn.check(matches(not(isEnabled()))); 54 | } 55 | 56 | @Test 57 | public void testCopyButtonEnabledWhenAllInputsAreSet() { 58 | clearAllInputs(); 59 | siteAddressInput.perform(typeText("http://www.example.com/test")); 60 | passwordInput.perform(typeText("mysecret")); 61 | copyBtn.check(matches(isEnabled())); 62 | } 63 | 64 | @Test 65 | public void testCopyButtonDisabledWhenOnlySiteAdressInputIsSet() { 66 | clearAllInputs(); 67 | siteAddressInput.perform(typeText("http://www.example.com/test")); 68 | copyBtn.check(matches(not(isEnabled()))); 69 | } 70 | 71 | @Test 72 | public void testCopyButtonDisabledWhenOnlyPasswordInputIsSet() { 73 | clearAllInputs(); 74 | passwordInput.perform(typeText("mysecret")); 75 | copyBtn.check(matches(not(isEnabled()))); 76 | } 77 | 78 | @Test 79 | public void testCopyToClipboard() { 80 | clearAllInputs(); 81 | siteAddressInput.perform(typeText("http://www.example.com/test")); 82 | passwordInput.perform(typeText("mysecret")); 83 | copyBtn.perform(click()); 84 | 85 | getInstrumentation().runOnMainSync(new Runnable() { 86 | @SuppressWarnings("deprecation") 87 | @Override 88 | public void run() { 89 | final Context context = getTargetContext(); 90 | String clipboardContent; 91 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { 92 | final ClipboardManager clipboard = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE); 93 | clipboardContent = clipboard.getPrimaryClip().getItemAt(0).getText().toString(); 94 | } 95 | else { 96 | final android.text.ClipboardManager clipboard = (android.text.ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE); 97 | clipboardContent = clipboard.getText().toString(); 98 | } 99 | 100 | assertEquals("C3bvEXk6rU", clipboardContent); 101 | } 102 | }); 103 | } 104 | 105 | // @Test 106 | // public void testToastMessage() { 107 | // clearAllInputs(); 108 | // 109 | // siteAddressInput.perform(typeText("http://www.example.com/test")); 110 | // passwordInput.perform(typeText("mysecret")); 111 | // copyBtn.perform(click()); 112 | // 113 | // // Check display of toast message 114 | // onView(withText(R.string.copiedToClipboardNotification)) 115 | // .inRoot(withDecorView(not(mActivityRule.getActivity().getWindow().getDecorView()))) 116 | // .check(matches(isDisplayed())); 117 | // onView(withText(R.string.copiedToClipboardNotification)).inRoot(new ToastMatcher()) 118 | // .check(matches(isDisplayed())); 119 | // } 120 | 121 | @Test 122 | public void testDisplayOfPassword() { 123 | ViewInteraction hashedPassword = onView(withId(R.id.hashedPassword)); 124 | hashedPassword.check(matches(not(withText("C3bvEXk6rU")))); 125 | 126 | clearAllInputs(); 127 | siteAddressInput.perform(typeText("http://www.example.com/test")); 128 | passwordInput.perform(typeText("mysecret")); 129 | hashedPassword.check(matches(withText("C3bvEXk6rU"))); 130 | 131 | passwordInput.perform(replaceText("myothersecret")); 132 | hashedPassword.check(matches(not(withText("C3bvEXk6rU")))); 133 | hashedPassword.check(matches(withText("dG4KvuJTNGrWRY2"))); 134 | } 135 | 136 | private void clearAllInputs() { 137 | siteAddressInput.perform(clearText()); 138 | passwordInput.perform(clearText()); 139 | } 140 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/uploadedlobster/PwdHash/PreferencesTest.java: -------------------------------------------------------------------------------- 1 | package com.uploadedlobster.PwdHash; 2 | 3 | import android.content.Context; 4 | import androidx.test.platform.app.InstrumentationRegistry; 5 | import androidx.test.ext.junit.runners.AndroidJUnit4; 6 | 7 | import com.uploadedlobster.PwdHash.util.Preferences; 8 | 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | 12 | import static org.junit.Assert.assertEquals; 13 | import static org.junit.Assert.assertNotEquals; 14 | 15 | @RunWith(AndroidJUnit4.class) 16 | public class PreferencesTest { 17 | @Test 18 | public void testSaveSiteAddress() { 19 | final Context context = InstrumentationRegistry.getTargetContext(); 20 | Preferences preferences = new Preferences(context); 21 | 22 | String urlToSave = "https://www.example.com/storage-test"; 23 | String urlLoaded = preferences.getSavedSiteAddress(); 24 | 25 | assertNotEquals(urlToSave, urlLoaded); 26 | 27 | preferences.setSavedSiteAddress(urlToSave); 28 | urlLoaded = preferences.getSavedSiteAddress(); 29 | assertEquals(urlToSave, urlLoaded); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/uploadedlobster/PwdHash/utils/ToastMatcher.java: -------------------------------------------------------------------------------- 1 | package com.uploadedlobster.PwdHash.utils; 2 | 3 | import android.os.IBinder; 4 | import androidx.test.espresso.Root; 5 | import android.view.WindowManager; 6 | 7 | import org.hamcrest.Description; 8 | import org.hamcrest.TypeSafeMatcher; 9 | 10 | class ToastMatcher extends TypeSafeMatcher { 11 | 12 | @Override 13 | public void describeTo(Description description) { 14 | description.appendText("is toast"); 15 | } 16 | 17 | @Override 18 | public boolean matchesSafely(Root root) { 19 | int type = root.getWindowLayoutParams().get().type; 20 | if ((type == WindowManager.LayoutParams.TYPE_TOAST)) { 21 | IBinder windowToken = root.getDecorView().getWindowToken(); 22 | IBinder appToken = root.getDecorView().getApplicationWindowToken(); 23 | if (windowToken == appToken) { 24 | //means this window isn't contained by any other windows. 25 | return true; 26 | } 27 | } 28 | return false; 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/androidTest/res/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phw/Android-PwdHash/65d7cdf980b31eb05109699670cfefd74e475da3/app/src/androidTest/res/drawable/icon.png -------------------------------------------------------------------------------- /app/src/androidTest/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Password HashTest 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/uploadedlobster/PwdHash/activities/PwdHashApp.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * PwdHash, PwdHashApp.java 3 | * A password hash implementation for Android. 4 | * 5 | * Copyright (c) 2010 - 2013 Philipp Wolfer 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 3. Neither the name of the RBrainz project nor the names of the 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | * 31 | * @author Philipp Wolfer @gmail.com> 32 | */ 33 | package com.uploadedlobster.PwdHash.activities 34 | 35 | import android.app.Activity 36 | import android.content.ClipData 37 | import android.content.ClipboardManager 38 | import android.content.Intent 39 | import android.database.Cursor 40 | import android.os.Bundle 41 | import android.text.Editable 42 | import android.text.TextWatcher 43 | import android.util.DisplayMetrics 44 | import android.util.Log 45 | import android.view.View 46 | import android.view.WindowManager 47 | import android.widget.* 48 | import android.widget.SimpleCursorAdapter.CursorToStringConverter 49 | import com.uploadedlobster.PwdHash.R 50 | import com.uploadedlobster.PwdHash.algorithm.DomainExtractor.extractDomain 51 | import com.uploadedlobster.PwdHash.algorithm.HashedPassword.Companion.create 52 | import com.uploadedlobster.PwdHash.storage.HistoryDataSource 53 | import com.uploadedlobster.PwdHash.storage.HistoryOpenHelper 54 | import com.uploadedlobster.PwdHash.storage.UpdateHistoryTask 55 | import com.uploadedlobster.PwdHash.util.Preferences 56 | 57 | /** 58 | * @author Philipp Wolfer @gmail.com> 59 | */ 60 | class PwdHashApp : Activity() { 61 | private var mPreferences: Preferences? = null 62 | private var mHistory: HistoryDataSource? = null 63 | private var mSiteAddress: AutoCompleteTextView? = null 64 | private var mPassword: EditText? = null 65 | private var mHashedPassword: TextView? = null 66 | private var mCopyBtn: Button? = null 67 | private var mSaveStateOnExit = true 68 | 69 | /** Called when the activity is first created. */ 70 | public override fun onCreate(savedInstanceState: Bundle?) { 71 | super.onCreate(savedInstanceState) 72 | window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE) 73 | setContentView(R.layout.main) 74 | mSiteAddress = findViewById(R.id.siteAddress) as AutoCompleteTextView 75 | mPassword = findViewById(R.id.password) as EditText 76 | mHashedPassword = findViewById(R.id.hashedPassword) as TextView 77 | mCopyBtn = findViewById(R.id.copyBtn) as Button 78 | mPreferences = Preferences(this) 79 | mHistory = HistoryDataSource(this) 80 | setWindowGeometry() 81 | restoreSavedState() 82 | handleIntents() 83 | registerEventListeners() 84 | initAutoComplete() 85 | } 86 | 87 | override fun onStop() { 88 | super.onStop() 89 | if (mSaveStateOnExit) { 90 | mPreferences!!.savedSiteAddress = domain 91 | } else { 92 | mPreferences!!.savedSiteAddress = "" 93 | } 94 | } 95 | 96 | private fun setWindowGeometry() { 97 | val window = window 98 | window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT) 99 | val metrics = DisplayMetrics() 100 | windowManager.defaultDisplay.getMetrics(metrics) 101 | val maxWidth = resources.getDimensionPixelSize( 102 | R.dimen.maxWindowWidth) 103 | if (metrics.widthPixels > maxWidth) { 104 | window.setLayout(maxWidth, WindowManager.LayoutParams.WRAP_CONTENT) 105 | } 106 | } 107 | 108 | private fun restoreSavedState() { 109 | val savedSiteAddress = mPreferences!!.savedSiteAddress 110 | if (savedSiteAddress != "") { 111 | mSiteAddress!!.setText(savedSiteAddress) 112 | } 113 | } 114 | 115 | private fun handleIntents() { 116 | val intent = intent 117 | if (intent != null) { 118 | val action = intent.action 119 | if (action != null && action == Intent.ACTION_SEND) { 120 | var siteAddress = intent.getStringExtra(Intent.EXTRA_TEXT) 121 | if (siteAddress != null && siteAddress != "") { 122 | siteAddress = extractDomain(siteAddress) 123 | mSiteAddress!!.setText(siteAddress) 124 | mPassword!!.requestFocus() 125 | } 126 | } 127 | } 128 | } 129 | 130 | private fun initAutoComplete() { 131 | mHistory!!.open() 132 | val from = arrayOf(HistoryOpenHelper.COLUMN_REALM) 133 | val to = intArrayOf(android.R.id.text1) 134 | val adapter = SimpleCursorAdapter(this, 135 | android.R.layout.simple_dropdown_item_1line, null, from, to, 0) 136 | 137 | // Set the CursorToStringConverter, to provide the labels for the 138 | // choices to be displayed in the AutoCompleteTextView. 139 | adapter.cursorToStringConverter = CursorToStringConverter { cursor: Cursor -> 140 | val columnIndex = cursor.getColumnIndexOrThrow(HistoryOpenHelper.COLUMN_REALM) 141 | cursor.getString(columnIndex) 142 | } 143 | 144 | // Set the FilterQueryProvider, to run queries for choices 145 | // that match the specified input. 146 | adapter.filterQueryProvider = FilterQueryProvider { constraint -> 147 | val partialInput = constraint?.toString() ?: "" 148 | mHistory!!.getHistoryCursor(partialInput) 149 | } 150 | mSiteAddress!!.setAdapter(adapter) 151 | } 152 | 153 | private fun registerEventListeners() { 154 | val updatePasswordTextWatcher: TextWatcher = object : TextWatcher { 155 | override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { 156 | val realm = domain 157 | val password = mPassword!!.text.toString() 158 | updateHashedPassword(realm, password) 159 | } 160 | 161 | override fun beforeTextChanged( 162 | s: CharSequence, start: Int, count: Int, 163 | after: Int 164 | ) { 165 | } 166 | 167 | override fun afterTextChanged(s: Editable) {} 168 | } 169 | mSiteAddress!!.addTextChangedListener(updatePasswordTextWatcher) 170 | mPassword!!.addTextChangedListener(updatePasswordTextWatcher) 171 | mCopyBtn!!.setOnClickListener { 172 | val realm = domain 173 | val password = mPassword!!.text.toString() 174 | if (realm == "") { 175 | mSiteAddress!!.requestFocus() 176 | } else if (password == "") { 177 | mPassword!!.requestFocus() 178 | } else { 179 | val hashedPassword = updateHashedPassword(realm, password) 180 | if (hashedPassword != "") { 181 | UpdateHistoryTask(mHistory!!).execute(realm) 182 | copyToClipboard(hashedPassword) 183 | val clipboardNotification: CharSequence = 184 | getString(R.string.copiedToClipboardNotification) 185 | showNotification(clipboardNotification) 186 | mSaveStateOnExit = false 187 | finish() 188 | } 189 | } 190 | } 191 | } 192 | 193 | private val domain: String get() = extractDomain(mSiteAddress!!.text.toString()) 194 | 195 | private fun updateHashedPassword(realm: String, password: String): String { 196 | var result = "" 197 | if (realm != "" && password != "") { 198 | val hashedPassword = create(password, realm) 199 | result = hashedPassword.toString() 200 | } 201 | mCopyBtn!!.isEnabled = result != "" 202 | mHashedPassword!!.text = result 203 | return result 204 | } 205 | 206 | private fun showNotification(text: CharSequence) { 207 | val duration = Toast.LENGTH_LONG 208 | val toast = Toast.makeText(this, text, duration) 209 | toast.show() 210 | } 211 | 212 | private fun copyToClipboard(hashedPassword: String) { 213 | try { 214 | val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager 215 | val clip = ClipData.newPlainText("", hashedPassword) 216 | clipboard.setPrimaryClip(clip) 217 | } catch (e: IllegalStateException) { 218 | // Workaround for some Android 4.3 devices, where writing to the clipboard manager raises an exception 219 | // if there is an active clipboard listener. 220 | Log.w("PwdHashApp", "IllegalStateException raised when accessing clipboard.") 221 | } 222 | } 223 | } -------------------------------------------------------------------------------- /app/src/main/java/com/uploadedlobster/PwdHash/algorithm/DomainExtractor.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * PwdHash, DomainExtractor.java 3 | * A password hash implementation for Android. 4 | * 5 | * Copyright (c) 2010 Philipp Wolfer 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 3. Neither the name of the RBrainz project nor the names of the 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | * 31 | * @author Philipp Wolfer @gmail.com> 32 | */ 33 | package com.uploadedlobster.PwdHash.algorithm 34 | 35 | /** 36 | * Domain name extractor. 37 | * 38 | * Turns host names into domain names. Based on original JavaScript code from: 39 | * https://www.pwdhash.com/ 40 | * 41 | * @author Philipp Wolfer @gmail.com> 42 | */ 43 | object DomainExtractor { 44 | @JvmStatic 45 | @Throws(IllegalArgumentException::class) 46 | fun extractDomain(uri: String?): String { 47 | requireNotNull(uri) { "Argument uri must not be null." } 48 | var domain: String = uri 49 | domain = domain.replace("http://", "") 50 | domain = domain.replace("https://", "") 51 | var firstSlash: Int 52 | if (domain.indexOf("/").also { firstSlash = it } > -1) domain = 53 | domain.substring(0, firstSlash) 54 | val parts = domain.split("\\.".toRegex()).toTypedArray() 55 | if (parts.size > 2) { 56 | domain = parts[parts.size - 2] + "." + parts[parts.size - 1] 57 | for (sld in mSecondLevelDomains) { 58 | if (domain == sld) domain = parts[parts.size - 3] + "." + domain 59 | } 60 | } 61 | return domain 62 | } 63 | 64 | private val mSecondLevelDomains = arrayOf("ab.ca", "ac.ac", "ac.at", 65 | "ac.be", "ac.cn", "ac.il", "ac.in", "ac.jp", "ac.kr", "ac.nz", 66 | "ac.th", "ac.uk", "ac.za", "adm.br", "adv.br", "agro.pl", "ah.cn", 67 | "aid.pl", "alt.za", "am.br", "arq.br", "art.br", "arts.ro", 68 | "asn.au", "asso.fr", "asso.mc", "atm.pl", "auto.pl", "bbs.tr", 69 | "bc.ca", "bio.br", "biz.pl", "bj.cn", "br.com", "cn.com", "cng.br", 70 | "cnt.br", "co.ac", "co.at", "co.il", "co.in", "co.jp", "co.kr", 71 | "co.nz", "co.th", "co.uk", "co.za", "com.au", "com.br", "com.cn", 72 | "com.ec", "com.fr", "com.hk", "com.mm", "com.mx", "com.pl", 73 | "com.ro", "com.ru", "com.sg", "com.tr", "com.tw", "cq.cn", 74 | "cri.nz", "de.com", "ecn.br", "edu.au", "edu.cn", "edu.hk", 75 | "edu.mm", "edu.mx", "edu.pl", "edu.tr", "edu.za", "eng.br", 76 | "ernet.in", "esp.br", "etc.br", "eti.br", "eu.com", "eu.lv", 77 | "fin.ec", "firm.ro", "fm.br", "fot.br", "fst.br", "g12.br", 78 | "gb.com", "gb.net", "gd.cn", "gen.nz", "gmina.pl", "go.jp", 79 | "go.kr", "go.th", "gob.mx", "gov.br", "gov.cn", "gov.ec", "gov.il", 80 | "gov.in", "gov.mm", "gov.mx", "gov.sg", "gov.tr", "gov.za", 81 | "govt.nz", "gs.cn", "gsm.pl", "gv.ac", "gv.at", "gx.cn", "gz.cn", 82 | "hb.cn", "he.cn", "hi.cn", "hk.cn", "hl.cn", "hn.cn", "hu.com", 83 | "idv.tw", "ind.br", "inf.br", "info.pl", "info.ro", "iwi.nz", 84 | "jl.cn", "jor.br", "jpn.com", "js.cn", "k12.il", "k12.tr", 85 | "lel.br", "ln.cn", "ltd.uk", "mail.pl", "maori.nz", "mb.ca", 86 | "me.uk", "med.br", "med.ec", "media.pl", "mi.th", "miasta.pl", 87 | "mil.br", "mil.ec", "mil.nz", "mil.pl", "mil.tr", "mil.za", 88 | "mo.cn", "muni.il", "nb.ca", "ne.jp", "ne.kr", "net.au", "net.br", 89 | "net.cn", "net.ec", "net.hk", "net.il", "net.in", "net.mm", 90 | "net.mx", "net.nz", "net.pl", "net.ru", "net.sg", "net.th", 91 | "net.tr", "net.tw", "net.za", "nf.ca", "ngo.za", "nm.cn", "nm.kr", 92 | "no.com", "nom.br", "nom.pl", "nom.ro", "nom.za", "ns.ca", "nt.ca", 93 | "nt.ro", "ntr.br", "nx.cn", "odo.br", "on.ca", "or.ac", "or.at", 94 | "or.jp", "or.kr", "or.th", "org.au", "org.br", "org.cn", "org.ec", 95 | "org.hk", "org.il", "org.mm", "org.mx", "org.nz", "org.pl", 96 | "org.ro", "org.ru", "org.sg", "org.tr", "org.tw", "org.uk", 97 | "org.za", "pc.pl", "pe.ca", "plc.uk", "ppg.br", "presse.fr", 98 | "priv.pl", "pro.br", "psc.br", "psi.br", "qc.ca", "qc.com", 99 | "qh.cn", "re.kr", "realestate.pl", "rec.br", "rec.ro", "rel.pl", 100 | "res.in", "ru.com", "sa.com", "sc.cn", "school.nz", "school.za", 101 | "se.com", "se.net", "sh.cn", "shop.pl", "sk.ca", "sklep.pl", 102 | "slg.br", "sn.cn", "sos.pl", "store.ro", "targi.pl", "tj.cn", 103 | "tm.fr", "tm.mc", "tm.pl", "tm.ro", "tm.za", "tmp.br", 104 | "tourism.pl", "travel.pl", "tur.br", "turystyka.pl", "tv.br", 105 | "tw.cn", "uk.co", "uk.com", "uk.net", "us.com", "uy.com", "vet.br", 106 | "web.za", "web.com", "www.ro", "xj.cn", "xz.cn", "yk.ca", "yn.cn", 107 | "za.com") 108 | } -------------------------------------------------------------------------------- /app/src/main/java/com/uploadedlobster/PwdHash/algorithm/HashedPassword.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * PwdHash, HashedPassword.java 3 | * A password hash implementation for Android. 4 | * 5 | * Copyright (c) 2010 Philipp Wolfer 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 3. Neither the name of the RBrainz project nor the names of the 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | * 31 | * @author Philipp Wolfer @gmail.com> 32 | */ 33 | package com.uploadedlobster.PwdHash.algorithm 34 | 35 | import android.util.Base64 36 | import android.util.Log 37 | import java.io.UnsupportedEncodingException 38 | import java.security.InvalidKeyException 39 | import java.security.Key 40 | import java.security.NoSuchAlgorithmException 41 | import java.util.* 42 | import java.util.regex.Pattern 43 | import javax.crypto.Mac 44 | import javax.crypto.spec.SecretKeySpec 45 | 46 | /** 47 | * Hashed Password 48 | * 49 | * Combination of page URI and plain text password. Treated as a string, it is 50 | * the hashed password. Based on original JavaScript code from: 51 | * https://www.pwdhash.com/ 52 | * 53 | * @author Philipp Wolfer @gmail.com> 54 | */ 55 | class HashedPassword private constructor( 56 | private val mPassword: String, 57 | private val mRealm: String 58 | ) { 59 | private var mHash: String? = null 60 | private var mExtras: Queue? = null 61 | override fun toString(): String { 62 | return mHash!! 63 | } 64 | 65 | private fun calculateHash() { 66 | val md5 = createHmacMD5(mPassword, mRealm) 67 | val hash = Base64.encodeToString(md5, Base64.NO_PADDING 68 | or Base64.NO_WRAP) 69 | val size = mPassword.length + PasswordPrefix.length 70 | val nonAlphanumeric = NonAlphanumericMatcher.matcher(mPassword) 71 | .find() 72 | mHash = applyConstraints(hash, size, nonAlphanumeric) 73 | } 74 | 75 | private fun applyConstraints( 76 | hash: String, size: Int, 77 | nonAlphanumeric: Boolean 78 | ): String { 79 | var startingSize = size - 4 80 | if (startingSize < 0) startingSize = 0 else if (startingSize > hash.length) startingSize = 81 | hash.length 82 | var result = hash.substring(0, startingSize) 83 | mExtras = LinkedList() 84 | for (c in hash.substring(startingSize).toCharArray()) { 85 | mExtras!!.add(c) 86 | } 87 | 88 | // Add the extra characters 89 | result += if (Pattern.compile("[A-Z]").matcher(result) 90 | .find() 91 | ) nextExtraChar() else nextBetween('A', 26) 92 | result += if (Pattern.compile("[a-z]").matcher(result) 93 | .find() 94 | ) nextExtraChar() else nextBetween('a', 26) 95 | result += if (Pattern.compile("[0-9]").matcher(result) 96 | .find() 97 | ) nextExtraChar() else nextBetween('0', 10) 98 | result += if (NonAlphanumericMatcher.matcher(result).find() 99 | && nonAlphanumeric 100 | ) nextExtraChar() else '+' 101 | while (NonAlphanumericMatcher.matcher(result).find() 102 | && !nonAlphanumeric 103 | ) { 104 | val replacement = Character.toString(nextBetween('A', 26)) 105 | result = NonAlphanumericMatcher.matcher(result).replaceFirst( 106 | replacement) 107 | } 108 | 109 | // For long passwords (about > 22 chars) the password might be longer 110 | // than the hash and mExtras is empty. In that case the constraints 111 | // above produce 0 bytes at the end of result. If nonAlphanumeric is not 112 | // set those 0 bytes are replaced, but in other cases they stay around 113 | // and must be removed here. This is a flaw in the original algorithm 114 | // which we have to work around here. 115 | result = result.replace("\u0000", "") 116 | 117 | // Rotate the result to make it harder to guess the inserted locations 118 | return rotate(result, nextExtra()) 119 | } 120 | 121 | private fun nextExtra(): Int { 122 | return nextExtraChar().toInt() 123 | } 124 | 125 | private fun nextExtraChar(): Char { 126 | return if (mExtras!!.size > 0) mExtras!!.remove() else '0' 127 | } 128 | 129 | private fun nextBetween(base: Char, interval: Int): Char { 130 | return between(base.toInt(), interval, nextExtra()).toChar() 131 | } 132 | 133 | companion object { 134 | private const val HMAC_MD5 = "HmacMD5" 135 | private const val PasswordPrefix = "@@" 136 | 137 | /** 138 | * Pattern to match only word characters. Since Java's regex implementation 139 | * is unicode aware, the pattern \W would match also non-ASCII word 140 | * characters. But since the JavaScript implementation of PwdHash only 141 | * considers ASCII characters we stay compatible. 142 | */ 143 | private val NonAlphanumericMatcher = Pattern 144 | .compile("[^a-zA-Z0-9_]") 145 | 146 | @JvmStatic 147 | fun create(password: String, realm: String): HashedPassword { 148 | val result = HashedPassword(password, realm) 149 | result.calculateHash() 150 | return result 151 | } 152 | 153 | private fun createHmacMD5(key: String?, data: String?): ByteArray { 154 | require(!(key == null || key == "")) { "key must not be null or empty" } 155 | requireNotNull(data) { "data must not be null" } 156 | val keyBytes = encodeStringToBytes(key) 157 | val dataBytes = encodeStringToBytes(data) 158 | return try { 159 | val mac = Mac.getInstance(HMAC_MD5) 160 | val sk: Key = SecretKeySpec(keyBytes, HMAC_MD5) 161 | mac.init(sk) 162 | mac.doFinal(dataBytes) 163 | } catch (e: NoSuchAlgorithmException) { 164 | Log.e(HashedPassword::class.java.name, 165 | "HMAC_MD5 algorithm not supported on this platform.", e) 166 | ByteArray(0) 167 | } catch (e: InvalidKeyException) { 168 | Log.e(HashedPassword::class.java.name, "Invalid secret key.", e) 169 | ByteArray(0) 170 | } 171 | } 172 | 173 | /** 174 | * Returns a new byte array with the encoded input string (1 byte per 175 | * character). 176 | * 177 | * Characters in the Latin 1 range (up to code point 255) will be returned 178 | * as Latin 1 encoded bytes. Characters above code point 255 will be 179 | * UTF-16le encoded but only the first byte will be used. 180 | * 181 | * This matches the original behavior of the PwdHash JavaScript 182 | * implementation pwdhash.com and keeps the hash values of passwords 183 | * containing non-latin1 characters compatible. 184 | * 185 | * @param data Input string 186 | * @return Byte array. 187 | */ 188 | private fun encodeStringToBytes(data: String): ByteArray { 189 | val bytes = ByteArray(data.length) 190 | for (i in 0 until data.length) { 191 | val codePoint = data.codePointAt(i) 192 | if (codePoint <= 255) bytes[i] = codePoint.toByte() else { 193 | try { 194 | val nonLatin1Char = Character.toString(data[i]) 195 | val charBytes = nonLatin1Char.toByteArray(charset("UTF-16le")) 196 | val unsignedByte = (0x000000FF and charBytes[0] 197 | .toInt()).toShort() 198 | bytes[i] = unsignedByte.toByte() 199 | } catch (e: UnsupportedEncodingException) { 200 | Log.w("Decoding error", Character.toString(data[i]) 201 | + " could not be decoded as UTF-16le") 202 | bytes[i] = 0x1A // SUB 203 | } 204 | } 205 | } 206 | return bytes 207 | } 208 | 209 | private fun between(min: Int, interval: Int, offset: Int): Int { 210 | return min + offset % interval 211 | } 212 | 213 | private fun rotate(s: String, amount: Int): String { 214 | var amount = amount 215 | val work: Queue = LinkedList() 216 | for (c in s.toCharArray()) { 217 | work.add(c) 218 | } 219 | while (amount-- > 0) work.add(work.remove()) 220 | val b = StringBuilder(work.size) 221 | for (c in work) b.append(c) 222 | return b.toString() 223 | } 224 | } 225 | } -------------------------------------------------------------------------------- /app/src/main/java/com/uploadedlobster/PwdHash/storage/HistoryDataSource.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * PwdHash, HistoryStorage.java 3 | * A password hash implementation for Android. 4 | * 5 | * Copyright (c) 2012 Philipp Wolfer 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 3. Neither the name of the RBrainz project nor the names of the 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | * 31 | * @author Philipp Wolfer @gmail.com> 32 | */ 33 | package com.uploadedlobster.PwdHash.storage 34 | 35 | import android.content.Context 36 | import android.database.Cursor 37 | import android.database.SQLException 38 | import android.database.sqlite.SQLiteDatabase 39 | 40 | class HistoryDataSource(context: Context?) { 41 | private var mDatabase: SQLiteDatabase? = null 42 | private val mDbHelper: HistoryOpenHelper = HistoryOpenHelper(context) 43 | 44 | @Throws(SQLException::class) 45 | fun open() { 46 | mDatabase = mDbHelper.writableDatabase 47 | } 48 | 49 | fun close() { 50 | mDbHelper.close() 51 | } 52 | 53 | fun insertHistoryEntry(realm: String) { 54 | val id = getExistingEntryId(realm) 55 | val values = arrayOf( 56 | if (id < 0) null else id.toString(), 57 | realm, realm) 58 | val sql = ("INSERT OR REPLACE INTO history (" 59 | + HistoryOpenHelper.Companion.COLUMN_ID + ", " 60 | + HistoryOpenHelper.Companion.COLUMN_REALM + ", " 61 | + HistoryOpenHelper.Companion.COLUMN_USAGE_COUNT + ", " 62 | + HistoryOpenHelper.Companion.COLUMN_LAST_ACCESS + ") " 63 | + "VALUES (?, ?, " 64 | + "(SELECT " 65 | + HistoryOpenHelper.Companion.COLUMN_USAGE_COUNT 66 | + " + 1 FROM history WHERE " 67 | + HistoryOpenHelper.Companion.COLUMN_REALM 68 | + " = ?), datetime('now'))") 69 | mDatabase!!.execSQL(sql, values) 70 | } 71 | 72 | fun getHistoryCursor(partialRealm: String): Cursor { 73 | val columns = arrayOf(HistoryOpenHelper.Companion.COLUMN_ID, 74 | HistoryOpenHelper.COLUMN_REALM) 75 | val selection: String = HistoryOpenHelper.Companion.COLUMN_REALM + " LIKE ?" 76 | val selectionArgs = arrayOf("%$partialRealm%") 77 | val orderBy: String = (HistoryOpenHelper.Companion.COLUMN_USAGE_COUNT 78 | + " DESC, " 79 | + HistoryOpenHelper.Companion.COLUMN_LAST_ACCESS 80 | + " DESC") 81 | val limit = SUGGESTION_LIMIT.toString() 82 | return mDatabase!!.query( 83 | HistoryOpenHelper.Companion.TABLE_HISTORY, 84 | columns, 85 | selection, 86 | selectionArgs, 87 | "", "", 88 | orderBy, 89 | limit) 90 | } 91 | 92 | private fun getExistingEntryId(realm: String): Int { 93 | val columns = arrayOf(HistoryOpenHelper.Companion.COLUMN_ID) 94 | val selection: String = HistoryOpenHelper.Companion.COLUMN_REALM + " LIKE ?" 95 | val selectionArgs = arrayOf(realm) 96 | val cursor = mDatabase!!.query(HistoryOpenHelper.Companion.TABLE_HISTORY, 97 | columns, 98 | selection, 99 | selectionArgs, 100 | "", 101 | "", 102 | "") 103 | return if (cursor.moveToFirst()) { 104 | val idColumn = cursor.getColumnIndex(HistoryOpenHelper.Companion.COLUMN_ID) 105 | val id = cursor.getInt(idColumn) 106 | cursor.close() 107 | id 108 | } else { 109 | -1 110 | } 111 | } 112 | 113 | companion object { 114 | private const val SUGGESTION_LIMIT = 6 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /app/src/main/java/com/uploadedlobster/PwdHash/storage/HistoryOpenHelper.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * PwdHash, HistoryOpenHelper.java 3 | * A password hash implementation for Android. 4 | * 5 | * Copyright (c) 2012 Philipp Wolfer 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 3. Neither the name of the RBrainz project nor the names of the 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | * 31 | * @author Philipp Wolfer @gmail.com> 32 | */ 33 | package com.uploadedlobster.PwdHash.storage 34 | 35 | import android.content.Context 36 | import android.database.sqlite.SQLiteDatabase 37 | import android.database.sqlite.SQLiteOpenHelper 38 | import com.uploadedlobster.PwdHash.util.Constants 39 | 40 | class HistoryOpenHelper internal constructor(context: Context?) : 41 | SQLiteOpenHelper(context, Constants.DATABASE_NAME, null, Constants.DATABASE_VERSION) { 42 | override fun onCreate(db: SQLiteDatabase) { 43 | db.execSQL(CREATE_TABLE) 44 | } 45 | 46 | override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {} 47 | 48 | companion object { 49 | const val TABLE_HISTORY = "history" 50 | const val COLUMN_ID = "_id" 51 | const val COLUMN_REALM = "realm" 52 | const val COLUMN_USAGE_COUNT = "usage_count" 53 | const val COLUMN_LAST_ACCESS = "last_access" 54 | private const val CREATE_TABLE = "CREATE TABLE " + TABLE_HISTORY + " (" + 55 | COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 56 | COLUMN_REALM + " TEXT," + 57 | COLUMN_USAGE_COUNT + " INTEGER NOT NULL DEFAULT 0," + 58 | COLUMN_LAST_ACCESS + " TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP)" 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/uploadedlobster/PwdHash/storage/UpdateHistoryTask.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * PwdHash, UpdateHistoryTask.java 3 | * A password hash implementation for Android. 4 | * 5 | * Copyright (c) 2012 Philipp Wolfer 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 3. Neither the name of the RBrainz project nor the names of the 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | * 31 | * @author Philipp Wolfer @gmail.com> 32 | */ 33 | package com.uploadedlobster.PwdHash.storage 34 | 35 | import android.os.AsyncTask 36 | 37 | class UpdateHistoryTask(private val mDataSource: HistoryDataSource) : 38 | AsyncTask() { 39 | override fun doInBackground(vararg params: String?): Void? { 40 | for (realm in params) { 41 | if (realm != null) { 42 | mDataSource.insertHistoryEntry(realm) 43 | } 44 | } 45 | return null 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/uploadedlobster/PwdHash/util/Constants.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * PwdHash, Constants.java 3 | * A password hash implementation for Android. 4 | * 5 | * Copyright (c) 2012 Philipp Wolfer 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 3. Neither the name of the RBrainz project nor the names of the 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | * 31 | * @author Philipp Wolfer @gmail.com> 32 | */ 33 | package com.uploadedlobster.PwdHash.util 34 | 35 | object Constants { 36 | const val PREFERENCES_NAME = "com.uploadedlobster.pwdhash.preferences" 37 | const val PREFERENCE_SAVED_SITE_ADDRESS = "saved_uri" 38 | const val DATABASE_NAME = "pwdhash" 39 | const val DATABASE_VERSION = 1 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/uploadedlobster/PwdHash/util/Preferences.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * PwdHash, Preferences.java 3 | * A password hash implementation for Android. 4 | * 5 | * Copyright (c) 2012 Philipp Wolfer 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 3. Neither the name of the RBrainz project nor the names of the 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | * 31 | * @author Philipp Wolfer @gmail.com> 32 | */ 33 | package com.uploadedlobster.PwdHash.util 34 | 35 | import android.content.Context 36 | import android.content.SharedPreferences 37 | 38 | class Preferences(packageContext: Context) { 39 | private val mSettings: SharedPreferences = packageContext.getSharedPreferences(Constants.PREFERENCES_NAME, Context.MODE_PRIVATE) 40 | 41 | var savedSiteAddress: String? 42 | get() = mSettings.getString(Constants.PREFERENCE_SAVED_SITE_ADDRESS, "") 43 | set(siteAddress) { 44 | val editor = mSettings.edit() 45 | editor.putString(Constants.PREFERENCE_SAVED_SITE_ADDRESS, siteAddress) 46 | editor.apply() 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phw/Android-PwdHash/65d7cdf980b31eb05109699670cfefd74e475da3/app/src/main/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phw/Android-PwdHash/65d7cdf980b31eb05109699670cfefd74e475da3/app/src/main/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phw/Android-PwdHash/65d7cdf980b31eb05109699670cfefd74e475da3/app/src/main/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phw/Android-PwdHash/65d7cdf980b31eb05109699670cfefd74e475da3/app/src/main/res/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phw/Android-PwdHash/65d7cdf980b31eb05109699670cfefd74e475da3/app/src/main/res/drawable-xxhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phw/Android-PwdHash/65d7cdf980b31eb05109699670cfefd74e475da3/app/src/main/res/drawable-xxxhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | 18 | 19 | 26 | 27 | 32 | 33 | 40 | 41 | 46 | 47 | 53 | 54 | 60 | 61 | 62 | 67 | 68 |