microg/GmsCore · error · RuntimeException

Collision in hash string, can't use SMS Retriever API

Error message

Collision in hash string, can't use SMS Retriever API

What it means

startSmsRetriever throws this when anyOtherPackageHasHashString(packageName, appHashString) returns true: another installed package computes the same 11-character SMS Retriever hash (derived from package name + signing certificate SHA-256). Because OTP codes are broadcast to whichever app holds the matching hash, a hash collision would let another app intercept your SMS codes, so the library refuses to start.

Source

Thrown at play-services-auth-api-phone/core/src/main/kotlin/org/microg/gms/auth/phone/SmsRetrieverCore.kt:101

                context.registerReceiver(smsBroadcastReceiver, intentFilter)
            }
        }
    }

    private suspend fun ensureReady(permissions: Array<String>): Boolean {
        if (SDK_INT < 19) throw RuntimeException("Version not supported")
        if (!ensurePermission(permissions)) return false
        configureBroadcastListenersIfNeeded()
        return true
    }

    suspend fun startSmsRetriever(packageName: String) {
        val appHashString = getHashString(packageName)

        if (!ensureReady(arrayOf(RECEIVE_SMS)))
            throw RuntimeException("Initialization failed")
        if (anyOtherPackageHasHashString(packageName, appHashString))
            throw RuntimeException("Collision in hash string, can't use SMS Retriever API")
        if (requests.values.any { it.packageName == packageName && it.appHashString == appHashString && it.type == RETRIEVER })
            throw RuntimeException("App already listening")

        val request = SmsRetrieverRequest(
            id = requestIdCounter.incrementAndGet(),
            type = RETRIEVER,
            packageName = packageName,
            appHashString = appHashString,
            timeoutPendingIntent = getTimeoutPendingIntent(context, packageName)
        )
        requests[request.id] = request
        alarmManager.set(AlarmManager.RTC, request.creation + TIMEOUT, request.timeoutPendingIntent)
    }

    suspend fun startWithConsentPrompt(packageName: String, senderPhoneNumber: String?) {
        if (!ensureReady(arrayOf(RECEIVE_SMS, READ_CONTACTS)))
            throw RuntimeException("Initialization failed")
        if (requests.values.any { it.packageName == packageName && it.senderPhoneNumber == senderPhoneNumber && it.type == USER_CONSENT })

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Uninstall the conflicting app that shares the same hash string (check other installed builds of your app)
  2. Sign your app with a unique release keystore so its hash differs from other packages
  3. Rename the applicationId of duplicate test builds to avoid hash overlap
  4. Remove clone/parallel-app wrappers that re-sign packages with a shared key
Defensive patterns

Strategy: try-catch

Try / catch

try { core.startSmsRetriever(pkg) } catch (e: RuntimeException) { if (e.message?.startsWith("Collision in hash string") == true) showHashConflictUi() else throw e }

Prevention

When it happens

Trigger: Two installed apps signed with the same certificate and sharing the same package-name/hash derivation, or a test/debug-signed duplicate build of your app installed alongside the real app.

Common situations: Debug and release builds signed with the same debug key installed side by side; clone/parallel-space apps; re-signed APKs installed on the same device during development.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/c01f1d8ccd047880. Report an issue: GitHub.