🔐

機密情報をflutterで扱うには…

2024/01/11に公開

前書き

この記事は、著者が調べ個人的にまとめたものとなります。そのため、誤りが含まれている場合がございます。もし誤りを見つけた場合はコメント等で教えていただけますと大変ありがたいです。

この記事を読むのにおすすめの方

  • なぜソースコードに機密情報を含めてはいけないのかを知りたい方
  • 機密情報をどのように扱えばいいかわからない方
  • 難読化と暗号化がイマイチよくわからない方
  • enviedについて知りたい方

はじめに

業務内で機密情報を扱う機会があり、enviedとSecretManager(AWS Secrets Manager,Google Cloud Secret Manager)を知りました。そこで、そもそもなぜソースコードにそのまま機密情報を載せてはいけないのか、enviedとSecretManagerを併用する理由までを書くことにします。

ソースコードに機密情報を含めてはいけない理由

flutterアプリをコンパイルすると人間が理解しやすい形(高水準言語)で書かれたプログラムをコンピュータが直接理解することができる形式(バイナリ形式)に変換し、バイナリファイルとして配布されます。

では、このバイナリファイルから高水準言語に変換する(逆コンパイル)ことは可能なのでしょうか。Androidアプリケーションのバイナリファイルの一種であるAPKファイルを例に挙げて説明します。APKファイルはapktoolを用いるととても簡単に逆コンパイルすることができます。

ApktoolはAPKファイルを解剖するためのツールのようなものです。このtoolを用いるとアプリの内部構造を見たり、カスタマイズしたり、アプリの基本を学んだりすることができます。
https://apktool.org/

インストール方法(homebrewを使用)

brew install apktool

apktoolを用いて実際に逆コンパイルを行ってみましょう。
今回逆コンパイラするもの(flutter createした直後のもの)

flutterでよく見る画面

まずはapkファイルを用意します。

flutter build apk

実行結果を見るとflutter-apkフォルダ内にapkファイルが出来上がっていることがわかります。このファイルを逆コンパイルしましょう。

tmp % flutter build apk

💪 Building with sound null safety 💪

Running Gradle task 'assembleRelease'...                         1,303ms
✓  Built
build/app/outputs/flutter-apk/app-release
.apk (16.8MB).

逆コンパイルするコマンド

apktool d {逆コンパイルしたいapkファイル名}.apk

実行結果は下記のようになります。

