安全分析React Native生物认证识别开源库

admin 2023年11月29日09:27:20评论27 views字数 21387阅读71分17秒阅读模式

本篇对移动端APP常用的一些生物识别认证(例如指纹)开源库进行了一些代码分析审计,主要是最近在研究这类账号身份安全,太难了,内心跟这首歌一样,沉默是金

背景描述

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

1用户验证一次,然后保持验证状态,直到他们手动注销;

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

第一种策略虽然对用户来说非常方便,但显然不是很安全。第二种方法非常安全,但对用户来说是一种负担,因为他们每次都必须输入凭据。实施生物识别身份验证减轻了这种负担,因为身份验证方法对用户来说变得非常简单和快速。

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

在这篇博文中,我们将首先了解生物识别身份验证的基本概念,以便我们可以研究几个提供生物识别身份验证支持的 React Native 库的安全性。

简述总结

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

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

下表总结了每个分析库提供的生物识别身份验证类型:

开源库

基于事件*

基于结果*

react-native-touch-id

expo-local-authentication

react-native-fingerprint-scanner

react-native-fingerprint-android

react-native-biometrics

生物识别认证

生物识别身份验证允许用户使用其生物识别数据(指纹或面部识别)对应用程序进行身份验证。一般来说,生物识别认证可以通过两种不同的方式实现:

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

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

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

另一方面,Frida 等工具无法绕过实施良好的基于结果的生物识别身份验证。要实现基于结果的安全生物识别身份验证,应用程序必须使用硬件支持的生物识别 API。

关于存储凭据的小说明                  
虽然我们在本博文中使用术语“凭据”,但我们并不提倡存储用户的凭据(即用户名和密码)。对于高安全性应用程序来说,将用户的凭据存储在设备上绝不是一个好主意,无论它们的存储方式如何。相反,上述的“凭证”应该是生物认证激活过程中生成的专用于生物认证的凭证(例如高熵字符串)。
       

要在 Android 上实现基于结果的安全生物识别身份验证,必须生成需要用户身份验证的加密密钥。setUserAuthenticationRequired 这可以通过生成密钥时的方法 来实现。每当应用程序尝试访问密钥时,Android 都会确保提供有效的生物识别信息。然后,必须使用密钥来执行加密操作,解锁凭证,然后将凭证发送到后端。这是通过 CryptoObject 向生物识别 API 提供使用先前密钥启动的 来完成的。例如,该类 BiometricPrompt 提供了一个 authenticate 以 a CryptoObject 作为参数的方法。然后可以在成功回调方法中通过结果参数获取对键的引用。有关在 Android 上实现安全生物识别身份验证的更多信息,请参阅 f-secure 撰写的这篇非常精彩的博文

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

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

基于事件的身份验证真的不安全吗?

是和不是。这完全取决于您的移动应用程序的威胁模型。应用程序提供基于结果的身份验证的要求是 OWASP MASVS ( MSTG-AUTH-8 ) 中的 2 级要求。2 级意味着您的应用程序正在处理敏感信息,通常用于金融、医疗或政府部门的应用程序。    

安全分析React Native生物认证识别开源库

OWASP MASVS 验证级别( 来源

如果您的应用程序使用基于事件的生物识别身份验证,则存在特定的攻击,这些攻击将使用户的凭据可供攻击者使用:

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

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

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

最后一个示例还能够攻击使用基于结果的生物识别身份验证的应用程序,因为在内存中解密凭据后可以立即注入应用程序,但此类攻击的门槛远高于只需复制应用程序的本地存储即可。

React Native是什么

React Native 是 Facebook 创建的开源移动应用程序框架。该框架构建在 ReactJS 之上,允许使用 JavaScript 进行跨平台移动应用程序开发。这使得开发人员可以使用 HTML、CSS 和 JavaScript 在不同平台上同时开发移动应用程序。在过去的几年里,它获得了相当大的关注,现在被许多开发人员使用。

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

生物特征认证就是这样一种功能,仍然需要平台特定的代码来实现。因此,毫不奇怪,创建了许多库,试图减轻开发人员在不同平台上单独实现它们的负担。    

React Native 生物特征认证库安全评估

在本节中,我们将了解为 React Native 应用程序提供生物识别身份验证的五个库。我们不会只关注文档,而是检查源代码以验证实现是否安全。根据 Google 上“生物识别 API React Native”的最佳结果,我们选择了以下库:

1react-native-touch-id

1expo-local-authentication

1react-native-fingerprint-scanner

1react-native-fingerprint-android

1react-native-biometrics

对于每个库,我们都链接到撰写本博文时最新的特定提交。如果您想使用这些库,请使用最新版本。

React-native-touch-id

GitHub:https: //github.com/naoufal/react-native-touch-id/ 审核版本

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

Readme 文件中,我们已经可以找到一些提示,表明该库不支持基于结果的生物识别身份验证。文档中给出的示例代码包含以下代码行:

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

在上面的示例中,很明显,这是基于事件的生物识别身份验证,因为 success 方法不验证身份验证的状态,也不为开发人员提供验证身份验证的方法。

更精明的人会注意到这个 optionalConfigObject 参数,它很可能包含用于基于结果的身份验证的数据,对吗?不幸的是,事实并非如此。如果我们进一步查看文档,我们会发现以下内容:    

Plaintext                  
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 方法仅采用示例中使用的两个参数。此外,可选参数 configoptionalConfigObject 在示例代码中)用于 UI 信息,在 iOS 上不执行任何操作。

