Bảo mật ứng dụng React Native với Keychains

I. Giới thiệu:‌‌‌‌

‌‌Trong thời buổi công nghệ phát triển như hiện tại, thì việc để lộ hay bị đánh cắp thông tin như mật khẩu, thẻ tín dụng là điều rất dễ xẩy ra. Vậy nên bảo mật dữ liệu là điều cực kỳ quan trọng đối với ứng dụng di động. ‌‌Hãy cùng nhau tìm hiểu về cách bảo mật thông tin trong ứng dụng React Native của bạn với Biometric (sinh trắc học) bằng cách sử dụng react-native-keychain.

II. Cài đặt:‌‌‌‌

1. Thêm thư viện react-native-keychain vào ứng dụng React Native:

npm install react-native-keychain
hoặc yarn add react-native-keychain

2.  Nếu bạn sử dụng react-native <= 0.59, đừng quên link thư viện vào project:

react-native link react-native-keychain

3.  Từ root project chạy dòng lệnh:

cd ios && pod install

Quá trình config thư viện đã hoàn thành rồi. Khá đơn giản phải không ạ :D.

Tham khảo thêm: https://github.com/oblador/react-native-keychain#api

Lưu ý: Khi bạn đang sử dụng react-native-keychain, điều quan trọng cần lưu ý là có những phương thức chỉ tương thích trên iOS hoặc Android. Tuy nhiên, những phương thức mà chúng ta sử dụng trong bài viết này đều có thể sử dụng được trên cả AndroidiOS.

Tham khảo thêm: react-native-keychain.

III. Sử dụng:‌‌‌‌

