The Android Developer Challenge is back! Submit your idea before December 2.

SafetyNet Attestation API

SafetyNet Attestation API는 앱 개발자가 앱을 실행하는 Android 기기를 평가할 수 있는 악용 방지 API입니다. 이 API는 서버가 정품 Android 기기에서 실행 중인 정품 앱과 상호작용하고 있는지 여부를 확인하는 악용 감지 시스템의 일부로 사용해야 합니다.

SafetyNet Attestation API는 기기 무결성 평가를 위해 암호로 서명된 증명을 제공합니다. 증명을 만들기 위해 API는 기기의 소프트웨어 및 하드웨어 환경을 검사하여 무결성 문제가 있는지 찾고 환경을 승인된 Android 기기의 참조 데이터와 비교합니다. 생성된 증명은 호출 앱이 제공하는 nonce에 바인딩되며 생성 타임스탬프와 요청 앱에 관한 메타데이터를 포함하고 있습니다.

다음은 이 API가 지원하도록 고안되지 않은 사용 사례입니다.

  • 독립 실행형 악용 방지 또는 앱 보안 메커니즘으로 작동하기. 게시된 앱 보안 권장사항 및 제품별 악용 방지 신호 모음과 함께 사용하세요.
  • 기기가 인터넷에 연결되어 있지 않을 때 작동하기. 이러한 시나리오에서는 API가 오류를 반환합니다.
  • API의 응답을 호출 앱에서 바로 해석하게 만들기. 모든 악용 방지 결정 로직을 내가 제어하는 서버로 이동하세요.
  • 시스템 수정에 관한 세부적인 신호 제공하기. 이 API는 다양한 수준의 시스템 무결성을 표현하는 부울 값을 제공합니다.
  • 기기 식별자, GPS 에뮬레이션 상태, 화면 잠금 상태와 같은 앱별 사용 사례의 신호 포함하기.
  • DRM 확인을 바꾸거나 강력하게 구현하기.
  • 기기가 루팅되었는지 여부를 확인하는 용도로만 사용하기(API가 기기의 전체적인 무결성을 확인하도록 고안되었음을 이용).

개요

SafetyNet Attestation API에서 사용하는 워크플로는 다음과 같습니다.

  1. SafetyNet Attestation API가 앱으로부터 호출을 수신합니다. 이 호출에 nonce가 포함되어 있습니다.
  2. SafetyNet 증명 서비스는 런타임 환경을 평가하고 평가 결과와 관련하여 Google 서버의 서명된 증명을 요청합니다.
  3. Google 서버에서 서명된 증명을 기기의 SafetyNet 증명 서비스에 전송합니다.
  4. SafetyNet 증명 서비스에서 이 서명된 증명을 앱에 반환합니다.
  5. 앱은 이 서명된 증명을 개발자의 서버에 전달합니다.
  6. 개발자의 서버에서는 응답의 유효성을 검사하고 응답을 바탕으로 악용 방지 결정을 내립니다. 개발자의 서버는 이러한 결과를 앱에 전달합니다.

그림 1은 이 프로세스를 그래픽으로 표현합니다.

그림 1. SafetyNet Attestation API 프로토콜

참고: 추가 문서 및 체크리스트

SafetyNet Attestation API의 초기화, 구성, 활성화 과정 전체에서 이 기본 문서 외에 다음 권장사항을 이해하고 준수하시기 바랍니다.

API 키 가져오기

SafetyNet Attestation API의 메서드를 호출하려면 API 키를 사용해야 합니다. 키를 만들어 삽입하려면 다음 단계를 완료하세요.

  1. Google API 콘솔의 라이브러리 페이지로 이동합니다.
  2. Android Device Verification API를 검색하여 선택합니다. Android Device Verification API 대시보드 화면이 나타납니다.
  3. 아직 이 API를 사용 설정하지 않았다면 사용 설정을 클릭합니다.
  4. 사용자 인증 정보 만들기 버튼이 나타나면 클릭하여 API 키를 생성합니다. 이 버튼이 나타나지 않으면 모든 API 사용자 인증 정보 드롭다운 목록을 클릭한 다음 Android Device Verification API를 사용 설정한 프로젝트와 연결된 API 키를 선택합니다.
  5. 왼쪽 사이드바에서 사용자 인증 정보를 클릭합니다. 표시되는 API 키를 복사합니다.
  6. SafetyNetClient 클래스의 attest() 메서드를 호출할 때 이 API 키를 사용합니다.

