開発

flutter_secure_storageとは?11.0系の暗号方式とトークン保存の判断【2026年8月時点】

flutter_secure_storageは、認証トークンのような漏らせない文字列を、OSが用意した保護領域へ預けるFlutter向けのパッケージです。2026年8月時点の最新版は11.0.0(2026年8月6日公開)で、この版でAndroid側のencryptedSharedPreferences指定が削除され、最低要求SDKが24へ上がりました。流通している解説記事の多くはこの削除前の手順で止まっています。この記事では、OSごとの保存先と暗号方式、shared_preferencesとの線引き、実装が壊れる条件、そして入れないほうがよい場面までを一次情報から整理します。

まとめ:預けるのは認証情報だけに絞り、11.0系の破壊的変更を先に確認する

結論から言えば、このパッケージに入れてよいのはアクセストークン・リフレッシュトークン・APIキー・端末固有の識別子のように、盗まれた瞬間に成りすましが成立する短い文字列だけです。ユーザー設定や表示状態は暗号化しても得るものがなく、読み書きのたびにOSの鍵操作を挟むぶん遅くなります。線引きは「盗まれたら他人になれるか」の一点で足ります。

導入前に確認すべき数値は3つあります。11.0.0のminSdkは24、compileSdkは37、そしてAndroidOptionsからencryptedSharedPreferencesが消えたこと。既存アプリが9系で動いているなら、上げる前に自動移行の挙動とバックアップ設定を確認する順序を守ってください。移行を確認せずに上げると、鍵の復号に失敗した端末で既存トークンが読めなくなり、全ユーザーが再ログインを強いられます。

flutter_secure_storageが鍵を置く場所とOS別の保存先

このパッケージはキーと文字列の対を扱う薄いAPIで、暗号化そのものは各OSの仕組みに任せています。保存先が違えば壊れ方も違うため、対象プラットフォームごとに実体を押さえておきます。

AndroidでRSA OAEPとAES-GCMが分担する鍵と本体の暗号化

Androidでは、値そのものをAES-GCMで暗号化し、その暗号鍵をRSA OAEPで包んでAndroid KeyStoreに預ける二段構えを取ります。10.0.0で既定がRSA_ECB_OAEPwithSHA_256andMGF1PaddingAES_GCM_NoPaddingに変わり、11.0.0では旧方式のRSA_ECB_PKCS1PaddingAES_CBC_PKCS7Paddingが削除されました。鍵はアプリの外へ出ないため、端末のroot化や別アプリからの読み出しに対する耐性が生まれます。

生体認証を要求したい場合はAndroidOptions.biometricを使い、enforceBiometricsで厳格さを切り替えます。falseなら生体が未登録の端末でも画面ロック認証へ落ちて動き続け、trueなら端末の保護設定そのものを要求します。B2Bの業務アプリでtrueを選ぶと、生体未登録の現場端末が一斉に弾かれる点に注意してください。

iOSとmacOSのKeychainに入る項目とアクセス条件の既定値

iOSとmacOSではAppleのKeychainへ項目を書き込みます。10.0.0でiOSとmacOSの実装はflutter_secure_storage_darwinへ統合され、要求はiOS 12・macOS 10.14以上、Swift Package Managerにも対応しました。

設計上いちばん効くのはIOSOptionsのaccessibilityです。既定値はunlockedで、端末のロックが解除されている間しか値を読めません。バックグラウンドでのトークン更新やプッシュ受信時の処理が必要なら、初回ロック解除以降は読めるfirst_unlockへ変える判断が要ります。端末外へ持ち出したくない値には、バックアップ経由で他端末へ渡らないfirst_unlock_this_deviceを選びます。

Windows・Linux・Webで保存先が変わる仕組みと制約

デスクトップとWebは実装がまったく別物です。10.0.0以降のWindowsは資格情報マネージャーではなく暗号化ファイルへ保存する方式に変わりました。LinuxはlibsecretとfreedesktopのSecret Serviceを使うため、キーリングが未設定のデスクトップ環境では動きません。11.0.0では既定キーリングが無い場合の処理と、消し残ったキーリングデータの扱いが修正されています。

プラットフォーム 保存先 実装上の前提
Android KeyStoreと暗号化領域 minSdk 24以上
iOS・macOS Keychain iOS 12・macOS 10.14以上
Windows 暗号化ファイル 10.0.0で方式変更
Linux libsecret キーリングの設定が必要
Web LocalStorage HTTPSかlocalhostのみ

