Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions src/kotlin/helpers/security/SecretHash.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,24 @@ import androidx.core.content.edit
import org.telegram.messenger.FileLog
import org.telegram.messenger.Utilities
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec

// Salted SHA-256 for fork-local secrets (account passcodes, hidden-chats exit code).
// Salted PBKDF2-HMAC-SHA256 for fork-local secrets (account passcodes, hidden-chats exit code).
// Kept out of InuConfig/backups by design — callers store into their own prefs file.
object SecretHash {
Comment thread
batyka2001 marked this conversation as resolved.
private fun hash(code: String, salt: ByteArray): String {
private const val PREFIX = "pbkdf2$"
private const val ITERATIONS = 120_000
private const val KEY_BITS = 256

private fun pbkdf2(code: String, salt: ByteArray, iterations: Int): String {
val spec = PBEKeySpec(code.toCharArray(), salt, iterations, KEY_BITS)
val dk = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(spec).encoded
return PREFIX + iterations + "$" + Utilities.bytesToHex(dk)
}

private fun legacySha256(code: String, salt: ByteArray): String {
val pwd = code.toByteArray(StandardCharsets.UTF_8)
val bytes = ByteArray(32 + pwd.size)
System.arraycopy(salt, 0, bytes, 0, 16)
Expand All @@ -19,11 +32,14 @@ object SecretHash {
return Utilities.bytesToHex(Utilities.computeSHA256(bytes, 0, bytes.size.toLong()))
}

private fun constEq(a: String, b: String): Boolean =
MessageDigest.isEqual(a.toByteArray(StandardCharsets.UTF_8), b.toByteArray(StandardCharsets.UTF_8))

fun store(prefs: SharedPreferences, hashKey: String, saltKey: String, code: String) {
try {
val salt = ByteArray(16).also { Utilities.random.nextBytes(it) }
prefs.edit {
putString(hashKey, hash(code, salt))
putString(hashKey, pbkdf2(code, salt, ITERATIONS))
putString(saltKey, Base64.encodeToString(salt, Base64.DEFAULT))
}
} catch (e: Exception) {
Expand All @@ -38,7 +54,16 @@ object SecretHash {
} else {
val saltB64 = prefs.getString(saltKey, "") ?: ""
val salt = if (saltB64.isNotEmpty()) Base64.decode(saltB64, Base64.DEFAULT) else ByteArray(0)
stored == hash(code, salt)
if (stored.startsWith(PREFIX)) {
val sep = stored.indexOf('$', PREFIX.length)
val iterations = stored.substring(PREFIX.length, sep).toInt()
constEq(stored, pbkdf2(code, salt, iterations))
} else if (constEq(stored, legacySha256(code, salt))) {
store(prefs, hashKey, saltKey, code)
true
} else {
false
}
}
} catch (e: Exception) {
FileLog.e(e); false
Expand Down