다음을 통해 공유


CryptoObject 만들기

지문 인증 결과의 무결성은 애플리케이션에 중요합니다. 애플리케이션에서 사용자의 ID를 아는 방법입니다. 이론적으로 맬웨어는 지문 스캐너에서 반환된 결과를 가로채 조작할 수 있습니다. 이 섹션에서는 지문 결과의 유효성을 유지하는 한 가지 방법을 설명합니다.

FingerprintManager.CryptoObject는 Java 암호화 API를 중심으로 하는 래퍼로, FingerprintManager가 인증 요청의 무결성을 보호하는 데 사용합니다. 일반적으로 Javax.Crypto.Cipher 개체는 지문 스캐너의 결과를 암호화하는 메커니즘입니다. Cipher 개체 자체는 애플리케이션이 Android 키 저장소 API를 사용하여 만든 키를 사용합니다.

이러한 클래스가 모두 함께 작동하는 방식을 이해하기 위해, 먼저 CryptoObject를 만드는 방법을 보여 주는 다음 코드를 살펴본 후 자세히 설명하겠습니다.

public class CryptoObjectHelper
{
    // This can be key name you want. Should be unique for the app.
    static readonly string KEY_NAME = "com.xamarin.android.sample.fingerprint_authentication_key";

    // We always use this keystore on Android.
    static readonly string KEYSTORE_NAME = "AndroidKeyStore";

    // Should be no need to change these values.
    static readonly string KEY_ALGORITHM = KeyProperties.KeyAlgorithmAes;
    static readonly string BLOCK_MODE = KeyProperties.BlockModeCbc;
    static readonly string ENCRYPTION_PADDING = KeyProperties.EncryptionPaddingPkcs7;
    static readonly string TRANSFORMATION = KEY_ALGORITHM + "/" +
                                            BLOCK_MODE + "/" +
                                            ENCRYPTION_PADDING;
    readonly KeyStore _keystore;

    public CryptoObjectHelper()
    {
        _keystore = KeyStore.GetInstance(KEYSTORE_NAME);
        _keystore.Load(null);
    }

    public FingerprintManagerCompat.CryptoObject BuildCryptoObject()
    {
        Cipher cipher = CreateCipher();
        return new FingerprintManagerCompat.CryptoObject(cipher);
    }

    Cipher CreateCipher(bool retry = true)
    {
        IKey key = GetKey();
        Cipher cipher = Cipher.GetInstance(TRANSFORMATION);
        try
        {
            cipher.Init(CipherMode.EncryptMode, key);
        } catch(KeyPermanentlyInvalidatedException e)
        {
            _keystore.DeleteEntry(KEY_NAME);
            if(retry)
            {
                CreateCipher(false);
            } else
            {
                throw new Exception("Could not create the cipher for fingerprint authentication.", e);
            }
        }
        return cipher;
    }

    IKey GetKey()
    {
        IKey secretKey;
        if(!_keystore.IsKeyEntry(KEY_NAME))
        {
            CreateKey();
        }

        secretKey = _keystore.GetKey(KEY_NAME, null);
        return secretKey;
    }

    void CreateKey()
    {
        KeyGenerator keyGen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, KEYSTORE_NAME);
        KeyGenParameterSpec keyGenSpec =
            new KeyGenParameterSpec.Builder(KEY_NAME, KeyStorePurpose.Encrypt | KeyStorePurpose.Decrypt)
                .SetBlockModes(BLOCK_MODE)
                .SetEncryptionPaddings(ENCRYPTION_PADDING)
                .SetUserAuthenticationRequired(true)
                .Build();
        keyGen.Init(keyGenSpec);
        keyGen.GenerateKey();
    }
}

샘플 코드는 애플리케이션에서 만든 키를 사용하여 각 CryptoObject에 대한 새 Cipher를 만듭니다. 키는 CryptoObjectHelper 클래스의 시작 부분에 설정된 KEY_NAME 변수로 식별됩니다. GetKey 메서드는 Android 키 저장소 API를 사용하여 키를 시도하고 검색합니다. 키가 없는 경우 CreateKey 메서드는 애플리케이션에 대한 새 키를 만듭니다.

