안전한 사용자 인증

Android에서 인증 시스템을 보호하려면 특히 사용자의 은행 및 이메일 계정과 같은 민감한 계정의 경우 비밀번호 기반 모델에서 벗어나는 것이 좋습니다. 사용자가 설치하는 일부 앱은 의도가 좋지 않을 수 있으며 사용자를 피싱하려고 시도할 수 있습니다.

또한 승인된 사용자만 기기를 사용한다고 가정하지 마세요. 휴대전화 도난은 흔한 문제이며 공격자는 잠금 해제된 기기를 타겟팅하여 사용자 데이터나 금융 앱에서 직접 이익을 얻습니다. 모든 민감한 앱은 생체 인식 인증을 통해 적절한 인증 제한 시간 (15분?)을 구현하고 송금과 같은 민감한 작업 전에 추가 인증을 요구해야 합니다.

생체 인식 인증 대화상자

생체 인식 라이브러리는 얼굴 인식이나 지문 인식과 같은 생체 인식 인증을 요청하는 프롬프트를 표시하는 함수 집합을 제공합니다. 하지만 생체 인식 프롬프트는 알려진 어깨너머 엿보기 위험이 있는 LSKF로 대체되도록 구성할 수 있습니다. 민감한 앱의 경우 생체 인식이 PIN으로 대체되지 않도록 하는 것이 좋습니다. 생체 인식 재시도를 모두 소진한 후 사용자는 기다리거나 비밀번호로 다시 로그인하거나 계정을 재설정할 수 있습니다. 계정 재설정에는 기기에서 쉽게 액세스할 수 없는 요소가 필요합니다 (아래 권장사항).

이 기능이 사기 및 휴대전화 도난을 완화하는 데 도움이 되는 방법

사기를 방지하는 데 도움이 될 수 있는 한 가지 특정 사용 사례는 거래 전에 앱 내에서 생체 인식 인증을 요청하는 것입니다. 사용자가 금융 거래를 하려고 하면 생체 인식 대화상자가 표시되어 거래를 하는 사용자가 실제로 의도한 사용자인지 확인합니다. 이 권장사항은 공격자가 LSKF를 알고 있는지 여부와 관계없이 공격자가 기기를 훔치는 것을 방지합니다. 공격자가 기기의 소유자인지 확인해야 하기 때문입니다.

보안 수준을 추가로 높이려면 앱 개발자가 클래스 3 생체 인식 인증을 요청하고 은행 및 금융 거래에 CryptoObject를 활용하는 것이 좋습니다.

구현

  1. androidx.biometric 라이브러리를 포함해야 합니다.
  2. 사용자가 인증되기를 원하는 로직을 보유한 활동 또는 프래그먼트에 생체 인식 로그인 대화상자를 포함합니다.

Kotlin

private var executor: Executor? = null
private var biometricPrompt: BiometricPrompt? = null
private var promptInfo: BiometricPrompt.PromptInfo? = null

fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_login)
  executor = ContextCompat.getMainExecutor(this)
  biometricPrompt = BiometricPrompt(this@MainActivity,
    executor, object : AuthenticationCallback() {
      fun onAuthenticationError(
        errorCode: Int,
        @NonNull errString: CharSequence
      ) {
        super.onAuthenticationError(errorCode, errString)
        Toast.makeText(
          getApplicationContext(),
          "Authentication error: $errString", Toast.LENGTH_SHORT
        )
          .show()
      }

      fun onAuthenticationSucceeded(
        @NonNull result: BiometricPrompt.AuthenticationResult?
      ) {
        super.onAuthenticationSucceeded(result)
        Toast.makeText(
          getApplicationContext(),
          "Authentication succeeded!", Toast.LENGTH_SHORT
        ).show()
      }

      fun onAuthenticationFailed() {
        super.onAuthenticationFailed()
        Toast.makeText(
          getApplicationContext(), "Authentication failed",
          Toast.LENGTH_SHORT
        )
          .show()
      }
    })
  promptInfo = Builder()
    .setTitle("Biometric login for my app")
    .setSubtitle("Log in using your biometric credential")
    .setNegativeButtonText("Use account password")
    .build()

  // Prompt appears when user clicks "Log in".
  // Consider integrating with the keystore to unlock cryptographic operations,
  // if needed by your app.
  val biometricLoginButton: Button = findViewById(R.id.biometric_login)
  biometricLoginButton.setOnClickListener { view ->
    biometricPrompt.authenticate(
      promptInfo
    )
  }
}

Java