flutter-apk % apktool d app-release.apk
I: Using Apktool 2.9.0 on app-release.apk
I: Loading resource table...
I: Decoding file-resources...
W: Cant find 9patch chunk in file: "O3.9.png". Renaming it to *.png.
I: Loading resource table from file: /Users/toumayukako/Library/apktool/framework/1.apk
W: Cant find 9patch chunk in file: "09.9.png". Renaming it to *.png.
W: Cant find 9patch chunk in file: "Pq.9.png". Renaming it to *.png.
W: Cant find 9patch chunk in file: "T2.9.png". Renaming it to *.png.
W: Cant find 9patch chunk in file: "jK.9.png". Renaming it to *.png.
W: Cant find 9patch chunk in file: "93.9.png". Renaming it to *.png.
W: Cant find 9patch chunk in file: "tr.9.png". Renaming it to *.png.
W: Cant find 9patch chunk in file: "Xs.9.png". Renaming it to *.png.
W: Cant find 9patch chunk in file: "8V.9.png". Renaming it to *.png.
W: Cant find 9patch chunk in file: "eK.9.png". Renaming it to *.png.
W: Cant find 9patch chunk in file: "hj.9.png". Renaming it to *.png.
W: Cant find 9patch chunk in file: "dH.9.png". Renaming it to *.png.
I: Decoding values */* XMLs...
I: Decoding AndroidManifest.xml with resources...
I: Regular manifest package...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
I: Copying META-INF/services directory

実行前のフォルダと実行後のフォルダを比較してみましょう。
実行前のフォルダ

flutter build apkした直後のfutter-apkフォルダ

実行後のフォルダ
apktool d をした直後のfutter-apkフォルダ
apkファイルの逆コンパイルによってソースコードを取得することが簡単にできてしまいました。このことから、ソースコードの中に機密情報が含まれていた場合、悪意のある第三者に使用されると簡単に機密情報を抜き取られてしまうということがわかります。

ではどのようにしたら機密情報を守ることができるのでしょうか。

enviedとSecret Managerについて

機密情報を悪意ある第三者から守るためにここではenviedとSecret Managerについて説明します。

envied

https://pub.dev/packages/envied

.envファイルから環境変数を読み込むことができるflutterパッケージ

enviedを用いる理由としては難読化を行う事ができるため、機密情報を悪意ある第三者が読みにくい形式に書き換えてくれます。

enviedではobfuscate (難読化)オプションをtrueにすることで、.envファイルに保存した機密情報を難読化してdart変数の自動生成することができます。類似パッケージとしてfutter_dotenvパッケージがありますがenviedはfutter_dotenvと異なり難読化のサポートとdartアナリストが型推論しやすくしてくれるというメリットがあります。

必要なパッケージのインストール方法

$ flutter pub add envied
$ flutter pub add --dev envied_generator
$ flutter pub add --dev build_runner

使用方法

  1. .envファイルに機密情報を保存する
DEMO_TOKEN = "KOREHADEMODESUYOBARETAKUNAINA"
  1. env.dartを作成し下記のようなクラスを作成する
import 'package:envied/envied.dart';

part 'env.g.dart'

()
abstract class Env {
    (varName: 'DEMO_TOKEN',obfuscate: true)
    static const String key = _Env.demotoken;
}
  1. build_runnerを実行する
flutter pub run build_runner build

4.env.dartを読み込み利用する

print("実行結果:${Env.key}"//実行結果:KOREHADEMODESUYOBARETAKUNAINA

使用結果
.envで保存されていたDEMO_TOKENを、env.g.dartで難読化してくれている。

.envファイルに保存されたTOKEN

DEMO_TOKEN = "KOREHADEMODESUYOBARETAKUNAINA"

env.g.dartで難読化されたTOKEN

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'env.dart';

// **************************************************************************
// EnviedGenerator
// **************************************************************************

class _Env {
  static const List<int> _enviedkeydemotoken = [
    3393644951,
    1195623047,
    2427837516,
    2910446672,
    1858426891,
    1135633294,
    1277408044,
    1873488489,
    3088080118,
    3392328430,
    3598641836,
    3382237227,
    854456591,
    2984332823,
    912120966,
    3029154490,
    3360286442,
    1989102396,
    2358871776,
    1115501652,
    2720661792,
    3793049848,
    1140447417,
    1272584134,
    1895410315,
    2774323054,
    3711860359,
    675010268,
    1538103029
  ];
  static const List<int> _envieddatademotoken = [
    3393645020,
    1195623112,
    2427837470,
    2910446613,
    1858426947,
    1135633359,
    1277408104,
    1873488428,
    3088080059,
    3392328353,
    3598641896,
    3382237294,
    854456668,
    2984332866,
    912121055,
    3029154549,
    3360286376,
    1989102461,
    2358871730,
    1115501585,
    2720661876,
    3793049785,
    1140447474,
    1272584083,
    1895410373,
    2774322991,
    3711860430,
    675010194,
    1538102964
  ];
  
  static final String demoteken = String.fromCharCodes(
    List.generate(_envieddatademotoken.length, (i) => i, growable: false)
        .map((i) => _envieddatademotoken[i] ^ _enviedkeydemotoken[i])
        .toList(growable: false),
  );
}

しかしこの方法では、.envファイルやenv.g.dartファイルをGithubにコミットしてしまうと情報漏洩してしまいます。Github等のサービスを用いずにファイルを管理するのは容易ではなく、幾ばくか工夫が必要になります。

Secret Manager

SecretManager(AWS Secrets Manager,Google Cloud Secret Manager)とは簡単にいうと機密情報を入れて鍵をかけて保管ができるものです。そのため、Githubで管理したくない.env等の機密情報を含むファイルをAWSで管理することができます。そうすると、逆コンパイル等で機密情報を取得される可能性もなくなり、アプリケーションのセキュリティを強化することができます。

特にサーバーアプリケーションでは機密情報をソースコードとともに保存する必要がなくなるため、逆コンパイルによる情報漏洩のリスクが下がります。しかし、機密情報を取得するためにSecret Managerにアクセスする必要があるためモバイルアプリケーションにて利用するには適切でないことがあります。

AWS Secrets Managerの使用方法:

  1. AWS Management Consoleにログインします。
  2. サービスメニューから「Secrets Manager」を選択します。
  3. 「新しいシークレットを保存」をクリックします。
  4. シークレットの種類(例えば、データベースの資格情報やその他のキー/値のペア)を選択し、必要な情報を入力します。
  5. シークレットの名前とオプションで説明を入力します。
  6. 自動的にパスワードをローテーションする場合は、その設定を行います。
  7. 「次のステップ」をクリックして設定を確認し、「保存」をクリックします。

Google Cloud Secret Managerの使用方法:

  1. Google Cloud Consoleにログインします。
  2. ナビゲーションメニューから「Security」>「Secret Manager」を選択します。
  3. 「Create Secret」をクリックします。
  4. シークレットの名前を入力し、シークレットの値を入力またはアップロードします。
  5. 必要に応じて、シークレットのバージョンを管理するための設定を行います。
  6. 「Create」をクリックしてシークレットを作成します。これらの手順は基本的なもので、具体的な使用方法はシークレットの種類や使用するサービスにより異なる場合があります。また、APIやSDKを使用してプログラムからシークレットを管理することも可能です。

Secret Manager(AWS Secrets Manager,Google Cloud Secret Manager)を用いることにより、直接的にファイルを用いたやり取りをする必要なく開発メンバーに機密情報を共有することができ、セキュリティ面でも機密情報を誤って公開してしまうことや第三者によって漏洩するリスクを削減することができます。

[オマケ] 難読化と暗号化について

機密情報をもし、対策を何も行っていないファイルに書き込みリリースをしてしまうと逆コンパイラを用いれば悪意ある第三者に抜き出されてしまう可能性があることを話しました。

ではどのようにすれば、抜き出されないのでしょうか。
この記事では難読化と暗号化の二種類について話します。

難読化

機密情報自体を抜き出しにくい(抜き出しても理解しにくい)ものにする方法
ソースコードの一部を隠したり変更したりすることにより、逆コンパイラ等をされても悪意ある第三者に情報を抜き取られることを難しくします。

この方法を用いると、apkで逆コンパイラを使用されても機密情報を取り出してもすぐには機密情報を理解することはできません。しかし、解読方法を見破られてしまうと悪意ある第三者に正しい機密情報を理解されてしまうというデメリットがあります。

暗号化

機密情報に鍵をかけてしまう方法
鍵を用いてデータを読めないようにすることにより、悪意ある第三者に情報を抜き取らせないようにします。

この方法を用いると、鍵を持っている人にしか機密情報を理解することはできません。仮に悪意ある第三者が全ての鍵を試せば読むことはできますが、128ビットの長さのもので10^12年以上かかるとされています。

難読化と暗号化の違い

難読化はソースコードを逆コンパイラされた際に時間をかければ解読方法を見破ることができます。そのため、機密情報を扱う際に確実に機密情報が漏れないとは言い難い方法です。それに対し暗号化は鍵が漏洩しない限りは機密情報が漏れないような方法です。仮に漏れるとしてもとてつもない時間がかかるとされています。

追記

[4/29]enviedを用いて開発する場合において、env.g.dartのような生成されたコードは.gitignoreに入れるのを忘れないように気を付けてください。

参考文献

今回記事を書くにあたり参考にした記事を紹介します。
https://zenn.dev/tristar/scraps/db0555636ccfe2

https://pub.dev/packages/envied

https://cloud-ace.jp/column/detail215/#:~:text=Secret Manager は、デフォルトで,分けることが可能です。

https://dev.classmethod.jp/articles/about-secrets-manager/

https://cloudnavi.nhn-techorus.com/archives/2727#:~:text=「難読化」とは、,することができます。

https://tooljp.com/windows/chigai/html/IT/encrypt-Obfuscation-chigai.html

https://www.umin.ac.jp/security/encryption1.htm

Discussion