암호화는 Cipher.GetInstance 호출을 사용하여 인스턴스화되고 변환(데이터 암호화 및 암호 해독 방법을 암호에 알려주는 문자열 값)을 취합니다. Cipher.Init를 호출하면 애플리케이션의 키를 제공하여 암호 초기화를 완료합니다.

Android가 키를 무효화할 수 있는 몇 가지 상황이 있습니다.

  • 디바이스에 새 지문이 등록되었습니다.
  • 디바이스에 등록된 지문이 없습니다.
  • 사용자가 화면 잠금을 사용하지 않도록 설정했습니다.
  • 사용자가 화면 잠금(화면 잠금 유형 또는 사용된 PIN/패턴)을 변경했습니다.

이 경우 Cipher.InitKeyPermanentlyInvalidatedException를 throw합니다. 위의 샘플 코드에서는 해당 예외를 트래핑하고 키를 삭제한 다음 새 키를 만듭니다.

다음 섹션에서는 키를 만들어 디바이스에 저장하는 방법을 설명합니다.

비밀 키 만들기

CryptoObjectHelper 클래스는 Android KeyGenerator를 사용하여 키를 만들고 디바이스에 저장합니다. KeyGenerator 클래스는 키를 만들 수 있지만 만들 키의 유형에 대한 메타데이터가 필요합니다. 이 정보는 KeyGenParameterSpec 클래스의 인스턴스에서 제공합니다.

KeyGeneratorGetInstance 팩터리 메서드를 사용하여 인스턴스화됩니다. 이 샘플 코드는 AES(Advanced Encryption Standard)를 암호화 알고리즘으로 사용합니다. AES는 데이터를 고정 크기의 블록으로 분할하고 각 블록을 암호화합니다.

그런 다음 KeyGenParameterSpec.Builder를 사용하여 KeyGenParameterSpec을 만듭니다. KeyGenParameterSpec.Builder은 만들 키에 대한 다음 정보를 래핑합니다.

  • 키의 이름입니다.
  • 키는 암호화 및 암호 해독 모두에 유효해야 합니다.
  • 샘플 코드 BLOCK_MODE 에서 암호화 블록 체인(KeyProperties.BlockModeCbc)으로 설정됩니다. 즉, 각 블록이 이전 블록(각 블록 간에 종속성 만들기)과 함께 XORed됨을 의미합니다.
  • CryptoObjectHelperPKCS7(공개 키 암호 표준 #7)을 사용하여 크기가 모두 동일하도록 블록을 채울 바이트를 생성합니다.
  • SetUserAuthenticationRequired(true)는 키를 사용하려면 사용자 인증이 필요함을 의미합니다.

KeyGenParameterSpec이 만들어진 후에는 키를 생성하여 디바이스에 안전하게 저장하는 KeyGenerator를 초기화하는 데 사용됩니다.

CryptoObjectHelper 사용

이제 샘플 코드에서 CryptoObjectHelper 클래스에 CryptoWrapper를 만들기 위한 논리를 상당 부분 캡슐화했으므로, 이 가이드의 시작 부분부터 다시 코드를 수행하여 CryptoObjectHelper를 사용하여 암호를 만들고 지문 스캐너를 시작하겠습니다.

protected void FingerPrintAuthenticationExample()
{
    const int flags = 0; /* always zero (0) */
    
    CryptoObjectHelper cryptoHelper = new CryptoObjectHelper();
    cancellationSignal = new Android.Support.V4.OS.CancellationSignal();
    
    // Using the Support Library classes for maximum reach
    FingerprintManagerCompat fingerPrintManager = FingerprintManagerCompat.From(this);
    
    // AuthCallbacks is a C# class defined elsewhere in code.
    FingerprintManagerCompat.AuthenticationCallback authenticationCallback = new MyAuthCallbackSample(this);

    // Here is where the CryptoObjectHelper builds the CryptoObject. 
    fingerprintManager.Authenticate(cryptohelper.BuildCryptoObject(), flags, cancellationSignal, authenticationCallback, null);
}

CryptoObject를 만드는 방법을 살펴보았으므로, 계속해서 FingerprintManager.AuthenticationCallbacks을 사용하여 지문 스캐너 서비스의 결과를 Android 애플리케이션으로 전송하는 방법을 살펴보겠습니다.