private Executor executor;
private BiometricPrompt biometricPrompt;
private BiometricPrompt.PromptInfo promptInfo;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login);
    executor = ContextCompat.getMainExecutor(this);
    biometricPrompt = new BiometricPrompt(MainActivity.this,
            executor, new BiometricPrompt.AuthenticationCallback() {
        @Override
        public void onAuthenticationError(int errorCode,
                @NonNull CharSequence errString) {
            super.onAuthenticationError(errorCode, errString);
            Toast.makeText(getApplicationContext(),
                "Authentication error: " + errString, Toast.LENGTH_SHORT)
                .show();
        }

        @Override
        public void onAuthenticationSucceeded(
                @NonNull BiometricPrompt.AuthenticationResult result) {
            super.onAuthenticationSucceeded(result);
            Toast.makeText(getApplicationContext(),
                "Authentication succeeded!", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onAuthenticationFailed() {
            super.onAuthenticationFailed();
            Toast.makeText(getApplicationContext(), "Authentication failed",
                Toast.LENGTH_SHORT)
                .show();
        }
    });

    promptInfo = new BiometricPrompt.PromptInfo.Builder()
            .setTitle("Biometric login for my app")
            .setSubtitle("Log in using your biometric credential")
            .setNegativeButtonText("Use account password")
            .build();

    // Prompt appears when the user clicks "Log in".
    // Consider integrating with the keystore to unlock cryptographic operations,
    // if needed by your app.
    Button biometricLoginButton = findViewById(R.id.biometric_login);
    biometricLoginButton.setOnClickListener(view -> {
            biometricPrompt.authenticate(promptInfo);
    });
}

권장사항

Codelab을 시작하여 생체 인식에 대해 자세히 알아보는 것이 좋습니다.

사용 사례에 따라 명시적인 사용자 작업이 있거나 없는 대화상자를 구현할 수 있습니다. 사기를 방지하려면 모든 거래에 명시적인 사용자 작업이 포함된 생체 인식 대화상자를 추가하는 것이 좋습니다. 인증을 추가하면 UX에 불편함이 발생할 수 있다는 점을 잘 알고 있습니다. 하지만 은행 거래에서 처리되는 정보의 특성과 생체 인식 인증이 다른 인증 방법보다 원활하다는 점을 고려할 때 이 수준의 탐색을 추가해야 한다고 생각합니다.

생체 인식 인증에 대해 자세히 알아보기

패스키

패스키는 비밀번호를 대체하는 더 안전하고 간편한 방법입니다. 패스키는 공개 키 암호화를 사용하여 사용자가 지문이나 얼굴 인식과 같은 기기의 화면 잠금 메커니즘을 사용하여 앱과 웹사이트에 로그인할 수 있도록 지원합니다. 이를 통해 사용자는 비밀번호를 기억하고 관리할 필요가 없으며 보안이 크게 개선됩니다.

패스키는 단일 단계로 다중 인증 요구사항을 충족할 수 있으며, 비밀번호와 OTP 코드를 모두 대체하여 피싱 공격에 대한 강력한 보호를 제공하고 SMS 또는 앱 기반 일회용 비밀번호의 사용자 환경 문제를 방지합니다. 패스키는 표준화되어 있으므로 단일 구현으로 사용자의 모든 기기, 브라우저, 운영체제에서 비밀번호 없는 환경을 지원할 수 있습니다.

Android에서는 패스키, 비밀번호, 제휴 로그인 (예: Google 계정으로 로그인) 등 주요 인증 방법을 통합하는 인증 관리자 Jetpack 라이브러리를 사용하여 패스키가 지원됩니다.

사기 완화에 도움이 되는 방법

패스키는 등록된 앱과 웹사이트에서만 작동하므로 피싱 공격으로부터 사용자를 보호합니다.

패스키의 핵심 구성요소는 암호화 비공개 키입니다. 일반적으로 이 비공개 키는 노트북이나 휴대전화와 같은 기기에만 있으며 Google 비밀번호 관리자와 같은 사용자 인증 정보 제공업체 (비밀번호 관리자라고도 함)에 의해 기기 간에 동기화됩니다. 패스키가 생성되면 해당 공개 키만 온라인 서비스에 저장됩니다. 로그인하는 동안 서비스는 비공개 키를 사용하여 공개 키의 챌린지에 서명합니다. 이 코드는 기기 중 하나에서만 생성할 수 있습니다. 또한 이 작업이 발생하려면 기기나 사용자 인증 정보 저장소를 잠금 해제해야 하므로 도난당한 휴대전화와 같은 무단 로그인으로부터 보호할 수 있습니다.

잠금 해제된 기기가 도난당한 경우 무단 액세스를 방지하려면 패스키를 적절한 인증 제한 시간 창과 결합해야 합니다. 기기를 훔친 공격자는 이전 사용자가 로그인했다는 이유만으로 애플리케이션을 사용할 수 없어야 합니다. 대신 사용자 인증 정보는 정기적인 간격 (예: 15분마다)으로 만료되어야 하며 사용자는 화면 잠금 재인증을 통해 신원을 확인해야 합니다.

도난범이 다른 기기에서 사용할 비밀번호를 훔칠 수 없으므로 패스키는 휴대전화를 도난당한 경우에도 사용자를 보호합니다. 패스키는 기기별로 다르기 때문입니다. Google 비밀번호 관리자를 사용하고 휴대전화를 도난당한 경우 컴퓨터와 같은 다른 기기에서 Google 계정에 로그인하여 도난당한 휴대전화에서 원격으로 로그아웃할 수 있습니다. 이렇게 하면 저장된 패스키를 포함하여 도난당한 휴대전화의 Google 비밀번호 관리자를 사용할 수 없게 됩니다.

최악의 경우 도난당한 기기를 복구할 수 없으면 패스키를 생성하고 동기화한 사용자 인증 정보 제공업체에 의해 패스키가 새 기기에 다시 동기화됩니다. 예를 들어 사용자가 Google 비밀번호 관리자를 선택하여 패스키를 생성한 경우, Google 계정에 다시 로그인하고 이전 기기의 화면 잠금을 제공하여 새 기기에서 패스키에 액세스할 수 있습니다.

Google 비밀번호 관리자의 패스키 보안 도움말에서 자세히 알아보세요.

구현

패스키는 Android 9 (API 수준 28) 이상을 실행하는 기기에서 지원됩니다. 비밀번호와 Google 계정으로 로그인은 Android 4.4부터 지원됩니다. 패스키를 시작하려면 다음 단계를 따르세요.

  1. 인증 관리자 Codelab을 따라 패스키 구현 방법을 초기 단계에서 파악하세요.
  2. 패스키 사용자 환경 디자인 가이드라인을 검토합니다. 이 문서에서는 사용 사례에 권장되는 흐름을 보여줍니다.
  3. 가이드에 따라 인증 관리자를 학습합니다.
  4. 앱의 사용자 인증 정보 관리자 및 패스키 구현을 계획합니다. 디지털 애셋 링크 지원 추가를 계획합니다.

패스키를 만들고, 등록하고, 인증하는 방법에 관한 자세한 내용은 개발자 문서를 참고하세요.

계정 보안 재설정

잠금 해제된 기기에 액세스할 수 있는 승인되지 않은 공격자 (예: 휴대전화를 강탈당한 경우)는 민감한 앱, 특히 은행 또는 현금 앱에 액세스하려고 시도합니다. 앱이 생체 인식 확인을 구현하는 경우 공격자는 계정을 재설정하여 침입하려고 시도합니다. 계정 재설정 흐름은 이메일이나 SMS OTP 재설정 링크와 같이 기기에서 쉽게 액세스할 수 있는 정보에만 의존해서는 안 됩니다.

다음은 앱의 재설정 흐름에 통합할 수 있는 일반적인 권장사항입니다.

  • OTP 외에 얼굴 인식
  • 보안 질문
  • 지식 요소 (예: 어머니의 결혼 전 성, 출생 도시, 좋아하는 노래)
  • 신분증 확인

SMS Retriever API

SMS Retriever API를 사용하면 Android 앱에서 SMS 기반 사용자 인증을 자동으로 실행할 수 있습니다. 이렇게 하면 사용자가 인증 코드를 수동으로 입력하지 않아도 됩니다. 또한 이 API는 사용자에게 RECEIVE_SMS 또는 READ_SMS과 같은 추가적이고 잠재적으로 위험한 앱 권한을 요청하지 않습니다. 하지만 SMS는 기기에 대한 무단 로컬 액세스를 방지하기 위한 유일한 사용자 인증으로 사용하면 안 됩니다.

사기 완화에 도움이 되는 방법

일부 사용자는 SMS 코드를 유일한 인증 요소로 사용하므로 사기가 쉽게 발생할 수 있습니다.

SMS Retriever API를 사용하면 앱이 사용자 상호작용 없이 SMS 코드를 직접 가져올 수 있으며 사기에 대한 보호 수준을 제공할 수 있습니다.

구현

SMS Retriever API 구현에는 Android와 서버의 두 부분이 있습니다.

Android: (가이드)

  1. 사용자의 전화번호를 가져옵니다.
  2. SMS 리트리버 클라이언트를 시작합니다.
  3. 전화번호를 서버로 보냅니다.
  4. 인증 메시지를 수신합니다.
  5. OTP를 서버로 전송합니다.

서버: (가이드)

  1. 인증 메시지를 구성합니다.
  2. SMS로 인증 메시지를 전송합니다.
  3. OTP가 반환되면 이를 확인합니다.

권장사항

앱이 통합되고 사용자의 전화번호가 SMS Retriever API로 확인되면 OTP를 가져오려고 시도합니다. 성공하면 SMS가 기기에서 자동으로 수신되었다는 강력한 신호입니다. 성공하지 못하고 사용자가 OTP를 수동으로 입력해야 하는 경우 사용자가 사기를 당하고 있을 수 있다는 경고 신호일 수 있습니다.

SMS는 잠금 해제된 기기를 강탈하는 공격자나 SIM 복제 공격과 같은 로컬 공격의 여지를 남기므로 유일한 사용자 확인 메커니즘으로 사용해서는 안 됩니다. 가능하면 생체 인식을 사용하는 것이 좋습니다. 생체 인식 센서를 사용할 수 없는 기기에서 사용자 인증은 현재 기기에서 쉽게 얻을 수 없는 요소를 하나 이상 사용해야 합니다.

자세히 알아보기

권장사항에 관한 자세한 내용은 다음 리소스를 참고하세요.