WebはWebCryptoで暗号化した値をLocalStorageへ置く構造で、鍵はブラウザごとに生成されます。同じユーザーでもブラウザや端末をまたぐと復号できません。

11.0.0で消えたEncryptedSharedPreferencesと移行時の確認点

11.0.0で入った破壊的変更4点とminSdk24が及ぼす影響

11.0.0の破壊的変更は4点です。AndroidOptionsからのencryptedSharedPreferences削除、旧鍵暗号RSA_ECB_PKCS1Paddingの削除、旧本体暗号AES_CBC_PKCS7Paddingの削除、そしてminSdkを24へcompileSdkを37へ引き上げたこと。API 21〜22向けのレガシーAES-CBC経路も同時に廃止されました。

影響が出やすいのはminSdkです。24はAndroid 7.0にあたり、23(Android 6.0)を下限に据えている業務アプリはここで引っかかります。既存の記事やサンプルにあるencryptedSharedPreferences: trueをそのまま書くとビルドが通りません。この指定は元をたどればJetpack Securityのライブラリに依存していたもので、そのライブラリが非推奨になった経緯を受けて10.0.0で非推奨化され、11.0.0で除かれました。

9系から上げるときにmigrateOnAlgorithmChangeが担う役割

9系までの暗号方式で書かれた値は、そのままでは新方式で読めません。この差を吸収するのがmigrateOnAlgorithmChangeで、既定値はtrueです。旧方式で保存された値を検出すると、読み出し時に復号して新方式で書き直します。

途中でアプリが落ちた場合に備えるならmigrateWithBackupをtrueにします。既定はfalseで、移行中の異常終了で値を失う可能性が残ります。数万台規模の実運用アプリを9系から11系へ上げるなら、この2つをtrueにしたうえで、移行に失敗した端末を再ログインへ誘導する経路を先に用意してください。パッケージ側の版を段階的に検証する運用は、FVMとは?Flutter SDKをプロジェクト単位で固定する運用とCI設定で扱っているSDK固定の考え方と合わせると事故が減ります。

10.0.0以降の各版で変わった点と更新を急がない場合の基準

10.0.0は全面的な書き直しで、minSdkが19から23へ、ビルドはJava 17へ、Windowsは暗号化ファイル保存へ、WebはWasm対応とuseSessionStorage追加という広い変更が入りました。10.1.0で名前空間を分けるstorageNamespaceとSecure Enclaveを使うuseSecureEnclave、10.3.0で生体認証の種類を指定するAndroidBiometricTypeが加わっています。

新規開発なら11.0.0を選んで構いません。更新を急がなくてよいのは、10.3.1で動いていてAndroidの生体認証を使っておらず、minSdkを24へ上げる調整が次の改修まで待てる場合です。逆に、生体キャンセル時のAEADBadTagExceptionに遭っているなら10.3.1以上へ上げる価値があります。

SharedPreferencesと分ける保存対象の線引きと判断基準

SharedPreferencesが2.5.5で示す用途と非推奨になったAPI

shared_preferencesの2.5.5は、Androidでは既定でDataStore Preferences、iOSとmacOSではNSUserDefaults、WebではLocalStorageへ書きます。公式の説明には、書き込みがディスクへ反映される保証がないため重要なデータの保存には使ってはならない、という趣旨の明記があります。暗号化の有無以前に、消えても困らない値のための仕組みだという設計思想です。

もう一点、レガシーのSharedPreferences APIは非推奨となり、SharedPreferencesAsyncSharedPreferencesWithCacheへの移行が案内されています。古い記事のコードをそのまま持ってくると、非推奨APIと削除済みオプションの両方を踏むことになります。

受託案件で保存先を分ける基準と暗号化が不要なデータの見分け方

判断は単純です。その値を第三者が手に入れたときにユーザーへ成りすませるならflutter_secure_storage、そうでなければshared_preferences。テーマ設定、直近に開いたタブ、チュートリアルの表示済みフラグは後者で足ります。

  • アクセストークン・リフレッシュトークン:secure_storageへ
  • APIキー・デバイス識別子:secure_storageへ
  • 表示設定・並び順・既読フラグ:shared_preferencesへ
  • 数MB規模のキャッシュや画像:どちらでもなくファイルやDBへ

