Bootstrap

Android 中获取和读取短信验证码

方法一:通过 SMS Retriever API

SMS Retriever API 是 Google 提供的一种安全的方式,可以从系统中获取不需要权限的短信验证码。这种方式不需要请求 READ_SMS 权限,非常适合处理短信验证码的情况。

1. 在 build.gradle 中添加依赖

dependencies {
    implementation 'com.google.android.gms:play-services-auth-api-phone:18.0.1'
}

2. 获取应用的哈希值

Google 的 SMS Retriever API 会通过一个特定的哈希值来验证短信的来源。获取这个哈希值后,将其附加在发送的短信中。

private String getAppHashKey() {
    try {
        PackageInfo info = getPackageManager().getPackageInfo(
                getPackageName(),
                PackageManager.GET_SIGNATURES);
        for (Signature signature : info.signatures) {
            MessageDigest md = MessageDigest.getInstance("SHA");
            md.update(signature.toByteArray());
            return Base64.encodeToString(md.digest(), Base64.NO_WRAP).substring(0, 9);
        }
    } catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return null;
}

3. 开启 SMS Retriever

SmsRetrieverClient client = SmsRetriever.getClient(this);
Task<Void> task = client.startSmsRetriever();
task.addOnSuccessListener(new OnSuccessListener<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
        // 成功启动 SMS Retriever
    }
});
task.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception e) {
        // 启动失败
    }
});

4. 接收短信

注册一个 BroadcastReceiver 来监听 SMS Retriever API 的广播

public class MySmsBroadcastReceiver extends BroadcastReceiver {
    private SmsReceiverListener listener;

    public void setSmsReceiverListener(SmsReceiverListener listener) {
        this.listener = listener;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if (SmsRetriever.SMS_RETRIEVED_ACTION.equals(intent.getAction())) {
            Bundle extras = intent.getExtras();
            Status status = (Status) extras.get(SmsRetriever.EXTRA_STATUS);
            switch (status.getStatusCode()) {
                case CommonStatusCodes.SUCCESS:
                    // 获取短信
                    String message = (String) extras.get(SmsRetriever.EXTRA_SMS_MESSAGE);
                    if (listener != null) {
                        listener.onSmsReceived(message);
                    }
                    break;
                case CommonStatusCodes.TIMEOUT:
                    // 超时
                    break;
            }
        }
    }
}

5. 处理短信中的验证码

使用正则表达式提取验证码

Pattern pattern = Pattern.compile("\\d{6}");
Matcher matcher = pattern.matcher(message);
if (matcher.find()) {
    String code = matcher.group(0);
    // 使用验证码
}

短信格式:

通过 SMS Retriever API,需要确保短信的格式包含应用的哈希值,例如:

Your verification code is 123456.
<#> Your App Name: Use this code to verify your phone number. 
abc123xyz (Your App's hash key)

在使用 SMS Retriever API 获取短信验证码时,短信必须包含应用的哈希值,这样 Android 系统才能识别出该短信是由你应用发送的,并自动从系统短信中提取该短信 

abc123xyz 是通过应用的签名生成的哈希值

哈希值用于验证短信的来源,确保是从与你应用关联的服务器发送的短信,而不是其他来源伪造的。系统通过这个哈希值自动识别和提取短信内容,不需要用户手动输入验证码。

6.哈希值生成步骤

  • 获取应用的签名证书:应用的哈希值是根据应用的签名证书生成的。在 Android 中,每个应用都有一个签名证书,用来唯一标识应用。SMS Retriever API 通过应用的签名证书来生成哈希值。

  • 使用 Java 代码生成哈希值:可以在应用的 ActivityUtils 类中调用它

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.util.Base64;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class AppSignatureHelper {

    private static final String HASH_TYPE = "SHA-256";
    private static final int NUM_HASHED_BYTES = 9;
    private static final int NUM_BASE64_CHAR = 11;

    public static String getAppHashKey(Context context) {
        try {
            // 获取应用包的信息
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(
                    context.getPackageName(),
                    PackageManager.GET_SIGNATURES);
            // 遍历签名信息
            for (Signature signature : packageInfo.signatures) {
                byte[] signatureBytes = signature.toByteArray();
                // 通过SHA-256进行哈希计算
                MessageDigest md = MessageDigest.getInstance(HASH_TYPE);
                md.update(signatureBytes);
                byte[] digest = md.digest();

                // 将哈希值编码为Base64格式,取前11位作为哈希值
                return Base64.encodeToString(digest, Base64.NO_WRAP).substring(0, NUM_BASE64_CHAR);
            }
        } catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }
}
  • 调用哈希值生成方法

       在应用启动时或者短信发送前调用此方法,获取哈希值。这个哈希值需要包含在发送的短信中

String hashKey = AppSignatureHelper.getAppHashKey(context);
Log.d("AppHashKey", "Hash Key: " + hashKey);
  • 将哈希值添加到短信内容中

       将生成的哈希值附加到短信的末尾

Your verification code is 123456.
<#> MyApp: Use this code to verify your phone number.
abc123xyz (Your App's hash key)

方法二:通过读取短信权限 (READ_SMS)

这种方法需要获取读取短信的权限,但由于隐私和安全问题,Google 对读取短信的权限要求非常严格,并且 Android 6.0 以上的版本还需要动态申请权限。

1. 在 AndroidManifest.xml 中添加权限

<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />

2. 动态申请权限(针对 Android 6.0 及以上)

public class SmsReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(intent.getAction())) {
            Bundle bundle = intent.getExtras();
            if (bundle != null) {
                Object[] pdus = (Object[]) bundle.get("pdus");
                for (Object pdu : pdus) {
                    SmsMessage message = SmsMessage.createFromPdu((byte[]) pdu);
                    String sender = message.getDisplayOriginatingAddress();
                    String content = message.getMessageBody();
                    // 处理短信内容,提取验证码
                }
            }
        }
    }
}

3. 注册一个 BroadcastReceiver 来监听收到的短信

public class SmsReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(intent.getAction())) {
            Bundle bundle = intent.getExtras();
            if (bundle != null) {
                Object[] pdus = (Object[]) bundle.get("pdus");
                for (Object pdu : pdus) {
                    SmsMessage message = SmsMessage.createFromPdu((byte[]) pdu);
                    String sender = message.getDisplayOriginatingAddress();
                    String content = message.getMessageBody();
                    // 处理短信内容,提取验证码
                }
            }
        }
    }
}

4. 提取短信验证码

SMS Retriever API 类似,可以使用正则表达式提取验证码。

总结

  1. SMS Retriever API 是更加推荐的方法,因为它不需要读取短信的权限,更加安全。
  2. 读取短信权限 方法需要申请敏感权限,使用较少。
;