对React Native 生物识别库的安全性分析研究
2021-06-01 11:18:54 Author: www.4hou.com(查看原文) 阅读量:124 收藏

0x01 基础概述

许多应用程序要求用户在访问任何内容之前先在应用程序内部进行身份验证。根据其中包含的信息的敏感性,应用程序通常有两种方法:

· 用户进行一次身份验证,然后保持身份验证,直到他们手动注销为止;

· 用户保持登录状态的时间不会太长,并且在一段时间不活动后必须重新进行身份验证。

第一种策略虽然对用户非常方便,但显然不是很安全。第二种方法相当安全,但是对用户来说却是一个负担,因为他们每次都必须输入其凭据。实施生物特征认证可以减轻这种负担,因为认证方法对用户而言变得相当容易和快速。

开发人员通常不会从头开始编写与操作系统的集成,而通常会使用框架或第三方提供的库。当使用跨平台的移动应用程序框架(例如Flutter,Xamarin或React Native)时,尤其如此,其中需要在特定于平台的代码中实现此类集成。由于身份验证是一项对安全性至关重要的功能,因此重要的是验证这些第三方库是否已安全地实现了所需的功能。

在此博客文章中,我们将首先看一下生物特征认证的基本概念,以便我们随后可以研究提供生物特征认证支持的多个React Native库的安全性。

我们分析了五个提供生物特征认证的React Native库。对于这些库中的每一个,我们分析了生物识别认证的实现方式以及它是否正确使用操作系统提供的加密原语来保护敏感数据。

我们的分析表明,五个分析的库中只有一个提供了基于结果的安全生物特征认证。其他库仅提供基于事件的身份验证,这是不安全的,因为仅对生物特征认证进行验证,而实际上并未以密码方式保护任何数据。

下表总结了每个分析库提供的生物特征认证类型:

image.png

0x02 生物特征识别

生物特征认证允许用户使用其生物特征数据(指纹或面部识别)对应用进行认证。通常,可以通过两种不同的方式来实现生物特征认证:

· 基于事件:生物统计API仅将身份验证尝试的结果返回给应用程序(“成功”或“失败”),这种方法被认为是不安全的;

· 基于结果:身份验证成功后,生物统计API会检索一些加密对象(例如解密密钥)并将其返回给应用程序。失败时,不会返回任何加密对象。

基于事件的身份验证是不安全的,因为它仅包含返回的布尔值(或类似值)。因此,可以使用代码工具(例如Frida)通过修改返回值或手动触发成功流程来绕过它。如果实现是基于事件的,则还意味着敏感信息以不安全的方式存储在某处:在应用程序从生物识别API接收到“成功”之后,它仍将需要使用一些身份验证向后端进行用户身份验证。一种凭证,将从本地存储中检索,无需解密密钥即可完成此操作,否则,实现将不是基于事件的,这意味着凭据无需适当加密即可存储在本地存储中的某个位置。

另一方面,良好的基于结果的生物特征认证将无法通过Frida之类的工具来绕过。要实现基于结果的安全生物特征认证,应用程序必须使用硬件支持的生物特征API。

存储凭证

尽管我们在此文章中使用“凭证”一词,但我们并不主张存储用户的凭证(即用户名和密码)。无论用户的凭据存储方式如何,将其存储在设备上对于高安全性应用程序从来都不是一个好主意。相反,上述“凭证”应该是专用于生物认证的凭证(例如高熵字符串),这些凭证是在生物认证的激活期间生成的。

要在Android上实施基于结果的安全生物身份验证,必须生成需要用户身份验证的加密密钥。这可以通过使用setUserAuthenticationRequired生成密钥时的方法来实现。每当应用程序尝试访问密钥时,Android将确保提供有效的生物识别信息。然后必须使用密钥来执行加密操作,从而解锁凭据,然后可以将凭据发送到后端。这是通过向CryptoObject生物识别API提供以上一个密钥开头的来完成的。例如,BiometricPrompt类提供了一个authenticate方法,该方法采用CryptoObject作为一个论点。然后,可以通过result参数在成功回调方法中获得对该键的引用。可以在f-secure的这篇非常不错的博客文章中找到有关在Android上实现安全生物特征认证的更多信息。

