تایید خودکار پیامک اعتبار سنجی در اندروید با SMS Retriever API

سلام

توی این پست در مورد نحوه ی پیاده سازی تایید خودکار پیامک اعتبار سنجی در اندروید با استفاده از SMS Retriever API توضیحاتی ارائه میدم. که امیدوارم مفید باشه براتون

روند سابق تایید کد اعتبار سنجی به این صورت بود که یک اپلیکیشن درخواست خواندن پیامک های گوشی رو ارسال میکرد به کاربر و اگر کاربر تایید میکرد اپلیکیشن به صورت خودکار پیام های دریافتی رو میخوند و کد رو درمیاورد و توی یه EditText قرار میداد.

مشکل این روند اینه که خب وقتی شما دسترسی به پیامک ها رو دادین چه تضمینی وجود داره که بقیه ی پیام های شما رو هم نخونه و امکان نقض حریم خصوصی وجود داره.

گوگل برای رفع این مشکل اولا که اپ هایی که از روش سابق استفاده کردن رو انگار میخواد از گوگل پلی حذف کنه (یا کرده!) و یک روش جایگزین قرار داده برای اینکار.

گوگل گفته شمایی که میخواید توی اپلیکیشنتون از اعتبار سنجی با شماره ی موبایل استفاده کنید و میخواید که این اعتبار سنجی به صورت خودکار باشه دیگه دسترسی پیامک رو نگیرید و بجاش با روشی که در زیر میگم یک هش به پیامک اعتبار سنجیتون اضافه کنید و با این کار دیگه نیازی به دسترسی پیامک ها ندارین!

پیامک اعتبار سنجی شما که برای کاربر ارسال میشه چیزی شبیه به اینه:

کد تایید نرم افزار: 52236
GDse38df452

که با استفاده از این کد هش شده (که باید طولش 11 باشه حتما) اجازه ی خوندن این پیامک رو به شما میده!

و شما با خوندن و استخراج کد میتونید اعتبار سنجی رو انجام بدید.

در ادامه این پیاده سازی رو انجام میدیم.

به طور خلاصه روند کار به این صورته که ابتدا شما:

1. شماره ی کاربر رو به سمت سرور خودتون ارسال میکنید
2. همزمان توی اپلیکیشن باید SMS Retriever API رو صدا بزنید تا به پیامک های دریافتی از سرور گوش بده
3. زمانی که سرور پیامک رو برای شماره ی کاربر فرستاد حالا سرویس های گوگل پلی با استفاده از او hash که توی پیامک دریافتی هست تعیین میکنن که آیا این پیامک برای برنامه ی مورد نظر هست یا خیر
4. و در مرحله ی آخر اپلیکیشن پیامک رو میخونه و مجدد پیامک رو همراه با شماره برای سرور برنامه ارسال میکنه


پیاده سازی SMS Retriever API

مرحله ی 1 (اضافه کردن کتابخانه های مورد نیاز):

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


مرحله 2 (شروع SmsRetriever):

بعد از اینکه کاربر شماره رو وارد کرد شما باید SmsRetriever رو در صفحه ی وارد کردن کد تایید پیاده سازی کنید:

private void smsListener() {
      SmsRetrieverClient client = SmsRetriever.getClient(this);
      client.startSmsRetriever();
}


مرحله 3 (پیاده سازی BroadcastReceiver برای دریافت پیامک):

public class AppSMSBroadcastReceiver extends BroadcastReceiver {
    private OnSmsReceiveListener onSmsReceiveListener;
    public void setOnSmsReceiveListener(OnSmsReceiveListener onSmsReceiveListener) {
        this.onSmsReceiveListener = onSmsReceiveListener;
    }

    @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);
                    onSmsReceiveListener.onReceive(message);
                    break;
                case CommonStatusCodes.TIMEOUT:
                    break;
            }
        }
    }

    public interface OnSmsReceiveListener {
        void onReceive(String code);
    }
}

همینطور که میبینید یک لیستنر هم قرار دادیم که وقتی کد دریافت شد کد رو به اکتیویتیمون یا هرجا که وصل کردیم ارسال کنه.

حالا باید BroadcastReceiver رو به برنامه معرفی کنیم تا ازش استفاده کنیم. برای اینکار یا میتونیم اون رو توی Manifest قرار بدیم یا اینکه مستقیم با کد جاوا ثبت کنیم.

