Android 短信验证码自动填充
本文主要介绍国外google上线的app 短信自动填充方案。
本方案主要是使用google提出的,防止开发者使用SMS相关权限造成的用户信息泄露
目录
注意点:
1、本方式不适合华为手机,华为有自己的获取方式
2、本方式不需要添加任何短信权限
3、项目版本
开发成本:
Android代码
1、导库
1)在项目根目录的build.gradle中添加下面代码
2)项目级build.gradle引用库
2、注册广播
1)静态注册
2)动态注册
3、启动监听
4、广播代码
其他开发成本
短信模板 (短信后i按加上11位哈市值)
1)命令(mac 命令)
2)代码,放入项目中运行,打包、启动app(注意debug签名与正是签名包的不同,正式的hashcode 需要正式打包,然后打印出来))
注意点:
1、本方式不适合华为手机,华为有自己的获取方式
2、本方式不需要添加任何短信权限
3、项目版本
compileSdkVersion>=34
minSdkVersion>=19
开发成本:
app需要集成两个google库,后端只需要改短信模板,在短信后面添加11位hash值,hash值生成方式在下面链接可以看到
google开发地址(国内需要翻墙)
Android代码
1、导库
1)在项目根目录的build.gradle中添加下面代码
buildscript {
repositories {
...
google()
mavenCentral()
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
2)项目级build.gradle引用库
dependencies {
implementation 'com.google.android.gms:play-services-auth:21.2.0'
implementation 'com.google.android.gms:play-services-auth-api-phone:18.1.0'
}
2、注册广播
1)静态注册
<receiver
android:name="<路径>.MySMSBroadcastReceiver"
android:exported="true"
android:permission="com.google.android.gms.auth.api.phone.permission.SEND">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED" />
</intent-filter>
</receiver>
2)动态注册
if(smsBrod==null){
smsBrod= MySMSBroadcastReceiver(this,null)
}
val intentFilter = IntentFilter();
intentFilter.addAction(SmsRetriever.SMS_RETRIEVED_ACTION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this@TestAlendActivity.registerReceiver(
smsBrod,
intentFilter,
SmsRetriever.SEND_PERMISSION,
null, Context.RECEIVER_EXPORTED
)
}
3、启动监听
fun googleMSM() {
val client = SmsRetriever.getClient(this)
val task: Task<Void> = client.startSmsRetriever()
task.addOnSuccessListener(OnSuccessListener<Void?> {
Log.i("SMS_CON", "googleMSM addOnSuccessListener")
if(smsBrod==null){
smsBrod= MySMSBroadcastReceiver(this,null)
}
val intentFilter = IntentFilter();
intentFilter.addAction(SmsRetriever.SMS_RETRIEVED_ACTION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this@TestAlendActivity.registerReceiver(
smsBrod,
intentFilter,
SmsRetriever.SEND_PERMISSION,
null, Context.RECEIVER_EXPORTED
)
}
})
task.addOnFailureListener(OnFailureListener {
Log.i("SMS_CON", "googleMSM addOnFailureListener")
})
}
4、广播代码
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
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 org.json.JSONException;
import org.json.JSONObject;
import java.lang.ref.SoftReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* BroadcastReceiver to wait for SMS messages. This can be registered either
* in the AndroidManifest or at runtime. Should filter Intents on
* SmsRetriever.SMS_RETRIEVED_ACTION.
*/
public class MySMSBroadcastReceiver extends BroadcastReceiver {
public MySMSBroadcastReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
Log.i("SMS_CON", "googleMSM MySMSBroadcastReceiver action=" + intent.getAction());
if (SmsRetriever.SMS_RETRIEVED_ACTION.equals(intent.getAction())) {
Bundle extras = intent.getExtras();
Status status = (Status) extras.get(SmsRetriever.EXTRA_STATUS);
Log.i("SMS_CON", "status =" + status);
if(status!=null){
switch (status.getStatusCode()) {
case CommonStatusCodes.SUCCESS:
// (Optional) Get SMS Sender address - only available in
// GMS version 24.20 onwards, else it will return null
// String senderAddress = extras.getString(SmsRetriever.EXTRA_SMS_ORIGINATING_ADDRESS);
// Get SMS message contents
String message = extras.getString(SmsRetriever.EXTRA_SMS_MESSAGE);
// Extract one-time code from the message and complete verification
// by sending the code back to your server.
Log.i("SMS_CON", "message =" + message);
if (message != null) {
Pattern code = Pattern.compile("(\\d{6})");
Matcher matcher = code.matcher(message);
String otp = "";
if (matcher.find()) {
otp = matcher.group(0);
} else {
otp = message;
}
Log.i("SMS_CON", "otp =" + otp);
Toast.makeText(context,"otp=\n"+otp,Toast.LENGTH_LONG).show();
}
break;
case CommonStatusCodes.TIMEOUT:
// Waiting for SMS timed out (5 minutes)
// Handle the error ...
Log.i("SMS_CON", "TIMEOUT =");
break;
}
}
}
}
}
其他开发成本
短信模板 (短信后i按加上11位哈市值)
Hash值生成方式
1)命令(mac 命令)
keytool -exportcert -keystore keystory名称.jks -storepass 你的密码 -alias 别名 | xxd -p | tr -d "[:space:]" | echo -n 你的包名 `cat` | shasum -a 256 | tr -d "[:space:]-" | xxd -r -p | base64 | cut -c1-11
2)代码,放入项目中运行,打包、启动app(注意debug签名与正是签名包的不同,正式的hashcode 需要正式打包,然后打印出来))
aab文件也可以通过命令安装到手机,网上搜一下 bundletool.jar 这个工具
import android.annotation.TargetApi;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.util.Base64;
import android.util.Log;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
public class AppSignatureHashHelper extends ContextWrapper {
public static final String TAG = AppSignatureHashHelper.class.getSimpleName();
private static final String HASH_TYPE = "SHA-256";
public static final int NUM_HASHED_BYTES = 9;
public static final int NUM_BASE64_CHAR = 11;
public AppSignatureHashHelper(Context context) {
super(context);
}
public ArrayList<String> getAppSignatures() {
ArrayList<String> appSignaturesHashs = new ArrayList<>();
try {
String packageName = getPackageName();
PackageManager packageManager = getPackageManager();
Signature[] signatures = packageManager.getPackageInfo(packageName,
PackageManager.GET_SIGNATURES).signatures;
for (Signature signature : signatures) {
String hash = hash(packageName, signature.toCharsString());
if (hash != null) {
appSignaturesHashs.add(String.format("%s", hash));
}
}
} catch (Exception e) {
Log.e(TAG, "Package not found", e);
}
return appSignaturesHashs;
}
@TargetApi(19)
private static String hash(String packageName, String signature) {
String appInfo = packageName + " " + signature;
try {
MessageDigest messageDigest = MessageDigest.getInstance(HASH_TYPE);
messageDigest.update(appInfo.getBytes(StandardCharsets.UTF_8));
byte[] hashSignature = messageDigest.digest();
hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES);
String base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING | Base64.NO_WRAP);
base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR);
return base64Hash;
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "No Such Algorithm Exception", e);
}
return null;
}
}
调用方式
String hashcodeStr= AppSignatureHashHelper(this).getAppSignatures()
上面会生成11位hash值,放在短信后面就行了
***以上是获取短信,短信自动填充代码就不写了,拿到字符串 设置到输入框就行,推荐按使用动态注册,可以给广播传入回调接口,处理返回的验证码(otp)
到这里就完成了,上面获取命令的方式是通过自己签名的方式,如果是让google生成签名,则需要去看链接里的步骤,比较详细
测试的话 可以让别人发短信给你 ,在app里会受到广播