https://labs.f-secure.com/blog/how-secure-is-your-android-keystore-authentication/

在iOS上,必须生成一个加密密钥并将其存储在key串中。key串中的条目必须设置有访问控制标志biometryAny。然后必须使用密钥执行加密操作,以解锁可发送到后端的凭据。通过向key串查询受密钥保护的biometryAnyiOS,iOS将确保用户使用其生物识别数据解锁所需的key。或者,我们可以将凭据本身直接存储在biometryAny保护下,而不是将密码密钥存储在“key串”中。

指纹认证

Android和iOS允许你信任“设备上已注册的所有指纹”或“设备上当前已注册的所有指纹”。在后一种情况下,如果添加或删除了指纹,则加密对象将无法使用。 对于Android,默认值为“所有指纹”,而在将指纹添加到设备的情况下,你可以使用setInvalidatedByBiometricEnrollment删除CryptoObject。 对于iOS,可以在biometryAny和biometryCurrentSet之间进行选择。虽然“当前已注册”选项是最安全的,但在本文中,我们不会对这种区别给予重视。

基于事件的身份验证真的不安全吗?是的。这完全取决于你的移动应用程序的威胁模型。应用程序提供基于结果的身份验证的要求是OWASP MASVSMSTG-AUTH-8)中的2级要求。级别2表示你的应用程序正在处理敏感信息,通常用于金融,医疗或政府部门的应用程序。image-20210415212004481

image-20210415212004481.png

OWASP MASVS验证级别

如果你的应用程序使用基于事件的生物特征认证,则将发生特定的攻击,这些攻击将使用户的凭据可供攻击者使用:

· 使用取证软件进行物理提取

· 从备份文件中提取数据(例如iTunes备份或adb备份)

· 具有root权限访问设备的恶意软件

最后一个示例也将能够攻击使用基于结果的生物特征认证的应用程序,因为有可能在凭据已在内存中解密后立即注入到应用程序中,但这种攻击的门槛比只需复制应用程序的本地存储。

0x03 React Native 框架

React Native是Facebook创建的开源移动应用程序框架。该框架建立在ReactJS之上,允许使用JavaScript进行跨平台的移动应用程序开发。这使开发人员可以使用HTML,CSS和JavaScript一次在不同平台上开发移动应用程序。在过去的几年中,它已经获得了很大的吸引力,现在被许多开发人员使用。

虽然是跨平台框架,但某些功能仍需要在本机Android(Java或Kotlin)或iOS(Objective-C或Swift)中进行开发。为了摆脱这种需求,许多库已经开始关注平台特定的代码并提供可直接在React Native中使用的JavaScript API。

生物特征认证是一种这样的功能,仍然需要实现平台特定的代码。因此,已经创建了许多库,以减轻开发人员必须在不同平台上分别实现它们的负担。

0x04 React Native 生物特征识别库

在本节中,我们将研究五个库,它们为React Native应用程序提供生物特征认证。我们将不仅检查文档,还检查源代码以验证实现是否安全。根据Google在“生物特征API react native”方面的最佳结果,我们选择了以下库:

· react-native-touch-id

· expo-local-authentication

· react-native-fingerprint-scanner

· react-native-fingerprint-android

· react-native-biometrics

对于每个库,在撰写此文章时,我们已链接到最新的提交,如果要使用它们,请使用最新版本的库。

React-native-touch-id

GitHub:https : //github.com/naoufal/react-native-touch-id/

该库不再由其开发人员维护,因此无论我们的分析结论如何,都不应再使用该库。

自述文件中,我们已经可以找到一些信息,表明该库不支持基于结果的生物特征认证。文档中给出的示例代码包含以下代码行:

TouchID.authenticate('to demo this react-native component', optionalConfigObject)
    .then(success => {
        AlertIOS.alert('Authenticated Successfully');
    })
    .catch(error => {
        AlertIOS.alert('Authentication Failed');
    });