Ví dụ sau sẽ cho thấy cách lưu trữ, truy xuất và sử dụng thông tin để đăng nhập lại vào ứng dụng bằng sinh trắc học.

  1. Lưu thông tin đăng nhập:
    Chúng ta sử dụng hàm setGenericPassword() để lưu trữ thông tin đăng nhập của người dùng (tên người dùng và mật khẩu) vào Keychain (iOS)Shared Preference (Android) .

    • Đầu tiên hàm sẽ sử dụng Keystore để tạo một cặp khoá (keypair) private/public. Cặp key này sẽ được lưu trữ trong Keystore.
    • Dữ liệu truyền vào sẽ được mã hoá bằng thuật toán AES-256-GCM với private key và lưu vào Secure Storage.
    import * as Keychain from 'react-native-keychain';
    
    const CONFIG = {
      accessControl:Keychain.ACCESS_CONTROL.BIOMETRY_ANY_OR_DEVICE_PASSCODE,
      accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED,
      storage: Keychain.STORAGE_TYPE.RSA,
    }
    const LoginScreen = props => {
       const username = 'quanna@vietnamlab.vn';
       const password = 'wertyuiop';
       await Keychain.setGenericPassword(username, password, CONFIG);
    }
    
  2. Truy xuất dữ liệu từ Keychain:
    Sử dụng hàm getGenericPassword() để lấy thông tin đăng nhập người dùng đã lưu trong Keychain.

    • Người dùng cần xác thực vân tay/khuôn mặt trước.
    • Sau khi xác thực thành công, tiếp tục gọi tới Keystore để lấy cặp khoá (keypair) private/public đã được tạo khi lưu thông tin đăng nhập.
    • Sử dụng private key để giải mã dữ liệu và trả lại cho người dùng.
    • Theo mặc định, hàm trả về Chuỗi. Vì vậy khi truy xuất các đối tượng, nên sử dụng JSON.parse.
    import * as Keychain from 'react-native-keychain';
    const CONFIG = {
     accessControl:Keychain.ACCESS_CONTROL.BIOMETRY_ANY_OR_DEVICE_PASSCODE,
     accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED,
     storage: Keychain.STORAGE_TYPE.RSA,
    }
    const LoginScreen = (props) => {
       const checkUserStatus = async () => {
          try {
             const credentials = await Keychain.getGenericPassword(CONFIG);
          } catch (error) {
             console.log("Keychain couldn't be accessed!", error);
          }
      };
    };
    
  3. Implement login code:
    Trong đoạn code dưới. Đầu tiên hàm function checkBiometricSupport() sẽ được gọi trong UseEffect để kiểm tra xem thiết bị có được hỗ trợ sinh trắc học hay không. Nếu có ta tiếp tục gọi tới hàm getUserData() để truy xuất xuống Keychain. Sau đó, hàm login () sẽ được gọi để đăng nhập bằng thông tin đăng nhập được trả về.

    import React, { useState, useEffect } from 'react';
    import * as Keychain from 'react-native-keychain';
    
    const CONFIG = {
     accessControl:Keychain.ACCESS_CONTROL.BIOMETRY_ANY_OR_DEVICE_PASSCODE,
     accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED,
     storage: Keychain.STORAGE_TYPE.RSA,
    }
    
    const LoginScreen = (props) => {
      const [email, setEmail] = useState('');
      const [password, setPassword] = useState('');
      const [bioSupport, setBioSupport] = useState(false);
      const [preLog, setPreLog] = useState(false);
    
      useEffect(() => {
        checkBiometricSupport();
      }, []);
    
      useEffect(() => {
        if (bioSupport) {
          getUserData();
        }
      }, [bioSupport]);
    
      useEffect(() => {
        if (preLog) {
          login();
        }
      }, [password, email]);
    
      const checkBiometricSupport = async () => {
        try {
          const biometricSupport = await Keychain.getSupportedBiometryType();
          setBioSupport(biometricSupport);
        } catch (error) {
          console.log("Keychain couldn't be accessed!", error);
        }
      };
    
      const getUserData = async () => {
        try {
          const credentials = await Keychain.getGenericPassword(CONFIG);
          if (credentials) {
            setPreLog(true);
            setEmail(credentials.username);
            setPassword(credentials.password);
          } else {
            setLoading(false);
          }
        } catch (error) {
          console.log("Keychain couldn't be accessed!", error);
          setLoading(false);
        }
      };
    
      const login = async () => {
        try {
          const response = await fetch('yoururl' + 'signin', {
            method: 'POST',
            headers: {
              Accept: 'application/json',
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({
              email,
              password,
            }),
          });
          const responseJson = await response.json();
          var message = responseJson.msg;
          if (responseJson.success === true) {
            await Keychain.setGenericPassword(email, password, CONFIG);
          }
        } catch (error) {
          console.error(error);
        }
      };
    };
    
  4. Xoá thông tin đăng nhập:
    Khi user thực hiện logout ra khỏi ứng dụng, hàm resetGenericPassword() được sử dụng để xoá toàn bộ thông tin đăng nhập của người dùng trong Keychain.

    import * as Keychain from 'react-native-keychain';
    
    const LoginScreen = (props) => {
      const removeCredentials = async () => {
        try {
          const credentials = await Keychain.resetGenericPassword();
        } catch (error) {
          console.log("Keychain couldn't be accessed!", error);
        }
      };
    };
    

IV. Lợi ích khi sử dụng react-native-keychain:

One library to access both iOS Keychain and Android Keystore in React-Native apps.

Có rất nhiều lý do để chúng ta sử dụng react-native-keychain. Đây là thư viện phổ biến nhất sử dụng để lưu trữ bảo mật thông tun, được React-native khuyên dùng. Ta hãy cùng xem một vài lợi ích mà thư viện này mang tới:

  • Thiết lập được quyền truy cập vào Keychain, ví dụ thiết bị phải được cài đặt mã khoá thì mới có thể truy cập.
  • Keychain không thể thể restored trên một thiết bị khác.
  • Encrypted keys được lưu ở hardware level.
  • Dễ dàng implement và sử dụng.
  • Được cập nhật liên tục và cộng đồng đông đảo.
    ……..

V. Kết luận:

Dựa trên kinh nghiệm của mình, react-native-keychain là lựa chọn tốt nhất để lưu trữ dữ liệu quan trọng cho ứng dụng của bạn. Như chúng ta đã thấy ở trên, việc implement và sử dụng hết sức đơn giản nhưng hiệu quả mang lại thì cao nhất.

Mình hy vọng bài viết này sẽ giúp mọi người hiểu hơn về cách sử dụng Keychain hay cụ thể hơn là đăng nhập bằng sinh trắc học. Cảm ơn mọi người đã đọc bài.