4番目は間違いが起きやすい箇所です。このパッケージは文字列の入れ物であって、大きなデータを置くと読み書きのたびに復号が走り、起動時間に跳ね返ります。トークン自体の構造や検証の話はJWTとは?構造・署名検証の仕組みとセッションとの違いで扱っているため、ここでは置き場所の判断に絞ります。

認証トークンの保存と読み取りを組み立てる実装手順と失敗時の扱い

読み書き削除の4メソッドと戻り値がnullで返る場面の見分け

APIはwritereaddeletedeleteAll、そしてキーの有無を見るcontainsKeyで構成されます。全部が非同期で、扱えるのは文字列だけです。構造を持つ値はJSON文字列にしてから渡します。

実装で迷うのはreadがnullを返したときの解釈です。未ログインでキーが無いのか、鍵の復号に失敗したのかで、取るべき行動が変わります。前者はログイン画面へ、後者はdeleteAllで領域を掃除してからログイン画面へ。両者を区別せずリトライだけを繰り返すと、壊れた鍵を抱えたまま無限に失敗する端末が生まれます。

インスタンスを1つに保つ設計と非同期の読み書きが競合する条件

FlutterSecureStorageのインスタンスは、オプションを含めて1箇所で生成し、そこから使い回す形にします。画面ごとにオプション違いのインスタンスを作ると、accessibilityや名前空間の指定が食い違い、書いた側と読む側で別項目を触ることになります。

もう1つの落とし穴が並行アクセスです。トークン更新中に別の画面が同じキーを読むと、更新前後のどちらが返るか決まりません。更新処理は1本のFutureにまとめ、待っている呼び出しは同じFutureを共有する形にします。更新の頻度や失効の設計そのものはリフレッシュトークンとは?更新フローと寿命・ローテーション設計を前提にすると整理しやすくなります。

受託開発で決めておく保存項目の一覧と引き継ぎ資料への記載事項

納品を伴う開発では、保存キーの一覧を仕様として残しておくと保守で助かります。キー名、値の意味、保存先(secure_storageかshared_preferencesか)、削除される条件(ログアウト時・アプリ削除時・鍵破損時)の4項目があれば、引き継いだ側が挙動を追えます。

アプリ側の保存設計は、認証基盤やバックエンドの寿命設計と噛み合っていないと機能しません。既存アプリの改修でこの整合を取る作業を含めて外部に任せる場合は、Flutter / React Nativeによるクロスプラットフォーム開発のように、両OSの保存挙動まで踏まえて設計できる体制かどうかを見てください。

Android・iOS・Webで実装が壊れる条件と回避のための設定

報告される不具合の大半は、パッケージの不備ではなくOS側の仕組みとの噛み合わせで起きます。頻度の高い順に3つ挙げます。

Androidの自動バックアップで鍵が壊れる原因と除外の設定

Androidは既定でアプリデータをGoogleドライブへバックアップします。暗号化された値はバックアップされる一方、KeyStoreの鍵は端末を出ません。復元先の端末では鍵の無い暗号文だけが戻り、java.security.InvalidKeyExceptionで読み出しに失敗します。

公式の案内はマニフェストでandroid:allowBackup="false"を指定すること、またはバックアップ対象からこのパッケージが使う保存領域を除外することです。アプリ全体のバックアップを止めたくない場合は除外指定を選びます。機種変更時のみ再ログインが必要になる仕様として、ユーザー向けの説明に含めておくと問い合わせが減ります。

iOSでアプリを削除しても項目が残る挙動と初回起動時に行う処理

Keychainの項目はアプリを削除しても端末に残ることがあります。同じアプリを入れ直した利用者が、ログアウトしたつもりのアカウントに自動で入り直す、という事故につながる挙動です。

対処は定型化しています。shared_preferences側に「起動済み」フラグを持ち、そのフラグが無い初回起動時にdeleteAllを呼ぶ。フラグはアプリ削除で消えるため、再インストールを検出できます。この処理を入れるかどうかは、共用端末を想定するかで決めてください。個人所有前提の消費者向けアプリなら、残っていたほうが体験としては自然です。

WebでHTTPS限定になる制約とブラウザ間で復号できない理由

Web出力では、HTTPSかlocalhostでしか動きません。WebCryptoの機能が安全なコンテキストに限定されているためで、社内向けに平文HTTPで配信している検証環境では読み書きが失敗します。