在上面的示例中,很明显,它是基于事件的生物统计身份验证,因为成功方法不会验证身份验证的状态,也不会为开发人员提供验证方法。

注意到optionalConfigObject参数,该参数很可能包含将在基于结果的身份验证中使用的数据,不幸的是,事实并非如此。如果我们在文档中进一步了解,则会发现以下内容:

authenticate(reason, config)
Attempts to authenticate with Face ID/Touch ID. Returns a Promise object.
Arguments
    - reason - An optional String that provides a clear reason for requesting authentication.
    - config - optional - Android only (does nothing on iOS) - an object that specifies the title and color to present in the confirmation dialog.

如我们所见,authenticate方法仅采用示例中使用的两个参数。此外,可选参数config(optionalConfigObject在示例代码中)在UI信息上不起作用,该参数在iOS上不起作用。

阅读了文档后,现在让我们看看源代码,看看该库是否提供了一种执行基于结果的生物特征认证的方法。

Android

首先让我们看一下Android的实现。我们可以在TouchID.android.js文件中找到React Native中实现authenticate的方法,该方法用于执行生物特征认证。该方法是执行库提供的生物特征认证的唯一方法。在该方法中可以找到以下代码:

authenticate(reason, config) {
  //...
  return new Promise((resolve, reject) => {
    NativeTouchID.authenticate(
      authReason,
      authConfig,
      error => {
        return reject(typeof error == 'String' ? createError(error, error) : createError(error));
      },
      success => {
        return resolve(true);
      }
    );
  });
}

我们已经在上面的代码片段中看到,success回调不验证身份验证的结果,仅返回布尔值。因此,Android实现是基于事件的。

iOS

现在让我们看一下iOS的实现,TouchID.ios.js文件仅包含一种authenticate生物识别身份验证方法,其中包含以下代码:

authenticate(reason, config) {
  //...
  return new Promise((resolve, reject) => {
    NativeTouchID.authenticate(authReason, authConfig, error => {
      // Return error if rejected
      if (error) {
        return reject(createError(authConfig, error.message));
      }

      resolve(true);
    });
  });
}

如我们所见,如果设置了error对象,认证将失败,否则将返回布尔值。该库没有为应用程序提供一种验证身份验证状态的方法。因此,iOS实现是基于事件的。

如我们所见,react-native-touch-id仅支持基于事件的生物特征认证。因此,使用此库的应用程序将无法实现安全的生物特征认证。

Expo-local-authentication

GitHub:https : //github.com/expo/expo

该库仅提供一种用于生物特征认证的JavaScript authenticateAsync方法,可以在LocalAuthentication.ts文件中找到该方法。以下代码负责生物识别身份验证:

export async function authenticateAsync(
    options: LocalAuthenticationOptions = {}
): Promise
    //...
    const promptMessage = options.promptMessage || 'Authenticate';
    const result = await ExpoLocalAuthentication.authenticateAsync({ ...options, promptMessage });

    if (result.warning) {
        console.warn(result.warning);
    }
    return result;
}

该方法执行对本机方法的调用,并返回结果对象。要查看结果对象中包含哪些数据,我们将不得不深入研究库的ExpoLocalAuthentication**.**authenticateAsync平台部分。

Android

authenticateAsync从JavaScript调用的方法可以在LocalAuthenticationModule.java文件中找到,以下代码段是我们感兴趣的部分:

public void authenticateAsync(final Map
   
      //...
      Executor executor = Executors.newSingleThreadExecutor();
      mBiometricPrompt = new BiometricPrompt(fragmentActivity, executor, mAuthenticationCallback);

      BiometricPrompt.PromptInfo.Builder promptInfoBuilder = new BiometricPrompt.PromptInfo.Builder()
              .setDeviceCredentialAllowed(!disableDeviceFallback)
              .setTitle(promptMessage);
      if (cancelLabel != null && disableDeviceFallback) {
        promptInfoBuilder.setNegativeButtonText(cancelLabel);
      }
      BiometricPrompt.PromptInfo promptInfo = promptInfoBuilder.build();
      mBiometricPrompt.authenticate(promptInfo);
    }
  });
}

