🐬

【Flutter】Keychainアクセスのオプションと、iOSネイティブ連携してる場合のハマりポイント

2023/02/18に公開

flutter_secure_storage

前提として、Flutter側はflutter_secure_storageを使用しています。
FlutterにおけるiOSのKeychainアクセスにはこのパッケージが有名だと思います。
https://pub.dev/packages/flutter_secure_storage
パッケージの実装の概要としては、MethodChannel[1]でプラットフォーム側のセキュア情報にアクセスします。

iOSのKeychainアクセスにおけるオプション

flutter_secure_storageではKeychainAccessibilityというオプションが用意されており、端末のアンロック状態やパスコード設定有無によってアクセス制限の堅さ分けがされています。

属性 概要 デバイス制約
passcode 端末にパスコードが設定されていてロック解除中のみアクセス可能。この中では一番堅い。 あり(※)
unlocked ロック解除中のみアクセス可能。デフォルト。 なし
unlocked_this_device ロック解除中のみアクセス可能。 あり(※)
first_unlock 端末の再起動後に一度ロック解除されるまでアクセスできない。(再起動後のロック解除後はロック画面中でもアクセス可能) なし
first_unlock_this_device 端末の再起動後に一度ロック解除されるまでアクセスできない。(再起動後のロック解除後はロック画面中でもアクセス可能) あり(※)

ピュアFlutterの場合はシンプル

ピュアFlutterの場合は、上述のflutter_secure_storageのKeychainAccessibilityを用途によって使い分ければ良いです。
基本的にはデフォルトで足りると思いますが、例えばロック画面でもアクセスしたいならfirst_unlockを使います。

final options = IOSOptions(accessibility: KeychainAccessibility.first_unlock);
await storage.write(key: key, value: value, iOptions: options);

ネイティブ連携してる場合の注意⚠️

ここからのお話は、デフォルト(つまりunlocked)の場合は関係ないかもしれません。

次の場合は注意が必要です

  • ネイティブ連携していて、かつネイティブiOS側(Swift)とFlutterの両方から同じKeychainの値にアクセスしている
    • さらにfirst_unlockなどデフォルト以外を使っている場合

私はこのパターンに当てはまっていたので、Flutterでfirst_unlockを設定しているのにロック画面でKeychainからデータ取得できずハマりました😭

原因は、Swift側ではデフォルト(unlocked)になっていたことです。

For example, by default, you can only access keychain items when the device is unlocked.

When Unlocked
Items with this setting are only accessible when the device is unlocked. A device without a passcode is considered to always be unlocked. This is the default accessibility when you don’t otherwise specify a setting.

https://developer.apple.com/documentation/security/keychain_services/keychain_items/restricting_keychain_item_accessibility

結論として、FlutterとSwift側で同じ属性を設定できていれば問題はありません
first_unlockなどデフォルト以外を使うときは、Swift側にも設定が必要です。

iOS(Swift)側の設定

要はflutter_secure_storageと同じ感じで、kSecAttrAccessibleAfterFirstUnlockなど任意の属性を選ぶだけです!
公式ドキュメントより
公式ドキュメントより
https://developer.apple.com/documentation/security/keychain_services/keychain_items/item_attribute_keys_and_values

私はネイティブiOSのKeychainアクセスにはkishikawakatsumiさんのKeychainAccessを使っています。

https://github.com/kishikawakatsumi/KeychainAccess/

Flutterでfirst_unlockを指定した場合

final options = IOSOptions(accessibility: KeychainAccessibility.first_unlock);
await storage.write(key: key, value: value, iOptions: options);

Swiftでもこのように書けば、両者でアクセスが可能です!

let keychain = Keychain(service: "com.example.github-token")
    .accessibility(.afterFirstUnlock)

参考

脚注
  1. https://docs.flutter.dev/development/platform-integration/platform-channels?tab=type-mappings-kotlin-tab
    https://zenn.dev/maropook/articles/1a07feaabca581 ↩︎

Discussion