* 출처
: https://qiita.com/mii-chang/items/769589a1379aacff009e
시작하기
어느 날 GooglePlayConsole에 이런 경고가 나오고있었습니다.
이 응용 프로그램은 android.permission.RECEIVE_SMS
권한을 부여했습니다. android.permission.RECEIVE_SMS
권한을 사용한다면 그 권한을 무슨 목적으로 사용 하는지를 명확하게 신청해 달라는 것이 이번 경고의 내용입니다.
이 응용 프로그램에서 android.permission.RECEIVE_SMS
의 사용은 계정 인증시 SMS 인증을하고 인증 코드를 자동 입력시키기 위해 수신 된 SMS의 내용을 얻을 않았다. 이 용도로 android.permission.RECEIVE_SMS
를 사용해 버리면 자신의 응용 프로그램에 필요한 SMS 이외의 모든 SMS 앱에서 읽을 수 버립니다. 그것은 보안으로 어떨 것인가 라고하는 것으로, Google에 꾸중을 받았습니다. 그리고 '대안으로 SMS Retriever API 를 사용 해주세요.'라고했습니다.
SMS Retriever API는
android.permission.RECEIVE_SMS
를 사용하지 않고 자신의 응용 프로그램에 필요한 SMS를 수신 할 수있는 구조가 SMS Retriever API 입니다. https://developers.google.com/identity/sms-retriever/overview
서버에서 보내 오는 SMS 메시지에 접두사와 자신의 응용 프로그램임을 식별 해시를 제공합니다.
https://developers.google.com/identity/sms-retriever/verify
keytool -alias MyAndroidKey -exportcert -keystore MyProduction.keystore | xxd -p | tr -d "[:space:]" |
응용 프로그램은 SMS Retriever API를 사용하고 자신의 앱 해시가 포함 된 SMS 내용만 가져올 수 있습니다.
https://developers.google.com/identity/sms-retriever/request
이번에는 위의 이미지 ③⑤ 부분의 구현에 대해서 쓰고 있습니다.
사전 준비
app 부하의 build.gradle
두 com.google.android.gms:play-services-auth
를 추가합니다.
버전은 여기 를 참고하십시오.
dependencies {
implementation "com.google.android.gms : play-services-auth : 16.0.1"
}
구현 방법
대부분 레퍼런스 거리인데 샘플을 올립니다
class MainActivity : AppCompatActivity () GoogleApiClient . ConnectionCallbacks , GoogleApiClient . OnConnectionFailedListener {
override fun onCreate ( savedInstanceState : Bundle ?) {
super . onCreate ( savedInstanceState )
setContentView ( R . layout . activity_main )
createSmsRetrieverClient ()
// Hash 만들기
// val helper = AppSignatureHelper (applicationContext)
// Log.d ( "hash ="helper.appSignatures.toString ()) //이 해시를 SMS로 포함
}
private fun createSmsRetrieverClient () {
val receiver = SMSBroadcastReceiver ()
val intentFilter = IntentFilter ()
intentFilter . addAction ( SmsRetriever . SMS_RETRIEVED_ACTION )
registerReceiver ( receiver , intentFilter )
val client = SmsRetriever . getClient ( this )
val task = client . startSmsRetriever ()
task . addOnSuccessListener {
// SMSRetriever 추가 성공시 청취자
Log . d ( "Listener" , "SUCCESS" )
}
task . addOnFailureListener {
// SMSRetriever 추가 실패 청취자
Log . d ( "Listener" , "FAILURE" )
}
}
inner class SMSBroadcastReceiver : BroadcastReceiver () {
override fun onReceive ( context : Context , intent : Intent ) {
if ( SmsRetriever . SMS_RETRIEVED_ACTION == intent . action ) {
val extras = intent . extras
val status = extras ! . get ( SmsRetriever . EXTRA_STATUS) as Status
var message = ""
when ( status . statusCode ) {
CommonStatusCodes . SUCCESS -> {
// 자신의 응용 프로그램에 대한 해시 값이 포함 된 SMS를 수신 할 때 여기에 와서
// 대상 SMS의 내용 만 취득 가능
message = extras . get ( SmsRetriever . EXTRA_SMS_MESSAGE ) as String
Log . d ( "SMSMessage =" , message )
}
CommonStatusCodes . TIMEOUT -> {
// 수신기를 시작하고 5 분 SMS를 수신하지 못하면 만료 될
Log . d ( "SMSMessage =" , "TIMEOUT" )
}
}
}
}
}
override fun onConnectionFailed ( p0 : ConnectionResult ) {
}
override fun onConnectionSuspended ( p0 : Int ) {
}
override fun onConnected ( p0 : Bundle ?) {
}
}
SMS를 수신하고 싶을 때 SmsRetrieverClient의 인스턴스를 생성하고 SMS를받을 준비를합니다.
서버에서
<#> 당신의 인증 코드 : 123456입니다
해시
라고하는 내용의 SMS가 보낸 해시가 자신의 앱 해시와 일치하지 않으면
extras . get ( SmsRetriever . EXTRA_SMS_MESSAGE )
그리고, SMS의 내용을 검색 할 수 있습니다.
해시 앱의 패키지 이름 인증서 정보에서 생성됩니다.
해시 생성을위한 샘플 코드 가 제공되고 있기 때문에, 이제 자신의 환경에 맞는 해시를 생성합니다.
package com.google.samples.smartlock.sms_verify; 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; /** * 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. */ public class AppSignatureHelper extends ContextWrapper { public static final String TAG = AppSignatureHelper.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 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("%s", hash)); } } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Unable to find package to obtain hash.", e); } return appCodes; } 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(); // 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.d(TAG, String.format("pkg: %s -- hash: %s", packageName, base64Hash)); return base64Hash; } catch (NoSuchAlgorithmException e) { Log.e(TAG, "hash:NoSuchAlgorithm", e); } return null; }
}
여담? 하지만, 참조 에 써있는 명령은 올바른 해시를 만들 수 없습니다 ... (이제 몇 시간 빠진 ...) 명령에 적용 항목을 잘못한 것이라고 생각 합니다만, 무엇을 잘못했는지 확인한 후에 추가하겠습니다. . .
수신 확인
서버에서 SMS를 보내주는 것이 실제 사용 용도라고 생각 합니다만, 확인만한다면 SMSBroadcastReceiver가 준비 되어있는 검증 단말기에 다른 단말에서 <#>
의 접두사를 붙여 생성된 해시를 포함한 SMS를 보내도 수신 확인이 가능합니다
정리
상기와 같이 구현하면 android.permission.RECEIVE_SMS
권한없이 자신의 응용 프로그램에 관련된 SMS의 내용만 응용 프로그램에서 읽을 수 있습니다. SMS 인증 코드 자동 입력 등에 활용하십시오!
내가 조사한 시점에서는 일본어의 정보가 매우 적었기 때문에, 글을 써보았습니다.
참고 주시면 감사하겠습니다!
샘플 코드
샘플 코드 전부는 GitHub 에 싣고 있습니다
'IT_Programming > Kotlin' 카테고리의 다른 글
[펌] Kotlin에서 자주 사용하는 annotation 정리 (0) | 2019.07.08 |
---|---|
[펌] AppUpdateManager를 이용한 앱 업데이트 처리 (0) | 2019.06.25 |
[펌] 코틀린 의 apply, with, let, also, run 은 언제 사용하는가? (0) | 2018.07.27 |
[펌] Java와 함께 사용하는 Kotlin (0) | 2017.08.28 |
[펌] ConstraintLayout으로 아름다운 애니메이션하기 (0) | 2017.06.02 |