我们可以看到BiometricPrompt.authenticate在没有提供的情况下执行了对BiometricPrompt.CryptoObject的调用。因此,生物特征认证只能基于事件,而不能基于结果。为了完整起见,让我们通过查看成功回调方法来验证此假设:

new BiometricPrompt.AuthenticationCallback () {
  @Override
  public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
    mIsAuthenticating = false;
    mBiometricPrompt = null;
    Bundle successResult = new Bundle();
    successResult.putBoolean("success", true);
    safeResolve(successResult);
  }
};

不出所料,onAuthenticationSucceeded回调方法不会验证result的值,而是返回一个布尔值,这表明Android实现是基于事件的。

iOS

现在让我们看一下iOS的实现。

authenticateAsync可以在EXLocalAuthentication.m文件中找到从JavaScript调用的方法。以下代码段是我们感兴趣的部分:

UM_EXPORT_METHOD_AS(authenticateAsync,
                    authenticateWithOptions:(NSDictionary *)options
                    resolve:(UMPromiseResolveBlock)resolve
                    reject:(UMPromiseRejectBlock)reject)
{
    //...
    [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
      localizedReason:reason
        reply:^(BOOL success, NSError *error) {
          resolve(@{
            @"success": @(success),
            @"error": error == nil ? [NSNull null] : [self convertErrorCode:error],
            @"warning": UMNullIfNil(warningMessage),
          });
        }];
}

就像Android实现一样,该库返回一个布尔值,该值指示身份验证是否成功。因此,iOS实现是基于事件的。

值得注意的是,该库允许在iOS上使用其他身份验证方法(设备PIN码,Apple Watch等)。不幸的是,其他方法的身份验证实现与生物身份验证存在相同的问题,如以下代码所示:

UM_EXPORT_METHOD_AS(authenticateAsync,
                    authenticateWithOptions:(NSDictionary *)options
                    resolve:(UMPromiseResolveBlock)resolve
                    reject:(UMPromiseRejectBlock)reject)
{
  NSString *disableDeviceFallback = options[@"disableDeviceFallback"];
  //...
  if ([disableDeviceFallback boolValue]) {
    // biometric authentication
  } else {
    [context evaluatePolicy:LAPolicyDeviceOwnerAuthentication
      localizedReason:reason
        reply:^(BOOL success, NSError *error) {
          resolve(@{
            @"success": @(success),
            @"error": error == nil ? [NSNull null] : [self convertErrorCode:error],
            @"warning": UMNullIfNil(warningMessage),
          });
        }];
  }
}

如我们所见,expo-local-authentication库仅支持基于事件的生物特征认证。因此,使用此库的开发人员将无法实施安全的生物特征认证。

React-native-fingerprint-scanner

资料来源:https : //github.com/hieuvp/react-native-fingerprint-scanner

该库为两个平台提供了两种不同的实现。

Android

该库提供了一种JavaScript方法来使用authenticate生物特征识别进行身份验证,该方法可以在authenticate.android.js文件中找到。在Android 6.0及更高版本上,authenticate方法如下:

const authCurrent = (title, subTitle, description, cancelButton, resolve, reject) => {
  ReactNativeFingerprintScanner.authenticate(title, subTitle, description, cancelButton)
    .then(() => {
      resolve(true);
    })
    .catch((error) => {
      reject(createError(error.code, error.message));
    });
}

在Android 6.0之前的Android版本上,authenticate方法如下:

const authLegacy = (onAttempt, resolve, reject) => {
  //...
  ReactNativeFingerprintScanner.authenticate()
    .then(() => {
      DeviceEventEmitter.removeAllListeners('FINGERPRINT_SCANNER_AUTHENTICATION');
      resolve(true);
    })
    .catch((error) => {
      DeviceEventEmitter.removeAllListeners('FINGERPRINT_SCANNER_AUTHENTICATION');
      reject(createError(error.code, error.message));
    });
}

在这两种情况下,如果对的调用均未引发错误,则ReactNativeFingerprintScanner**.**authenticate方法将返回布尔值,否则将引发异常。因此,Android实现是基于事件的。

