안전한 사용자 인증

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가 성공하지 못하여 사용자가 직접 OTP를 입력해야 하는 경우 사기를 당했을 수 있다는 경고 신호일 수 있습니다.

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

자세히 알아보기

권장사항에 대한 자세한 내용은 다음 리소스를 확인하세요.