加えて、暗号鍵はブラウザごとにLocalStorageへ保持されます。同じユーザーが別ブラウザで開いても値は復号できず、シークレットウィンドウでは保持されません。10.0.0で追加されたuseSessionStorageを使えばタブを閉じた時点で消える動きにできます。Web出力を対象に含めるかどうかの判断そのものはFlutter Webとは?CanvasKitとWasmの描画差・SEO制約・採用条件を先に見てから決めるほうが手戻りが少なくなります。

flutter_secure_storageを入れない判断が正しくなる場面

保存対象を広げた設計で生じる不具合と切り戻しに必要な作業量の判断

暗号化されているから全部ここに入れよう、という設計は失敗します。理由は3つとも実務的です。読み書きが非同期になるため同期前提だった画面の描画順が崩れる、値ごとにOSの鍵操作が走るため起動直後にまとめて読むと待ち時間が伸びる、そしてAndroidのバックアップ除外がアプリ全体の設定に波及する。

切り戻しはさらに厄介です。すでに配布済みの端末には旧設計の値が残っているため、移行コードを一時的に両方へ書く期間が必要です。キーが20個あれば、移行判定・読み替え・掃除の3層を全キーぶん書くことになります。最初から入れる値を絞るほうが、後から絞るより圧倒的に安く済みます。

生体認証や鍵管理まで求める要件でこのパッケージを見送る判断の基準

次の条件に当てはまるなら、このパッケージ単体で組むのは見送ってください。医療・金融のように鍵の管理主体と監査証跡を要件として問われる案件、サーバー側で鍵をローテーションして端末の復号可否を制御したい案件、そして値の暗号化ではなく処理そのものを保護領域で行いたい案件。いずれもOSのAPIを直接扱うか、専用のSDKを選ぶ領域です。

逆に、要件が「トークンを平文で置かない」「端末を落としても他人に読まれない」の範囲に収まるなら、これ以上の仕組みは過剰です。Secure Enclaveを使うuseSecureEnclaveや生体要求も、必要が説明できないうちは入れないほうが対応端末の幅を保てます。

よくある質問

導入前に質問として挙がりやすい点をまとめます。

flutter_secure_storageは無料で商用利用できますか?

ライセンスはBSD-3-Clauseで、商用の受託開発でも費用は発生しません。配布物にライセンス表記を含める義務があるため、アプリ内のライセンス一覧画面へ含めてください。Flutterには依存パッケージのライセンスを一覧表示する仕組みが標準で備わっているので、通常はその画面で条件を満たせます。

11.0.0に上げるとAndroidの既存データは読めなくなりますか?

既定では読めます。migrateOnAlgorithmChangeがtrueのため、旧方式で保存された値は読み出し時に新方式へ書き直されます。ただし移行中にアプリが落ちると値を失う可能性があるため、migrateWithBackupをtrueにしたうえで、読み出しがnullになった場合の再ログイン経路を用意しておくのが安全です。minSdkが24へ上がる点は別途ビルド側の対応が要ります。

iOSでアプリを削除するとトークンも消えますか?

消えない場合があります。Keychainの項目はアプリの削除と連動しないことがあり、入れ直した端末で以前の値が読めてしまいます。再インストール時に初期化したいなら、shared_preferences側の初回起動フラグを見てdeleteAllを呼ぶ処理を入れてください。Androidはアプリ削除でデータ領域ごと消えるため、この対処はiOSとmacOS向けの備えです。

SharedPreferencesにトークンを入れてはいけないのですか?

推奨されません。shared_preferencesの保存先は平文で、root化やジェイルブレイクを経た端末、あるいはバックアップ経由で読み出せる状態です。公式の説明でも重要なデータの保存には使わないよう明記されています。加えてレガシーのSharedPreferences APIは非推奨で、新規実装ではSharedPreferencesAsyncSharedPreferencesWithCacheを使う流れになっています。

Webビルドでもトークン保存に使ってよいですか?

条件付きです。HTTPSかlocalhostでのみ動作し、暗号鍵はブラウザのLocalStorageに置かれるため、ブラウザのデータ消去で復号できなくなります。XSSが成立した場合の防御にはならない点も理解しておいてください。Web単体で長期のリフレッシュトークンを持たせるより、短命のアクセストークンをメモリに置き、更新はサーバー側のCookieに任せる構成のほうが安全側に倒れます。埋め込んだWebページへログイン状態を渡す場面の設計は、flutter_inappwebviewとは?6.1系の停滞とJS連携・Cookie制御の判断で扱っています。

関連記事

資料請求

RELATED POSTS 関連記事