好了,文档已经讲完了,现在让我们深入研究源代码,看看该库是否提供了一种执行基于结果的生物识别身份验证的方法。

安卓

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

SQL                  
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 其中包含以下代码:

SQL                  
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 方法,可以在 LocalAuthentication.ts authenticateAsync 文件中找到该方法 。以下代码负责生物特征认证:

SQL                  
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

安卓

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

SQL                  
public void authenticateAsync(final Mapoptions,final Promise promise) {                  
                    
      //...                  
      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 . 因此,生物特征认证只能是基于事件的而不是基于结果的。为了完整起见,让我们通过查看 成功回调 方法来验证这个断言:    

SQL                  
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 的实现。

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

SQL                  
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 等)。不幸的是,其他方法的身份验证的实现遇到了与生物识别身份验证相同的问题,如以下代码片段所示:

SQL                  
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 审核版本

该库为两个平台提供了两种不同的实现。我们先从安卓开始。

安卓    

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

SQL                  
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 方法如下:

SQL                  
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));                  
    });                  
}

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

          

iOS系统

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

SQL                  
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);                  
    });                  
  });                  
}

如果调用 没有返回错误,该方法将再次返回一个布尔值。因此,iOS 实现是基于事件的。ReactNativeFingerprintScanner . authenticate

与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 平台上实现生物识别身份验证。

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

SQL                  
static async authenticate(warningCallback:?(response:FingerprintError) => {}):Promise<null> {                  
  //..                  
 
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 文件中找到 。该方法的相关行可以在下面找到:

SQL                  
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 文件中提供的方法 。不过,文档中明确提到,此方法仅验证用户的生物识别信息,不应用于安全敏感功能:

Plaintext                  
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 文件中提供的方法 。根据文档,要使用此方法,必须首先使用该方法创建密钥对 ,并且必须将公钥发送到服务器。身份验证过程包括在服务器上发送和验证的加密签名。下图取自 自述 文件,说明了此过程。

安全分析React Native生物认证识别开源库

好吧!从理论上讲,这看起来非常安全:在服务器上验证的加密签名是执行生物识别身份验证的正确方法。然而,我们仍然需要验证库中的加密操作是否正确完成。

我们来分析一下平台具体的实现。

安卓    

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

1用于执行签名的私钥需要用户认证;

1success回调使用生物认证的结果进行加密操作;

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

首先,让我们分析一下类 createSignature 中的方法 ReactNativeBiometrics

SQL                  
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 对象是用私钥发起的 biometricKeyAliasCryptoObject 然后用签名启动 A。最后,我们可以看到 被 CryptoObject 正确地赋予了该 BiometricPrompt.authenticate 方法。好的,到目前为止一切顺利。

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

SQL                  
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

SQL                  
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 一样,要验证该库是否使用安全实现,我们必须验证:

1私钥需要用户认证;

1私钥用于执行密码操作;

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

那么,让我们开始吧。以下代码片段显示了该 createSignature 方法的相关部分,可在 ReactNativeBiometrics.m 文件中找到:

SQL                  
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);                  
      }                  
      //...                  
    }                  
}
       

该库尝试从钥匙串中检索由 标识的私钥, biometricKeyTag 然后使用它来签署提供的有效负载。签名成功后,库将加密后的数据返回给应用程序。这看起来已经很好了!

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

SQL                  
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 访问控制标志生成并添加到钥匙串中的。因此,从钥匙串中检索密钥需要成功的生物识别身份验证。

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

因此,iOS 实现允许基于结果的安全生物识别身份验证。

正如我们所看到的,react-native-biometrics 库提供了两种生物识别身份验证方法,其中之一 createSignature 提供了基于结果的安全生物识别身份验证。

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

结果:基于结果的安全身份验证

结论

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

下表总结了每个分析库提供的生物识别身份验证类型:

开源库

基于事件

基于结果

react-native-touch-id

expo-local-authentication

react-native-fingerprint-scanner

react-native-fingerprint-android

react-native-biometrics        

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

              

原文始发于微信公众号(暴暴的皮卡丘):安全分析React Native生物认证识别开源库

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年11月29日09:27:20
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   安全分析React Native生物认证识别开源库http://cn-sec.com/archives/2250395.html

发表评论

匿名网友 填写信息