이 API 키를 만든 후 SafetyNet API 클라이언트 메일링 리스트에 참여하세요.

API 할당량 및 모니터링

중요: 프로젝트별로 SafetyNet Attestation API 호출에 사용할 수 있는 기본 할당량은 일일 요청 10,000건입니다.

앱에 더 높은 할당량이 필요한 경우 사용자에게 앱을 배포하기 전에 요청하세요. 방법은 다음과 같습니다.

  1. 이 API에 관해 제공된 문서를 자세히 읽어 봅니다. 권장사항을 따르지 않은 요청은 거부될 수 있습니다.
  2. 할당량 요청 양식을 작성합니다.
  3. 요청이 처리되었다고 알리는 확인 이메일을 기다립니다. 일반적으로 요청은 영업일 기준 2~3일 이내에 처리된다고 예상하면 됩니다.

참고: 프로젝트에 프로비저닝된 할당량에 관계없이 개별 앱 인스턴스는 분당 최대 요청 5건으로 제한됩니다. 이 한도를 초과하면 1분 이내의 남은 모든 요청에서 오류가 반환됩니다.

앱의 재시도 메커니즘을 구현할 때는 이런 동작을 염두에 두세요.

앱의 API 할당량에 관계없이 할당량 모니터링 및 알림을 설정하는 것이 좋습니다.

Google Play 서비스 버전 확인하기

SafetyNet Attestation API를 사용하기 전에 사용자의 기기에 올바른 버전의 Google Play 서비스가 설치되어 있는지 반드시 확인해야 합니다. 잘못된 버전이 설치되어 있으면 API 호출 후에 앱이 응답을 중지할 수도 있습니다. 잘못된 버전이 설치되어 있음이 앱에서 감지되면 사용자에게 기기의 Google Play 서비스 앱을 업데이트하도록 요청해야 합니다.

사용자 기기에 설치된 Google Play 서비스의 버전이 내가 사용 중인 Android SDK의 버전과 호환되는지 여부를 확인하려면 다음 코드 스니펫과 같이 isGooglePlayServicesAvailable() 메서드를 호출하세요.

if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
            == ConnectionResult.SUCCESS) {
      // The SafetyNet Attestation API is available.
    } else {
      // Prompt user to update Google Play services.
    }
    

Google Play 서비스 v13.0 이상을 실행하는 기기에서 SafetyNet Attestation API는 앱 제한 API 키도 지원합니다. 이 기능은 할당량 제한 API 키가 실수로 사용되거나 승인 없이 사용되는 위험을 줄입니다. 이 선택적 기능을 사용하려면 다음 코드 스니펫과 같이 기기에 있는 Google Play 서비스의 최소 버전이 v13.0 이상인지 여부를 확인하세요.

if (GoogleApiAvailability.getInstance()
        .isGooglePlayServicesAvailable(context, 13000000) ==
        ConnectionResult.SUCCESS) {
      // The SafetyNet Attestation API is available.
    } else {
      // Prompt user to update Google Play Services.
    }
    

SafetyNet 증명 요청하기

Google API 콘솔에서 Android Device Verification API에 유효한 API 키를 가져오고 나면 앱이 SafetyNet Attestation 서비스를 사용할 수 있습니다. 그러려면 다음 단계를 완료하세요.

  1. nonce를 가져옵니다.
  2. SafetyNet 증명을 요청합니다.
  3. 응답을 서버에 전송합니다.
  4. 서버에 수신된 응답을 다른 악용 방지 신호와 함께 사용하여 앱의 동작을 제어합니다.

앱의 응답성을 유지하려면 앱의 기본 실행 스레드 외부에서 위 단계를 완료하세요. 별도의 실행 스레드를 만드는 방법을 자세히 알아보려면 여러 스레드에 작업 보내기를 참고하세요.

