NCMBのキーをファイルストアから暗号化Zipファイルで配信する

NCMBで利用するアプリケーションキーとクライアントキーは、アプリ側の設定やACL（アクセス権限）が適切に設定されていれば、万一漏洩したとしても即座に不正利用につながるものではありません。

しかし、漏洩したことが分かればキーの再生成に加えて、アプリのビルドし直しやレビュー再提出と言った手間が発生します。

そこで、キーをファイルストアに保存しておき、それを随時取得する形の運用はできないか検討しました。100%必ず安全という訳ではありませんが、参照にしてください。

今回はWebやMonacaでの利用を想定しています。他の環境であればコンパイルするので、キーの漏洩は防げるかと思います。

## ファイルストアのHTTPSファイル公開を利用する

NCMBのファイルストアでは、保存したファイルをHTTPS経由でアクセスできる機能があります。これを使って、認証キーなしでファイルストアからファイルをダウンロードできるようにします。設定は管理画面にあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/5b5e5177-bdde-d595-f9fa-56aeddc03129.png)


## ファイルを暗号化Zipでアップロードする

この時アップロードするファイルは暗号化Zipファイルとします。今回は `config.zip` としています。内容は以下のようなJSONファイル `config.json` を暗号化したものです。パスワードは `ncmb` などとします。

```json
{
	"applicationKey": "YOUR_APPLICATION_KEY",
	"clientKey": "YOUR_CLIENT_KEY"
}
```

この時のファイルURLは以下のようになるでしょう。

```
https://mbaas.api.nifcloud.com/2013-09-01/applications/uScDnX7D9IBrBMCU/publicFiles/config.zip
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/647f299b-2c8e-3d69-fcfd-41f97c5bc6c6.png)


## コードについて

ではこのファイルを読み込むのに必要なライブラリとしてzip.jsを使います。

```html
<script src="https://unpkg.com/@zip.js/zip.js@2.6.62/dist/zip.min.js"></script>
<script src="https://unpkg.com/ncmb@3.1.5/ncmb.min.js"></script>
```

### 変数定義

先ほどのZip暗号化ファイルのURLと、パスワードを記述します。今回は平文で記述していますが、パスワードも暗号化するなりした方が良いでしょう。

```jsx
const configZip = 'https://mbaas.api.nifcloud.com/2013-09-01/applications/uScDnX7D9IBrBMCU/publicFiles/config.zip';
const password = 'ncmb';
```

## イベントハンドリング

Monacaであれば `deviceready` 、Webの場合は `DOMContentLoaded` のイベントを使います。

```jsx
const event = window.cordova ? 'deviceready' : 'DOMContentLoaded';
document.addEventListener(event, async (e) => {
  // この中に記述
});
```

## Zipファイルのダウンロード

Zipファイルはfetch関数でダウンロードします。

```jsx
const res = await fetch(configZip);
const blob = await res.blob();
```

## 暗号化Zipファイルの中身を取り出す

zip.jsを使って暗号化Zipファイルの内容を読み取ります。

```jsx
const config = JSON.parse(await decompress(blob));
```

`decompress` 関数の内容です。今回はconfig.jsonしか圧縮していないという決め打ちです。また、その内容はテキストファイルとして読み込みます。

```jsx
const decompress = async (blob) => {
	// Zip読み取り用リーダー
  const reader = new zip.ZipReader(new zip.BlobReader(blob), { password });
  // 1つ目のファイルが設定ファイル
  const [entry] = await reader.getEntries();
  // テキストでファイル内容を読み取り
  return entry.getData(new zip.TextWriter());
}
```

## NCMBの初期化

後はテキスト内容を使ってNCMBを初期化するだけです。

```jsx
window.ncmb = new NCMB(config.applicationKey, config.clientKey);
```

## 全体の処理

今回の処理全体は以下のようになります。

```jsx
// 設定ファイルのURL
const configZip = 'https://mbaas.api.nifcloud.com/2013-09-01/applications/uScDnX7D9IBrBMCU/publicFiles/config.zip';
// 暗号化Zipファイルのパスワード
const password = 'ncmb';

const event = window.cordova ? 'deviceready' : 'DOMContentLoaded';
document.addEventListener(event, async (e) => {
  // Zipファイルを取得
	const res = await fetch(configZip);
  const blob = await res.blob();
  // 設定ファイルの内容を読み込む
  const config = JSON.parse(await decompress(blob));
  // NCMBの初期化
	window.ncmb = new NCMB(config.applicationKey, config.clientKey);  
});

const decompress = async (blob) => {
	// Zip読み取り用リーダー
  const reader = new zip.ZipReader(new zip.BlobReader(blob), { password });
  // 1つ目のファイルが設定ファイル
  const [entry] = await reader.getEntries();
  // テキストでファイル内容を読み取り
  return entry.getData(new zip.TextWriter());
}
```

## 注意点

結局のところ、暗号化Zipファイルのパスワードが漏洩すると、Zipファイルを伸張できてしまいます。パスワードを暗号化したとしても、そのキーが漏洩したら…と堂々巡りにはなりそうです。

`password` などの平易な変数名を使わないようにしたり、難読化する、パスワード文字列を[JavaScript Obfuscator Tool](https://obfuscator.io/)で難読化するといった対処になるでしょう。[Monacaアプリの場合はロジック暗号化プラグイン](https://ja.docs.monaca.io/reference/power_plugins/html5_resource_encryption)を使って、ロジック全体を暗号化するのがお勧めです。

また、ファイルストアへのHTTPSアクセスもAPIアクセスにコールされるので注意してください。気になる場合には別なサーバーにアップしておく（CORS設定も必要そうです）のがお勧めです。

## まとめ

ロジックが公開されてしまうJavaScriptでキーを隠蔽化するのには限度がありますが、ファイルストアに保存しておくことで、万一漏洩した際の差し替えも手軽になります。平文のファイルで保存するとあまり意味がないので、暗号化Zipファイルを使ってみました。

前述の通り、アプリの設定やACLを適切に行うのは基本として、その上でキーを隠蔽化したい場合に参考としてください。