CVE-2024-39846 (published 2024-06-29T21:15Z)

NewPass before 1.2.0 stores passwords (rather than password hashes) directly, which makes it easier to obtain unauthorized access to sensitive information. NOTE: in each case, data at rest is encrypted, but is decrypted within process memory during use.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/6eero/NewPass
commit_details: repo_url: https://github.com/6eero/NewPass
author: gero
author_date: 2024-05-31 22:17:46+02:00
msg: Feature: Store password hash in encrypted shared preferences

Previously, the database encryption password (used for login as well and created during the first launch) was directly stored encrypted in EncryptedSharedPreferences. With this commit, when a user inputs the password to create the user during the first launch, the password is hashed using PBKDF2WithHmacSHA256, and the resulting hash is stored in encrypted shared preferences.

Then, every time the user inputs the known password to login, the password is hashed using the same algorithm, the same number of iterations, and is confronted with the (hashed) password retrieved from the EncryptedSharedPreferences.

This improves security by ensuring that the actual password is not stored directly, but rather its hash, making it more difficult for unauthorized access to obtain sensitive information.
repository: repo_url: https://github.com/6eero/NewPass
repo_name: 6eero/NewPass
description: 🔐 NewPass is a free and open source password manager which will allow you to generate and store your passwords securely, saving them locally and encrypting them on your phone's memory
repo_language: Java
owner: 6eero
file_changes: filename: PasswordUtils.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,61 @@
+package com.gero.newpass.encryption;
+
+import android.util.Base64;
+import android.util.Log;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+public class PasswordUtils {
+
+    private static final int SALT_LENGTH = 16;   // in bytes (at least 16 bytes)
+    private static final int HASH_LENGTH = 256; // in bits
+    private static final int ITERATIONS = 10000;
+
+    public static String hashPassword(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
+
+        SecureRandom random = new SecureRandom();
+        byte[] salt = new byte[SALT_LENGTH];
+        random.nextBytes(salt);
+
+
+        PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, HASH_LENGTH);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
+        byte[] hash = keyFactory.generateSecret(spec).getEncoded();
+
+        String saltBase64 = Base64.encodeToString(salt, Base64.NO_WRAP);
+        String hashBase64 = Base64.encodeToString(hash, Base64.NO_WRAP);
+
+        return saltBase64 + ":" + hashBase64;
+    }
+
+    public static boolean verifyPassword(String password, String storedPassword) throws NoSuchAlgorithmException, InvalidKeySpecException {
+
+        String[] parts = storedPassword.split(":");
+
+        if (parts.length != 2) {
+            throw new IllegalArgumentException("Stored password must have the format 'salt:hash'");
+        }
+
+        String saltBase64 = parts[0];
+        String hashBase64 = parts[1];
+
+        byte[] salt = Base64.decode(saltBase64, Base64.NO_WRAP);
+        byte[] hash = Base64.decode(hashBase64, Base64.NO_WRAP);
+
+        /*
+        The password provided by the user during the login attempt is hashed using the same algorithm,
+        the same number of iterations and the same salt retrieved from the database.
+         */
+        PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, hash.length * 8);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
+        byte[] testHash = keyFactory.generateSecret(spec).getEncoded();
+
+        return java.util.Arrays.equals(hash, testHash);
+    }
+}

