11package dev.pranav.applock.core.utils
22
33import android.util.Base64
4+ import dev.pranav.applock.core.utils.SecurityUtils.MAX_PASSWORD_LENGTH
45import java.security.MessageDigest
56import java.security.SecureRandom
67
78object SecurityUtils {
89
910 private const val HASH_ALGORITHM = " SHA-256"
1011 private const val SALT_LENGTH = 16
11- private const val MAX_PASSWORD_LENGTH = 64
12+ const val MAX_PASSWORD_LENGTH = 64
1213
1314 /* *
1415 * Sanitizes the input string by filtering out control characters and null bytes.
1516 * Also limits the length to [MAX_PASSWORD_LENGTH].
1617 */
1718 fun sanitizePassword (input : String ): String {
18- return input.filter { it.code >= 32 && it.code != 127 }
19+ return input
20+ .filter { it.code >= 32 && it.code != 127 }
1921 .take(MAX_PASSWORD_LENGTH )
2022 }
2123
2224 /* *
2325 * Generates a random cryptographic salt.
2426 */
2527 fun generateSalt (): ByteArray {
26- val random = SecureRandom ()
27- val salt = ByteArray (SALT_LENGTH )
28- random.nextBytes(salt)
29- return salt
28+ return ByteArray (SALT_LENGTH ).also { salt ->
29+ SecureRandom ().nextBytes(salt)
30+ }
31+ }
32+
33+ fun hashPassword (password : String ): String {
34+ return hashPassword(password, generateSalt())
3035 }
3136
3237 /* *
@@ -35,35 +40,51 @@ object SecurityUtils {
3540 * Format: salt:hash
3641 */
3742 fun hashPassword (password : String , salt : ByteArray ): String {
43+ val sanitizedPassword = sanitizePassword(password)
44+
3845 val md = MessageDigest .getInstance(HASH_ALGORITHM )
3946 md.update(salt)
40- val hashedPassword = md.digest(password .toByteArray(Charsets .UTF_8 ))
41-
47+ val hash = md.digest(sanitizedPassword .toByteArray(Charsets .UTF_8 ))
48+
4249 val saltBase64 = Base64 .encodeToString(salt, Base64 .NO_WRAP )
43- val hashBase64 = Base64 .encodeToString(hashedPassword , Base64 .NO_WRAP )
44-
50+ val hashBase64 = Base64 .encodeToString(hash , Base64 .NO_WRAP )
51+
4552 return " $saltBase64 :$hashBase64 "
4653 }
4754
55+ fun isSaltedHash (value : String ): Boolean {
56+ val parts = value.split(" :" )
57+ if (parts.size != 2 ) return false
58+
59+ return try {
60+ Base64 .decode(parts[0 ], Base64 .NO_WRAP )
61+ Base64 .decode(parts[1 ], Base64 .NO_WRAP )
62+ true
63+ } catch (_: Exception ) {
64+ false
65+ }
66+ }
67+
4868 /* *
4969 * Verifies an input password against a stored salted hash string.
5070 */
5171 fun verifyPassword (inputPassword : String , storedSaltedHash : String ): Boolean {
5272 return try {
5373 val parts = storedSaltedHash.split(" :" )
5474 if (parts.size != 2 ) return false
55-
56- val salt = Base64 .decode(parts[0 ], Base64 .DEFAULT )
57- val storedHash = parts[1 ]
58-
75+
76+ val salt = Base64 .decode(parts[0 ], Base64 .NO_WRAP )
77+ val expectedHash = Base64 .decode(parts[1 ], Base64 .NO_WRAP )
78+
79+ val sanitizedInput = sanitizePassword(inputPassword)
80+
5981 val md = MessageDigest .getInstance(HASH_ALGORITHM )
6082 md.update(salt)
61- val inputHashed = md.digest(inputPassword.toByteArray(Charsets .UTF_8 ))
62- val inputHashBase64 = Base64 .encodeToString(inputHashed, Base64 .NO_WRAP )
63-
64- inputHashBase64 == storedHash
65- } catch (e: Exception ) {
83+ val actualHash = md.digest(sanitizedInput.toByteArray(Charsets .UTF_8 ))
84+
85+ MessageDigest .isEqual(actualHash, expectedHash)
86+ } catch (_: Exception ) {
6687 false
6788 }
6889 }
69- }
90+ }
0 commit comments