로그인, 구매 이벤트, 새로운 인앱 상품 획득을 포함한 앱의 모든 중요한 작업을 보호하려면 위와 같이 확인해야 합니다. 하지만 SafetyNet Attestation API를 호출하면 지연 시간, 모바일 데이터 사용량, 배터리 사용량이 늘어나므로 보안과 사용성 사이의 균형을 찾는 것이 좋습니다. 예를 들어 로그인 시 SafetyNet 증명을 요청하고 최대 30분의 주기로 재확인을 실행하도록 선택할 수 있고 앱에서 증명을 요청하는 시기를 서버에서 결정하여 악의적 사용자가 개발자의 확인 시점을 예측하기 어렵게 할 수도 있습니다.

nonce 가져오기

SafetyNet Attestation API를 호출할 때 nonce를 전달해야 합니다. 결과로 생성되는 증명에 이 nonce가 포함되어 개발자는 이 증명이 API 호출에 속하며 공격자가 재생한 것이 아님을 판단할 수 있습니다.

SafetyNet 요청에 사용되는 nonce는 길이가 16바이트 이상이어야 합니다. nonce를 다양화하여 같은 nonce가 두 번 사용되지 않도록 하세요. 서버에 전송할 데이터에서 nonce의 일부를 파생하는 것이 좋습니다. 예를 들어 사용자 이름의 해시를 요청 타임스탬프와 연결하여 nonce를 만듭니다.

중요: nonce에 최대한 많은 데이터를 포함하세요. 그러면 공격자가 재생 공격을 실행하기가 더 어려워집니다. 예를 들어 사용자 이름에서 nonce를 파생하면 재생 공격이 동일 계정으로 제한됩니다. 하지만 구매 이벤트의 모든 세부정보에서 nonce를 파생하면 API의 응답 메시지가 이 구매 이벤트에만 제한적으로 사용됩니다.

API로부터 서명된 응답을 수신하면 항상 이 서명된 응답의 nonce와 서버에 전송된 메시지의 나머지 부분에서 재구성한 nonce를 비교하세요. 이렇게 확인하면 공격자가 정상 기기에서 수집한 서명된 증명을 악의적으로 작성한 다른 요청에 재사용할 수 없습니다.

암호화 기능 사용에 관한 자세한 내용은 암호화 사용 방법 가이드를 참고하세요.

증명 요청하기

Google Play 서비스 연결을 설정하고 nonce를 만들었으면 SafetyNet 증명을 요청할 준비가 되었습니다. 요청 응답이 즉각적이지 않을 수도 있으므로 콜백 리스너에서 서비스의 응답을 처리하도록 설정하는 것이 좋습니다. 다음 코드 스니펫은 리스너의 예를 보여줍니다.

Kotlin

    SafetyNet.getClient(this).attest(nonce, API_KEY)
        .addOnSuccessListener(this) {
            // Indicates communication with the service was successful.
            // Use response.getJwsResult() to get the result data.
        }
        .addOnFailureListener(this) { e ->
            // An error occurred while communicating with the service.
            if (e is ApiException) {
                // An error with the Google Play services API contains some
                // additional details.
                val apiException = e as ApiException

                // You can retrieve the status code using the
                // apiException.statusCode property.
            } else {
                // A different, unknown type of error occurred.
                Log.d(FragmentActivity.TAG, "Error: " + e.message)
            }
        }
    

자바

    // The nonce should be at least 16 bytes in length.
    // You must generate the value of API_KEY in the Google APIs dashboard.
    SafetyNet.getClient(this).attest(nonce, API_KEY)
        .addOnSuccessListener(this,
            new OnSuccessListener<SafetyNetApi.AttestationResponse>() {
                @Override
                public void onSuccess(SafetyNetApi.AttestationResponse response) {
                    // Indicates communication with the service was successful.
                    // Use response.getJwsResult() to get the result data.
                }
            })
        .addOnFailureListener(this, new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // An error occurred while communicating with the service.
                if (e instanceof ApiException) {
                    // An error with the Google Play services API contains some
                    // additional details.
                    ApiException apiException = (ApiException) e;
                    // You can retrieve the status code using the
                    // apiException.getStatusCode() method.
                } else {
                    // A different, unknown type of error occurred.
                    Log.d(TAG, "Error: " + e.getMessage());
                }
            }
        });
    

