암호화 저장소 도입 - study-pals/frontend GitHub Wiki

배경

refresh token을 관리하기 위해, EncryptedStorage처럼 키-값 쌍을 암호화하여 저장할 수 있는 방법이 필요하다.

라이브러리 조사

react-native-encrypted-storage

조사 결과 Android, iOS 환경에서만 동작한다.

react-native-mmkv

공식 문서에 모바일 환경에서의 지원만이 명시되어 있으며, 관련 정보를 조사해본 결과 Android·iOS·Web까지만 네이티브 바인딩을 제공하고 있다.

따라서 Windows, Macos 환경을 커버할 수 없다.

react-native-async-storage + 커스텀 암호화

react-native-async-storage 는 네 플랫폼에서 모두 동작한다. js 단에서 직접 암복호화를 구현하고, 이를 asyncStorage에 적용해보면 어떨까?

→ 암호화에 사용할 key값도 결국 어딘가에 저장해야 한다. key값을 네트워크 통신을 통해 서버에서 관리하는 게 아닌 한, 결국 key값을 위한 암호화된 저장소가 또 필요하다. 결국 문제는 원점으로 돌아간다.

react-native-keychain

Android, iOS 환경에서 암호화 저장소를 제공하며, Catalyst를 통해 macOS도 지원 가능하다.

react-native-sensitive-info

https://mcodex.dev/react-native-sensitive-info/docs/

공식 문서 상으로는 네 가지 플랫폼을 모두 지원한다. 하지만 적용을 시도해 본 결과, Android와 Windows 둘 다에 적용 실패했다. 유지보수가 4년 전에 끊겨 더 이상 현재의 react native 아키텍처와 호환되지 않는 모양이다. 해당 레포지토리의 깃허브 이슈에도 관련하여 많은 이슈가 등록되어 있으나 해결되지 않고 있다.

결론

플랫폼별로 암호화 저장소 로직을 분기시킨다.

  • Android: keystore를 사용한다. (react-native-keychain으로 커버 가능)
  • iOS, Macos: keychain을 사용한다. (react-native-keychain으로 커버 가능)
  • Windows: 암호화 저장소 네이티브 모듈을 만들어 사용한다.

Windows 암호화 저장소 네이티브 모듈 만들기

https://microsoft.github.io/react-native-windows/docs/native-platform-getting-started

대부분 공식 문서를 참고했다.

깃허브 주소

1. 라이브러리 프로젝트 생성

npx --yes [email protected] --react-native-version "0.78.2" rnw-secure-storage
  • Library Type: Turbo module 선택
  • Languages: Kotlin & Objective-C 선택
npx react-native init-windows --template cpp-lib --overwrite

2. 네이티브 c++와 JS 사이의 API 정의

// src/NativeRnwSecureStorage.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  get: (key: string) => Promise<string | null>;
  set: (key: string, value: string) => Promise<void>;
  delete: (key: string) => Promise<void>;
}

export default TurboModuleRegistry.getEnforcing<Spec>('RnwSecureStorage');

3. 작성한 API 기반으로 codegen 실행

codegen을 위해 package.json에 아래 config를 추가한다.

// package.json
{
  "name": "rnw-secure-storage",
...
  "codegenConfig": {
    "name": "RnwSecureStorageSpec",
    "type": "modules",
    "jsSrcsDir": "src",
    "includesGeneratedCode": true,
    "windows": {
      "namespace": "rnwSecureStorageCodegen",
      "outputDirectory": "windows/rnwSecureStorage/codegen",
      "separateDataTypes": true
    },
...

이후 터미널에 npx react-native codegen-windows를 실행하면 .clang-format, NativeRnwSecureStorageSpec.g.h 파일이 생성된다.

여기서 NativeRnwSecureStorageSpec.g.h는 JS와 통신할 API 정보를 가지는 구조체이다.

// NativeRnwSecureStorageSpec.g.h
struct RnwSecureStorageSpec : winrt::Microsoft::ReactNative::TurboModuleSpec {
  static constexpr auto methods = std::tuple{
      Method<void(std::string, Promise<std::optional<std::string>>) noexcept>{0, L"get"},
      Method<void(std::string, std::string, Promise<void>) noexcept>{1, L"set"},
      Method<void(std::string, Promise<void>) noexcept>{2, L"delete"},
  };

4. 네이티브 모듈 작성

rnwSecureStorage.cpp, rnwSecureStorage.h 파일에 실제 네이티브 모듈의 기능을 정의해야 한다. 이 부분은 AI의 도움을 받아 작성하였다.

Windows.Security.Credentials API에서 제공하는 PasswordVault에 키-값 쌍을 저장하며, 내부적으로 DPAPI-AES 암호화를 사용한다.

5. npm 배포

tsc로 JS 코드를 번들링하고, rnw-secure-storage라는 이름으로 npm에 배포하였다.

Macos 에서의 문제

Internal error when a required entitlement isn't present.

iOS에서는 추가 설정 없이 잘 동작했으나, Macos에서 Keychain.setGenericPassword 호출 시 위 에러가 발생했다.

해결: keychain sharing entitlement 등록

https://developer.apple.com/documentation/xcode/configuring-keychain-sharing

Xcode IDE 상에서 keychain sharing entitlement를 등록하여 문제를 해결하였다. 하지만 release 시에는 해당 Bundle Identifier를 사용하지 않으므로 추가로 문제가 발생할 여지가 있다. 배포 시 재점검할 필요가 있을 듯하다.

image

⚠️ **GitHub.com Fallback** ⚠️