من چون میخوام با لیستنر بهش دسترسی داشته باشم به صورت زیر توی کد جاوا پیاده میکنم:

IntentFilter intentFilter = new IntentFilter(&quotcom.google.android.gms.auth.api.phone.SMS_RETRIEVED&quot);

AppSMSBroadcastReceiver appSMSBroadcastReceiver = new AppSMSBroadcastReceiver();
appSMSBroadcastReceiver.setOnSmsReceiveListener(new AppSMSBroadcastReceiver.OnSmsRecieveListener() {
    @Override
    public void onReceive(String code) {
       // کد دریافتی از برودکست 
    }
});

@Override
protected void () {
    super.();
    registerReceiver(appSMSBroadcastReceiver, intentFilter);
}

@Override
protected void () {
    super.();
    unregisterReceiver(appSMSBroadcastReceiver);
}


مرحله 4 (ساختن hash):

حالا باید کد hash مربوط به اپلیکیشن رو بسازیم و به سرور بگیم که این کد رو توی پیامک دریافتی ما قرار بده تا سرویس های گوگل پلی بدونن که این پیامک دریافتی برای اپلیکیشن ماست.

با استفاده از کلاس زیر میتونیم این کد رو بسازیم:

public class AppSignatureHelper  extends ContextWrapper {
    public static final String TAG = AppSignatureHelper.class.getSimpleName();

    private static final String HASH_TYPE = &quotSHA-256&quot
    public static final int NUM_HASHED_BYTES = 9;
    public static final int NUM_BASE64_CHAR = 11;

    public AppSignatureHelper(Context context) {
        super(context);
    }

    /**
     * Get all the app signatures for the current package
     *
     * @return
     */
    public ArrayList<String> getAppSignatures() {
        ArrayList<String> appCodes = new ArrayList<>();

        try {
            // Get all package signatures for the current package
            String packageName = getPackageName();
            PackageManager packageManager = getPackageManager();
            Signature[] signatures = packageManager.getPackageInfo(packageName,
                    PackageManager.GET_SIGNATURES).signatures;

            // For each signature create a compatible hash
            for (Signature signature : signatures) {
                String hash = hash(packageName, signature.toCharsString());
                if (hash != null) {
                    appCodes.add(String.format(&quot%s&quot, hash));
                }
            }
        } catch (PackageManager.NameNotFoundException e) {
            Log.v(TAG, &quotUnable to find package to obtain hash.&quot, e);
        }
        return appCodes;
    }

    private static String hash(String packageName, String signature) {
        String appInfo = packageName + &quot &quot + signature;
        try {
            MessageDigest messageDigest = MessageDigest.getInstance(HASH_TYPE);
            messageDigest.update(appInfo.getBytes(StandardCharsets.UTF_8));
            byte[] hashSignature = messageDigest.digest();

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

            Log.v(TAG + &quotsms_sample_test&quot, String.format(&quotpkg: %s -- hash: %s&quot, packageName, base64Hash));
            return base64Hash;
        } catch (NoSuchAlgorithmException e) {
            Log.v(TAG+ &quotsms_sample_test&quot, &quothash:NoSuchAlgorithm&quot, e);
        }
        return null;
    }
}

حالا باید توی کلاس Application برنامه متد ()getAppSignatures رو اجرا کنید که لیستی از کد ها برمیگردونه که شما میتونید اولی رو بردارید و اون میشه کد hash شما که باید توی پیامک قرار داده بشه:

AppSignatureHelper appSignature = new AppSignatureHelper(this);
for (String signature : appSignature.getAppSignatures()) {
    Log.e(TAG, &quotonCreate: &quot + signature );
}


نکاتی در مورد این کلاس:

بعد از اینکه نسخه رو آماده کردید این کلاس رو حذف کنید

حتما در زمان ریلیز هم کد هش رو بگیرید و از اون کد استفاده کنید چون ممکنه در زمان دیباگ کد hash با زمان ریلیز متفاوت باشه


تمام.

کد نمونه رو در گیت هاب زیر میتونید ببینید:

https://github.com/softrunapp/SMS-Retriever-API

رفرنس:

https://medium.com/android-dev-hacks/autofill-otp-verification-with-latest-sms-retriever-api-73c788636783