Android Verify OTP automatically with SMS Retriever API

Nagendra Hari Karthick
4 min readApr 5, 2019

Hello Everyone,

We are going to discuss about Automatic OTP verification in Kotlin using Google SMS retriever API. In this busy world, user always expect the things to be simple. If we come up with an complex and expect the user to use it. Then it would be difficult, to avoid such scenarios and toimprove the user experience automatic OTP verification comes in.

Come on, Let’s discuss this in detail.

Before proceeding we need to create an SMS template which should satisfy the below requirements

  • SMS text should not be longer than 140 bytes (Approx. 140 characters)
  • Begin with the prefix <#>
  • Contain a one-time code that should be generated from the server.
  • End with an 11-character hash string that identifies your app

Example:

<#> Dear User, Your OTP for Phone number verification is ******. Thankyou. EryNHKc84EP

Now we will see how to generate this hash string,

AppSignatureHelper.kt

import android.content.Context
import android.content.ContextWrapper
import android.content.pm.PackageManager
import android.util.Base64
import android.util.Log
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*

/**
* This is a helper class to generate your message hash to be included in your SMS message.
*
* Without the correct hash, your app won't recieve the message callback. This only needs to be
* generated once per app and stored. Then you can remove this helper class from your code.
*/
class AppSignatureHelper(context: Context) : ContextWrapper(context) {

/**
* Get all the app signatures for the current package
*
@return
*/
// Get all package signatures for the current package
// For each signature create a compatible hash
val appSignatures: ArrayList<String>
get() {
val appCodes = ArrayList<String>()

try {
val packageName = packageName
val packageManager = packageManager
// For API level 28 use PackageManager.GET_SIGNING_CERTIFICATES
// For API level less than 27 use PackageManager.GET_SIGNATURES
val signatures = packageManager.getPackageInfo(
packageName,
PackageManager.GET_SIGNATURES
).signatures
for
(signature in signatures) {
val hash = hash(packageName, signature.toCharsString())
if (hash != null) {
appCodes.add(String.format("%s", hash))
}
}
} catch (e: PackageManager.NameNotFoundException) {
Log.e(TAG, "Unable to find package to obtain hash.", e)
}

return appCodes
}

companion object {
val TAG = AppSignatureHelper::class.java.simpleName

private val HASH_TYPE = "SHA-256"
val NUM_HASHED_BYTES
= 9
val NUM_BASE64_CHAR = 11

private fun hash(packageName: String, signature: String): String? {
val appInfo = "$packageName $signature"
try
{
val messageDigest = MessageDigest.getInstance(HASH_TYPE)
messageDigest.update(appInfo.toByteArray(StandardCharsets.UTF_8))
var hashSignature = messageDigest.digest()

// truncated into NUM_HASHED_BYTES
hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES)
// encode into Base64
var base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING or Base64.NO_WRAP)
base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR)

Log.d(TAG, String.format("pkg: %s -- hash: %s", packageName, base64Hash))
return base64Hash
} catch (e: NoSuchAlgorithmException) {
Log.e(TAG, "hash:NoSuchAlgorithm", e)
}

return null
}
}
}

In your MainActivity.kt file use the below code

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val appSignatureHelper = AppSignatureHelper(applicationContext)
appSignatureHelper.appSignatures;
}
}

Now run the code and check in the logs for the key “AppSignatureHelper”. You can find the App Signature hash in the logs.

This Hash is used by SMSRetriever Client to identify our app and pass the message content to the application.

Now let’s get into the actual implementation

In AndroidManifest.xml

Configure the broadcast receiver

<receiver android:name=".SMSBroadcastReceiver">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED" />
</intent-filter>
</receiver>

your manifest file should look like below

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="nh.otp.verify"
>

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<receiver android:name=".SMSBroadcastReceiver">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED" />
</intent-filter>
</receiver>
</application>

</manifest>

In app gradle add the following dependencies and sync the project.

implementation "com.google.android.gms:play-services-auth-api-phone:16.0.0"

Create a broadcast receiver file with the name mentioned in manifest ,the logic for receiving the SMS and extracting the OTP.

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.Status
import java.util.regex.Pattern

class SMSBroadcastReceiver : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent) {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
if (extras != null) {
val status = extras.get(SmsRetriever.EXTRA_STATUS) as Status

when (status.statusCode) {
CommonStatusCodes.SUCCESS -> {
// Get SMS message contents
val message = extras.get(SmsRetriever.EXTRA_SMS_MESSAGE) as String
// Extract the 6 digit integer from SMS
val pattern = Pattern.compile("\\d{6}")
val matcher = pattern.matcher(message)
if (matcher.find()) {
// Update UI
Toast.makeText(context, "Your OTP is :" + matcher.group(0), Toast.LENGTH_SHORT).show()
}

}
CommonStatusCodes.TIMEOUT -> {
}
}// Waiting for SMS timed out (5 minutes)
// Handle the error ...

}

}

}

}

In our MainActivity.kt file initialize the SMS retriever client and start the broadcast receiver. For more details on the code, check comments.

import android.content.IntentFilter
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import com.google.android.gms.auth.api.phone.SmsRetriever

class MainActivity : AppCompatActivity() {
internal var TAG = this@MainActivity::class.toString()
internal var smsBroadcastReceiver: SMSBroadcastReceiver? = null
override fun
onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize the SmsRetriever client
val client = SmsRetriever.getClient(this)
// Start the SMS Retriever task
val task = client.startSmsRetriever()
task.addOnSuccessListener { aVoid ->
// if successfully started, then start the receiver.
registerReceiver(
smsBroadcastReceiver,
IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
)

}
task.addOnFailureListener { e ->
// if failure print the exception.
Log.e(TAG, e.toString()) }
}
}

Now run the code, once the SMS is received a toast message will be shown with the OTP.

Simply you can test the code my installing the application in one device and send an SMS(as per the template) to the application installed device from another device. The code will work like a boss. No need to generate SMS from server.

Feel free to ask doubts in the comment section.

Thanks

Nagendra Hari Karthick

Google Certified Associate Android Developer

--

--