Android(Kotlin)

[Kotlin] 휴대폰 인증번호 자동추출

삽질중 2025. 7. 1. 11:09
// SMS
implementation 'com.google.android.gms:play-services-auth:21.3.0'

 

1. SmsReciver Class 

@Suppress("DEPRECATION")
open class SmsReceiver() : BroadcastReceiver() {
    var pattern: Pattern = Pattern.compile("\\d{6}")
    var mAuthNumber: String? = null

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

    override fun onReceive(context: Context, intent: Intent) {

        if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
            val extras = intent.extras

            if (extras == null) {
                DLog.e(Exception(), "SmsReceiver Extras Null !!!!!!!!!!!!!")
                return
            }

            var status: Status? = null
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                status = BundleCompat.getParcelable(extras, SmsRetriever.EXTRA_STATUS, Status::class.java)
            } else {
                status = extras.get(SmsRetriever.EXTRA_STATUS) as Status
            }

            when (status?.statusCode) {
                CommonStatusCodes.SUCCESS -> {
                    val message = extras.getString(SmsRetriever.EXTRA_SMS_MESSAGE)
                    DLog.d(Exception(), "SMS : $message")
                    mAuthNumber = null

                    val matcher = pattern.matcher(message)
                    if (matcher.find()) {
                        mAuthNumber = matcher.group(0)
                        DLog.d(Exception(), "SMS 인증번호 : $mAuthNumber")
                    }
                }

                CommonStatusCodes.TIMEOUT -> {}
            }
        }
    }

    // Todo: SMS Hash 추출
    //  Debug 모드와 Release 모드 Hash 값은 다름
    //  (Package Hash Ex : Debug - sSJX3jjngYe / Release - 20ak8Oh6RlW)
    @SuppressLint("PackageManagerGetSignatures")
    fun getAppSignatures(context: Context): String? {
        var hash: String? = ""

        val packageName = context.packageName
        val packageManager = context.packageManager
//            val signatures = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures

        val signatures = with(packageManager) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                getPackageInfo(packageName, GET_SIGNING_CERTIFICATES)
                    .signingInfo
                    ?.apkContentsSigners
            } else {
                getPackageInfo(packageName, GET_SIGNATURES).signatures
            }
        }

        if (signatures != null) {
            for (signature in signatures) {
                hash = getHash(packageName, signature.toCharsString())
                if (hash != null) {
                    DLog.d(Exception(), String.format("문자 마지막에 붙는 해쉬값 : %s", hash))
                    return hash
                }
            }
        }

        DLog.e(Exception(), "getAppSignatures Hash Value Fail~~~~~~~~ : " + hash)

        return hash
    }

    private fun getHash(packageName: String, signature: String): String? {
        val appInfo = "$packageName $signature"

        try {
            val messageDigest = MessageDigest.getInstance(HASH_TYPE)
            messageDigest.update(appInfo.toByteArray(StandardCharsets.UTF_8))

            val hashSignature = Arrays.copyOfRange(messageDigest.digest(), 0, NUM_HASHED_BYTES)
            val base64Hash = Base64
                .encodeToString(hashSignature, Base64.NO_PADDING or Base64.NO_WRAP)
                .substring(0, NUM_BASE64_CHAR)

            return base64Hash
        } catch (e: CloneNotSupportedException) {
            DLog.e(Exception(), e.stackTraceToString())
        }

        return null
    }
}

 

2. 적용방법

// 문자 인증번호 추출 Receiver
private lateinit var mSmsReceiver: SmsReceiver

override fun onDestroy() {
   super.onDestroy()
   try {
       unregisterReceiver(mSmsReceiver)
   } catch (ignored: IllegalArgumentException) {
       DLog.e(Exception(), ignored.stackTraceToString())
   }
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    //Todo : SMS 문자 인증번호 추출 (240613 추가)
        mSmsReceiver = object : SmsReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                super.onReceive(context, intent)

                mAuthNumber?.let { authNumber ->
                    if (!authNumber.isEmpty()) {
                        //JavaScript Interface 전송
                          Toast.makeText(context, "SMS : " + authNumber, Toast.LENGTH_SHORT).show();
                        DLog.i(Exception(), "SMS : $authNumber")

                        val result = JSONObject()
                        mIWebview.post {
                            try {
                                result.put("auth_number", authNumber)
                                val callback = "authNumber('$result')"

                                DLog.i(Exception(), "SMS JSON : $callback")
                                mIWebview.evaluateJavascript(callback, null)
                            } catch (e: JSONException) {
                                DLog.e(Exception(), e.stackTraceToString())
                            }
                        }

                        //원인은 알 수 없으나 메세지 한번 들어오고 다시 보내면 문자가 안들어옴.
                        //추측으로는 register 해제되는게 아닐까?
                        //문자 받아서 처리후 다시 초기화 해주면 계속 받을 수 있어 이렇게 처리함.
                        initSMSReceive()
                    }
                }
            }
        }
        
        // SMS 인증번호 Receive 초기화
        initSMSReceive()
}

// Todo : SMS Receiver Register
private fun initSMSReceive() {
    val client = SmsRetriever.getClient(this)
    val task = client.startSmsRetriever()

    task.addOnSuccessListener {
        val intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            registerReceiver(mSmsReceiver, intentFilter, RECEIVER_EXPORTED)
        } else {
            registerReceiver(mSmsReceiver, intentFilter)
        }
        // SMS 문자 뒤에 붙일 Hash 값. (고정값임)
        DLog.i(Exception(), "Signatures Hash : " + mSmsReceiver.getAppSignatures(this@MainActivity))
        //                Toast.makeText(MainActivity.this, "Signatures Hash : " + smsReceiver.getAppSignatures(MainActivity.this), Toast.LENGTH_SHORT).show();
    }

    task.addOnFailureListener { e -> DLog.e(Exception(), e.stackTraceToString()) }
}