programming_language: Java
filename: LoginActivity.java
change_type: ModificationType.MODIFY
diff: @@ -39,6 +39,8 @@ import com.gero.newpass.utilities.SystemBarColorHelper;
 import com.gero.newpass.utilities.VibrationHelper;
 import com.gero.newpass.viewmodel.LoginViewModel;
 
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
 import java.util.Locale;
 
 public class LoginActivity extends AppCompatActivity {
@@ -73,11 +75,11 @@ public class LoginActivity extends AppCompatActivity {
         });
 
         loginViewModel.getLoginSuccessLiveData().observe(this, success -> {
-            String savedPasswordSharedPreferences = encryptedSharedPreferences.getString("password", "");
+            String hashedPassword = encryptedSharedPreferences.getString("password", "");
 
             if (success) {
                 Intent intent = new Intent(LoginActivity.this, MainViewActivity.class);
-                StringHelper.setSharedString(savedPasswordSharedPreferences);
+                StringHelper.setSharedString(hashedPassword);
                 startActivity(intent);
                 finish();
             } else {
@@ -90,8 +92,8 @@ public class LoginActivity extends AppCompatActivity {
         //Determining whether to set dark or light mode based on shared preferences
         SharedPreferencesHelper.toggleDarkLightModeUI(this);
 
-        String password = encryptedSharedPreferences.getString("password", "");
-        Boolean isPasswordEmpty = password.isEmpty();
+        String hashedPassword = encryptedSharedPreferences.getString("password", "");
+        Boolean isPasswordEmpty = hashedPassword.isEmpty();
 
         if (!isPasswordEmpty) {
             textViewRegisterOrUnlock.setText(getString(R.string.unlock_newpass_button_text));
@@ -179,7 +181,11 @@ public class LoginActivity extends AppCompatActivity {
 
         buttonRegisterOrUnlock.setOnClickListener(v -> {
             String passwordInput = passwordEntry.getText().toString();
-            loginViewModel.createUser(passwordInput, encryptedSharedPreferences);
+            try {
+                loginViewModel.createUser(passwordInput, encryptedSharedPreferences);
+            } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+                throw new RuntimeException(e);
+            }
             VibrationHelper.vibrate(v, VibrationHelper.VibrationType.Strong);
         });
     }
@@ -213,7 +219,11 @@ public class LoginActivity extends AppCompatActivity {
                     return true;
                 case MotionEvent.ACTION_UP:
                     v.performClick();
-                    loginViewModel.loginUserWithPassword(passwordInput, encryptedSharedPreferences);
+                    try {
+                        loginViewModel.loginUserWithPassword(passwordInput, encryptedSharedPreferences);
+                    } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+                        throw new RuntimeException(e);
+                    }
                     VibrationHelper.vibrate(v, VibrationHelper.VibrationType.Strong);
                     return true;
             }

programming_language: Java
filename: LoginViewModel.java
change_type: ModificationType.MODIFY
diff: @@ -15,8 +15,11 @@ import androidx.lifecycle.ViewModel;
 import androidx.security.crypto.EncryptedSharedPreferences;
 
 import com.gero.newpass.R;
+import com.gero.newpass.encryption.PasswordUtils;
 import com.gero.newpass.repository.ResourceRepository;
 
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
 import java.util.concurrent.Executor;
 
 public class LoginViewModel extends ViewModel {
@@ -40,11 +43,11 @@ public class LoginViewModel extends ViewModel {
 
 
 
-    public void loginUserWithPassword(String password, EncryptedSharedPreferences sharedPreferences) {
+    public void loginUserWithPassword(String password, EncryptedSharedPreferences sharedPreferences) throws NoSuchAlgorithmException, InvalidKeySpecException {
 
-        String savedPasswordSharedPreferences = sharedPreferences.getString("password", "");
+        String hashedPassword = sharedPreferences.getString("password", "");
 
-        if (savedPasswordSharedPreferences.equals(password)) {
+        if (PasswordUtils.verifyPassword(password, hashedPassword)) {
             loginSuccessLiveData.setValue(true);
             loginMessageLiveData.setValue(resourceRepository.getString(R.string.login_done));
         } else {
@@ -88,11 +91,13 @@ public class LoginViewModel extends ViewModel {
     }
 
 
-    public void createUser(String password, EncryptedSharedPreferences sharedPreferences) {
+    public void createUser(String password, EncryptedSharedPreferences sharedPreferences) throws NoSuchAlgorithmException, InvalidKeySpecException {
 
         if (password.length() >= 4) {
             SharedPreferences.Editor editor = sharedPreferences.edit();
-            editor.putString("password", password);
+
+            String hashedPassword = PasswordUtils.hashPassword(password);
+            editor.putString("password", hashedPassword);
             editor.apply();
 
             loginSuccessLiveData.setValue(true);

programming_language: Java