iOS

就像Android一样,该库提供了一种JavaScript authenticate方法来使用生物识别技术进行身份验证。该方法的实现可以在authenticate.ios.js文件中找到,也可以在以下代码片段中找到:

export default ({ description = ' ', fallbackEnabled = true }) => {
  return new Promise((resolve, reject) => {
    ReactNativeFingerprintScanner.authenticate(description, fallbackEnabled, error => {
      if (error) {
        return reject(createError(error.code, error.message))
      }

      return resolve(true);
    });
  });
}

如果对的调用未返回错误,则ReactNativeFingerprintScanner**.**authenticate方法将返回布尔值。因此,iOS实现是基于事件的。

与expo-local-authentication类似,react-native-fingerprint-scanner也支持iOS上的其他身份验证方法。如果在调用方法时将fallbackEnabled参数设置为默认值,则这些可用作备用方法。由于true``authenticate``authenticate方法也用于这些备用方法,因此它们也遭受与库提供的生物特征认证相同的问题。

如我们所见,react-native-fingerprint-scanner库仅支持基于事件的生物特征认证。因此,使用此库的开发人员将无法实施安全的生物特征认证。

react-native-fingerprint-android

GitHub:https : //github.com/jariz/react-native-fingerprint-android评论版

顾名思义,该库仅在Android平台上实现生物识别身份验证。

该库提供了一种authenticate生物识别身份验证方法,可以在index.android.js文件中找到该方法。我们感兴趣的部分如下:

static async authenticate(warningCallback:?(response:FingerprintError) => {}):Promise
  //..
  let err;
  try {
    await FingerprintAndroidNative.authenticate();
  } catch(ex) {
    err = ex
  }
  finally {
    //remove the subscriptions and crash if needed
    DeviceEventEmitter.removeAllListeners("fingerPrintAuthenticationHelp");
    if(err) {
      throw err
    }
  }
}

我们可以在方法原型中看到该方法返回一个Promise,这类似于返回布尔值,因此表明该库提供的生物特征认证是基于事件的。

但是,为了非常确定,让我们仍然看一下Java源码,找到FingerprintAndroidNative**.**authenticate方法。

该方法的实现可以在FingerprintModule.java文件中找到。,方法的相关行可以在下面找到:

public void authenticate(Promise promise) {
    //...
    fingerprintManager.authenticate(null, 0, cancellationSignal, new AuthenticationCallback(promise), null); 
    //..
}

如我们所见,FingerprintManager.authenticate方法无需提供即可执行对FingerprintManager.CryptoObject方法的调用。因此,生物特征认证只能基于事件,而不能基于结果。通过检查OnAuthenticationSucceeded回调方法,我们甚至可以进一步确定。

如我们所见,react-native-fingerprint-android库仅支持基于事件的生物特征认证。因此,使用此库的开发人员将无法实施安全的生物特征认证。

React-native-biometrics

GitHub:https : //github.com/SelfLender/react-native-biometrics

该库提供了两种使用生物识别技术进行身份验证的方法。

执行生物特征认证的第一种方法是index.ts文件中提供的simplePrompt方法。但是,在文档中明确提到该方法仅验证用户的生物特征,并且不应将其用于对安全敏感的功能:

simplePrompt(options)
Prompts the user for their fingerprint or face id. Returns a Promise that resolves if the user provides a valid biometrics or cancel the prompt, otherwise the promise rejects.

**NOTE: This only validates a user's biometrics. This should not be used to log a user in or authenticate with a server, instead use createSignature. It should only be used to gate certain user actions within an app.

因此,我们将不研究此方法,因为读者应该已经清楚它是基于事件的生物特征认证。

在库中执行生物特征认证的第二种方法是index.ts文件中createSignature提供的方法。根据文档,要使用此方法,必须首先使用该方法创建密钥对,并且必须将公钥发送到服务器。身份验证过程包括在服务器上发送和验证的加密签名。下图来自自述文件,说明了createKeys过程。

image-20210415212037430image-20210415212037430.png

认证流程