onSuccess() 메서드는 서비스와의 통신에 성공했음을 나타내지만 기기가 SafetyNet 증명을 전달했는지 여부는 나타내지 않습니다. 다음 섹션에서는 증명 결과를 읽고 무결성을 인증하는 방법을 설명합니다.

서버에 SafetyNet 증명 응답 전송하기

앱이 SafetyNet과 통신할 때 서비스는 SafetyNet 증명 결과가 들어 있는 응답을 제공하고 메시지의 무결성을 인증하는 데 도움을 주는 추가 정보를 포함합니다. 결과는 SafetyNetApi.AttestationResponse 개체로 제공됩니다. 이 개체의 getJwsResult() 메서드를 사용하여 요청의 데이터를 가져옵니다. 응답은 JSON Web Signature(JWS) 형식으로 지정됩니다.

유효성 검사와 사용을 위해 JWS 개체를 다시 서버에 보냅니다.

서버에서 SafetyNet 증명 응답 사용하기

다음 JWS 발췌 부분은 페이로드 데이터의 형식과 샘플 콘텐츠를 보여줍니다.

{
      "timestampMs": 9860437986543,
      "nonce": "R2Rra24fVm5xa2Mg",
      "apkPackageName": "com.package.name.of.requesting.app",
      "apkCertificateDigestSha256": ["base64 encoded, SHA-256 hash of the
                                      certificate used to sign requesting app"],
      "ctsProfileMatch": true,
      "basicIntegrity": true,
    }
    

서명된 증명의 페이로드에는 일반적으로 다음 필드가 있습니다.

응답의 타임스탬프

  • timestampMs: Google 서버에서 JWS 응답 메시지가 생성된 UNIX 에포크 이후의 시간(단위: 밀리초)입니다.

호출 앱에서 제공되는 데이터

  • nonce: 호출 앱이 API에 전달하는 일회용 토큰입니다.

호출 앱에 관한 데이터

  • apkPackageName: 호출 앱의 패키지 이름입니다.
  • apkCertificateDigestSha256: 호출 앱의 서명 인증서의 SHA-256 해시를 Base-64 인코딩으로 표현한 것입니다.

무결성 결과

  • ctsProfileMatch: 보다 엄격한 기기 무결성 결과입니다. ctsProfileMatch의 값이 true이면 앱을 실행하는 기기의 프로필이 Android 호환성 테스트를 통과한 기기의 프로필과 일치합니다.
  • basicIntegrity: 보다 관대한 기기 무결성 결과입니다. basicIntegrity의 값만 true이면 앱을 실행하는 기기가 조작되지 않았을 가능성이 높습니다. 그러나 기기가 반드시 Android 호환성 테스트를 통과했다고는 볼 수 없습니다.

    Android 호환성 테스트에 관한 자세한 내용은 Android 기기 설계호환성 테스트 도구 모음(CTS)을 참고하세요.

선택적 필드

  • error: 현재 API 요청과 관련된, 인코딩된 오류 정보입니다.
  • advice: 기기를 정상 상태로 되돌리는 방법을 제안합니다.

있을 수 있는 무결성 결과

JWS 메시지에는 기기 호환성 확인 결과를 나타내는 두 매개변수 ctsProfileMatchbasicIntegrity가 포함됩니다. 앱을 실행하는 기기의 상태에 따라 각 매개변수의 값이 다릅니다(표 1 참고).

표 1. 기기 상태가 basicIntegrityctsProfileMatch의 값에 영향을 주는 방식의 예

기기 상태 ctsProfileMatch의 값 basicIntegrity의 값
CTS에 통과한 인증 정품 기기 true true
잠금 해제된 부트로더가 있는 인증 기기 false true
정품 미인증 기기(예: 제조업체가 인증을 신청하지 않음) false true
맞춤 ROM이 있는 기기(루팅되지 않음) false true
에뮬레이터 false false
기기 없음(예: 프로토콜 에뮬레이션 스크립트) false false
시스템 무결성 손상의 신호(예: 루팅) false false
다른 활성 공격의 신호(예: API 후크) false false

오류 사례

JWS 메시지는 몇 가지 유형의 오류 상황을 보여줄 수도 있습니다.

  • null 결과는 서비스 호출이 성공적으로 완료되지 않았음을 나타냅니다.
  • JWS의 오류 매개변수는 네트워크 오류나 공격자가 가장한 오류와 같은 문제가 발생했음을 나타냅니다. 대부분의 오류는 일시적이며 서비스를 다시 호출하면 발생하지 않습니다. 몇 번 더 다시 시도해야 할 수 있으며 재시도 간의 지연 시간이 길어집니다.
  • 기기가 조작된(응답의 basicIntegrity가 false로 설정됨) 경우 결과에 호출 앱에 관한 apkPackageNameapkCertificateDigestSha256과 같은 데이터가 없을 수 있습니다. 이 상황은 Google 시스템에서 안정적으로 호출 앱을 확인할 수 없을 때 발생합니다.

서명된 증명에서 오류가 보고될 때 취할 조치는 다음과 같습니다.

  • 다시 시도합니다. 적법한 기기에서 발생하는 오류는 일시적이며 서비스를 다시 호출하면 발생하지 않습니다.
  • 관련 기기에서 앱이 API를 호출한 횟수가 초당 5회를 초과하지 않는지 확인하고 프로젝트의 API 할당량이 소진되지 않았는지 확인합니다.
  • 활동을 가장하기 위해 의도적으로 오류 사례를 트리거하는 공격자의 소행일 수도 있다고 가정합니다.

향후 확인 전달에 관한 조언

advice 매개변수가 있다면 이 매개변수는 SafetyNet Attestation API가 특정 결과에서 ctsProfileMatchbasicIntegrity를 false로 설정하는 이유를 설명하는 정보를 제공합니다. 이 매개변수의 값에는 다음 예와 같이 문자열 목록이 포함됩니다.

{"advice": "LOCK_BOOTLOADER,RESTORE_TO_FACTORY_ROM"}
    

앱에서 개발자는 advice 매개변수의 값을 향후 SafetyNet 증명을 전달하는 사용자 친화적인 메시지로 전환할 수 있습니다. 다음 목록을 참고하세요.

LOCK_BOOTLOADER
사용자는 기기의 부트로더를 잠가야 합니다.
RESTORE_TO_FACTORY_ROM
사용자는 기기를 공장 출고 시 ROM으로 복원해야 합니다.

SafetyNet 증명 응답 인증하기

SafetyNet 증명 응답이 실제로 SafetyNet 서비스에서 왔는지, 이 증명 응답에 요청과 일치하는 데이터가 있는지 확인하는 단계를 취해야 합니다.

JWS 메시지의 출처를 확인하려면 다음 단계를 완료하세요.

  1. JWS 메시지에서 SSL 인증서 체인을 추출합니다.
  2. SSL 인증서 체인의 유효성을 검사하고 SSL 호스트 이름 매칭을 사용하여 호스트 이름 attest.android.com에 리프 인증서가 발급되었는지 확인합니다.
  3. 인증서를 사용하여 JWS 메시지의 서명을 인증합니다.
  4. JWS 메시지의 데이터가 원래 요청의 데이터와 일치하는지 확인합니다. 특히 타임스탬프의 유효성 검사를 하고 nonce, 패키지 이름, 앱 서명 인증서의 해시가 예상 값과 일치하는지 확인합니다.

GitHub의 android-play-safetynet 샘플 API 사용법에 나온 것과 같은 표준 암호화 솔루션을 사용하여 JWS 문을 인증해야 합니다.

초기 테스트 및 개발 중에(프로덕션 중 아님) JWS 문의 서명을 인증하기 위한 온라인 API를 호출할 수 있습니다. 이 프로세스도 GitHub의 android-play-safetynet 샘플 API 사용법에서 확인할 수 있습니다. 온라인 인증 API는 초기 단계 테스트를 목적으로만 사용되며, 개발자는 일일 요청 10,000건의 고정 할당량을 가집니다.

중요: 온라인 인증 API를 사용하면 JWS 메시지가 SafetyNet Attestation API의 서버에서 서명되었다는 사실만 확인됩니다. 이 API는 페이로드의 필드가 개발자의 서비스에서 기대하는 값과 일치하는지 여부는 인증할 수 없습니다.

예상치 못한 사례를 고려한 계획

변경사항 및 중단을 고려하여 사용량을 계획하는 것이 좋습니다.

API 변경사항
언제든지 결과에 새로운(실험적) 필드가 나타날 수 있습니다. 이러한 추가 필드가 파서 또는 사용 로직을 중단하지 않아야 합니다. 특히 실험적 필드가 SafetyNet API 클라이언트 메일링 리스트에 발표되기 전에는 이러한 필드에 의존하지 마세요.
SafetyNet Attestation API가 다운됨

드물기는 하지만 SafetyNet Attestation API를 사용할 수 없는 이벤트가 발생하는 경우 이 API 사용자는 이 API 및 응답의 사용 가능 여부품질에의 의존성을 크게 제어하는 서버 측 기능을 구축하는 것이 좋습니다.

일반적인 전략으로는 앱에 이 API 호출을 중지하도록 동적으로 안내하는 기능뿐 아니라 기기 기반 및 사용자 기반의 허용 목록을 채택하여 특정 부류의 기기 및 사용자와 관련한 SafetyNet Attestation API 결과를 무시하는 방식이 있습니다.

샘플 코드

SafetyNet API 작업에 관한 추가 안내는 GitHub에서 제공하는 샘플 코드에서 확인하세요.

알림 목록

SafetyNet API 클라이언트 메일링 리스트에 참여하여 SafetyNet Attestation API에 관한 업데이트를 수신하는 것이 좋습니다.

의견

API에 관한 의견을 주시기 바랍니다. Google은 이 API의 새로운 기능을 최우선으로 하는 데 의견을 반영합니다.

자세히 알아보기

SafetyNet Attestation API 사용 시 권장사항을 자세히 알아보려면 다음 링크를 참고하세요.

추가 서비스 약관

SafetyNet API에 액세스하거나 SafetyNet API를 사용하면 Google API 서비스 약관 및 이 추가 약관에 동의하게 됩니다. SafetyNet API에 액세스하기 전에 모든 관련 약관 및 정책을 자세히 읽고 숙지하시기 바랍니다.

SafetyNet 서비스 약관

필드 내 관찰에서 대량으로 수집되는 여느 데이터와 마찬가지로 거짓양성과 거짓음성의 가능성이 모두 있습니다. Google은 파악한 한도 내에서 최대한 데이터를 제시합니다. Google은 감지 메커니즘을 광범위하게 테스트하여 정확성을 보장하며 시간에 지남에 따라 정확성을 유지하도록 감지 방법을 개선하기 위해 노력하고 있습니다.

개발자는 모든 관련 법률, 규정, 타사 권리를 준수할 것에 동의합니다(데이터나 소프트웨어의 수입 또는 수출과 관련된 법률, 개인정보 보호법, 현지 법률을 포함하며 이에 국한되지 않음). 개발자는 불법 행위나 타사 권리 침해를 장려하거나 조장하는 데 API를 사용하지 않으며 Google(또는 Google 제휴사)과의 다른 서비스 약관을 위반하지 않습니다.

개발자는 하드웨어 및 소프트웨어 정보(예: 기기 및 애플리케이션 데이터와 SafetyNet 증명 결과)를 수집하고 분석을 위해 이러한 데이터를 Google에 전송하여 SafetyNet API가 작동한다는 점을 인정하고 이해합니다. Google API 서비스 약관의 3(d)항에 따라, SafetyNet API를 사용하면 이러한 데이터를 수집하고 Google과 공유하는 데 필요한 고지 또는 동의를 제공하는 책임이 개발자 자신에게 있음에 동의하는 것으로 간주됩니다.