简单分析发现还是挺安全的:在服务器上验证密码签名是执行生物特征认证的正确方法。但是,我们仍然需要验证库中的加密操作是否正确完成。

让我们分析特定于平台的实现。

Android

为了验证该库使用了安全的实现,我们必须验证:

· 用于执行签名的私钥需要用户身份验证;

· 成功回调使用生物特征认证的结果来执行加密操作。

· 该库将上述加密操作的结果返回给应用程序。

因此,首先让我们从ReactNativeBiometrics类中分析createSignature方法:

public void createSignature(final ReadableMap params, final Promise promise) {
    //...
    Signature signature = Signature.getInstance("SHA256withRSA");
    KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    keyStore.load(null);

    PrivateKey privateKey = (PrivateKey) keyStore.getKey(biometricKeyAlias, null);
    signature.initSign(privateKey);

    BiometricPrompt.CryptoObject cryptoObject = new BiometricPrompt.CryptoObject(signature);

    AuthenticationCallback authCallback = new CreateSignatureCallback(promise, payload);
    //...
    BiometricPrompt biometricPrompt = new BiometricPrompt(fragmentActivity, executor, authCallback);

    PromptInfo promptInfo = new PromptInfo.Builder()
            .setDeviceCredentialAllowed(false)
            .setNegativeButtonText(cancelButtomText)
            .setTitle(promptMessage)
            .build();
    biometricPrompt.authenticate(promptInfo, cryptoObject);
}

在上面的代码中,我们可以看到一个Signature对象是使用私钥启动的biometricKeyAlias,然后使用签名启动CryptoObject。最后,我们可以看到CryptoObject正确地将赋予了BiometricPrompt.authenticate方法。

现在让我们看一下如何创建使用过的密钥对:

public void createKeys(Promise promise) {
    //...  
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
    KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(biometricKeyAlias, KeyProperties.PURPOSE_SIGN)
            .setDigests(KeyProperties.DIGEST_SHA256)
            .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
            .setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
            .setUserAuthenticationRequired(true)
            .build();
    keyPairGenerator.initialize(keyGenParameterSpec);
    //...
}

我们可以在上面的代码片段中看到使用AndroidKeystore,并且密钥对已配置为需要使用setUserAuthenticationRequired方法进行用户身份验证。

现在,我们只需要验证成功回调是否正确处理并返回认证结果即可。让我们看一下该类的onAuthenticationSucceeded方法CreateSignatureCallback

public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
    //...
    BiometricPrompt.CryptoObject cryptoObject = result.getCryptoObject();
    Signature cryptoSignature = cryptoObject.getSignature();
    cryptoSignature.update(this.payload.getBytes());
    byte[] signed = cryptoSignature.sign();
    String signedString = Base64.encodeToString(signed, Base64.DEFAULT);
    signedString = signedString.replaceAll("\r", "").replaceAll("\n", "");

    WritableMap resultMap = new WritableNativeMap();
    resultMap.putBoolean("success", true);
    resultMap.putString("signature", signedString);
    promise.resolve(resultMap);
    //... 
}

成功回调使用身份验证结果来获取Signature对象并签署提供的有效负载。签名然后在base64中编码并在promise中返回。

因此,应用程序可以向库提供有效载荷,该有效载荷将在用户成功提供其生物特征数据之后进行签名。然后将签名返回给应用程序,然后可以将其最终发送到服务器以进行验证并完成身份验证。

因此,Android实现允许基于结果的安全生物特征认证。

iOS

与Android一样,要验证该库是否使用了安全的实现,我们必须验证以下内容:

· 私钥需要用户身份验证;

· 私钥用于执行加密操作;

· 该库将上述加密操作的结果返回给应用程序。

我们开始深入研究。以下代码片段显示了createSignature方法的相关部分,可在ReactNativeBiometrics.m文件中找到:

RCT_EXPORT_METHOD(createSignature: (NSDictionary *)params resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
    //...
    NSData *biometricKeyTag = [self getBiometricKeyTag];
    NSDictionary *query = @{
                            (id)kSecClass: (id)kSecClassKey,
                            (id)kSecAttrApplicationTag: biometricKeyTag,
                            (id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
                            (id)kSecReturnRef: @YES,
                            (id)kSecUseOperationPrompt: promptMessage
                            };
    SecKeyRef privateKey;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&privateKey);

    if (status == errSecSuccess) {
      NSError *error;
      NSData *dataToSign = [payload dataUsingEncoding:NSUTF8StringEncoding];
      NSData *signature = CFBridgingRelease(SecKeyCreateSignature(privateKey, kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256, (CFDataRef)dataToSign, (void *)&error));

      if (signature != nil) {
        NSString *signatureString = [signature base64EncodedStringWithOptions:0];
        NSDictionary *result = @{
          @"success": @(YES),
          @"signature": signatureString
        };
        resolve(result);
      }
      //...
    }
}

该库尝试从key串中检索由biometricKeyTag标识的私钥,然后使用它对提供的有效负载进行签名。签名成功后,库会将加密的数据返回给应用程序。

现在让我们看一下如何生成私钥,以确保需要适当的用户身份验证才能访问它。密钥对是在createKeys方法中的同一文件中创建的。以下代码片段显示了该方法的相关部分:

RCT_EXPORT_METHOD(createKeys: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
    //...
    SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
                                                                    kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
                                                                    kSecAccessControlBiometryAny, &error);
    //...
    NSDictionary *keyAttributes = @{
        (id)kSecClass: (id)kSecClassKey,
        (id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
        (id)kSecAttrKeySizeInBits: @2048,
        (id)kSecPrivateKeyAttrs: @{
        (id)kSecAttrIsPermanent: @YES,
        (id)kSecUseAuthenticationUI: (id)kSecUseAuthenticationUIAllow,
        (id)kSecAttrApplicationTag: biometricKeyTag,
        (id)kSecAttrAccessControl: (__bridge_transfer id)sacObject
        }
    };
    //...
    id privateKey = CFBridgingRelease(SecKeyCreateRandomKey((__bridge CFDictionaryRef)keyAttributes, (void *)&gen_error));
    //...
}

在上面的代码片段中,我们可以看到使用kSecAccessControlBiometryAny访问控制标志生成了密钥对并将其添加到key串中。因此,从key串中检索key将需要成功的生物特征认证。

因此,应用程序可以向库提供有效负载,该负载将在用户成功通过身份验证后进行签名。然后将签名返回给应用程序,然后可以将其提交给服务器进行验证。

因此,iOS实施允许基于结果的安全生物特征认证。

如我们所见,react-native-biometrics库提供了两种生物特征认证方法,其中一种createSignature提供了基于结果的安全生物特征认证。

应当注意,库执行生物特征认证的方式要求服务器执行签名验证,这不仅是解密,而且还需要在服务器上进行更多更改,而不仅仅是解密本地设备上的令牌并将其发送到服务器以进行验证。但是,虽然将它集成到应用程序中比较困难,但是它具有防止重放攻击的优点,因为发送到服务器的身份验证有效载荷对于每个身份验证都是不同的。

0x05 分析总结

在我们分析的五个库中,只有一个是react-native-biometrics,它提供了一种基于结果的安全生物身份验证,该身份验证可实现不可绕过的身份验证。其他四个库仅提供基于事件的生物特征认证,这仅允许客户端认证实现,因此可以绕过该认证。

下表总结了每个分析库提供的生物特征认证类型:

image.png

第三方库和移动开发框架的使用肯定可以减少所需的开发工作,并且对于不需要高度安全性的应用程序来说,不会有太多出错的地方。但是,如果你的应用程序确实包含敏感数据或功能,例如金融,政府或医疗保健部门的应用程序,则SDLC的每个步骤都应包括安全性。在这种情况下,选择正确的移动开发框架以及信任哪些外部库是非常重要的一步。

本文翻译自:https://blog.nviso.eu/2021/04/06/a-closer-look-at-the-security-of-react-native-biometric-libraries/如若转载,请注明原文地址


文章来源: https://www.4hou.com/posts/n7Xp
如有侵权请联系:admin#unsafe.sh