ニフクラmBaaSお役立ちブログ

スマホアプリ開発にニフクラmBaaS。アプリ開発に役立つ情報をおとどけ!

JavaScript SDKの署名処理をWebAssembly化する!(未達)

f:id:mbaasdevrel:20180119154130p:plain

こちらはまだ実践中で、成功していないのですが…。もしアイディアがある方は @nifcloud_mb または コミュニティまでご連絡ください。

JavaScript SDKの抱える課題

JavaScript SDKの大きな欠点として、アプリケーションキーとクライアントキーが見えてしまうと言う問題があります。多少の難読化はできますが、それでも変数をウォッチしてしまえば分かってしまうでしょう。

そんな中、一筋の光明が見えてきています。それはUnityがWebGLに対応し、その中でニフクラ mobile backendが使われているということです。この時使われている技術がWebAssemblyになります。

WebAssemblyとは?

HTML5 APIの一つで、Webブラウザ上でバイナリファイルを実行できる技術になります。バイナリ、つまりコンパイルされていますのでソースコードが読まれることはありません。先日、iOS11のSafariが対応したことで、殆どのモダンなWebブラウザで WebAssembly が使えるようになりました。

f:id:mbaasdevrel:20180119153952p:plain

Can I use... Support tables for HTML5, CSS3, etc

IE11、Opera Mini、UC Browser for Android、Samsung Internetは非対応となっていますが、それ以外のブラウザでは利用できます。デスクトップであればほぼ問題ないでしょう。

その WebAssembly は主に以下の言語が対応しています。

  • C
  • C++
  • Rust
  • C#

LLVMから WebAssembly に変換することはできるので、SwiftなどもLLVMに変換すれば使えるようです(まだ試していませんが)。

やりたいこと

WebAssemblyを使うところは署名処理のみになります。つまり与えられた文字列に対してクライアントキーでHMAC SHA2でハッシュを作成し、さらにBASE64でエンコードするだけです。これさえできてしまえばクライアントキーを漏洩せず、Webアプリケーションの中でmBaaSが使えるようになります。

さて、ここからは葛藤になります。

Rust

まず試したのはRustです。RustではCargoというパッケージ管理が使われています。必要な外部パッケージとしては hmac / base64 / sha2 になります。

[package]
name = "ncmb"
version = "0.1.0"
authors = ["Atsushi Nakatsugawa <atsushi@moongift.jp>"]
crate-type = "cdylib"

[dependencies]
hmac = "0.5.0"
base64 = "0.9.0"
sha2 = "0.7.0"

そしてRustのコードは次のようになります。

extern crate base64;
extern crate hmac;
extern crate sha2;
use std::os::raw::c_char;
use std::ffi::CString;
use std::ffi::CStr;

fn main() {

}

#[no_mangle]
pub fn sig(c_buf: *const c_char) -> *const c_char {
  use hmac::Mac;
  let request_body: &CStr = unsafe { CStr::from_ptr(c_buf) };
  let key = "134...f75";
  let mut mac = hmac::Hmac::<sha2::Sha256>::new(key.as_bytes()).unwrap();
  let bytes = request_body.to_bytes();
  mac.input(bytes);
  let digest = mac.result();
  let expect_signature = base64::encode(&digest.code().to_vec());
  expect_signature.as_ptr() as *const c_char
}

mainがないとエラーになるので、それを追加しています。多少コードは変わってしまっているのですが、デスクトップであれば適切な署名が生成されるのは確認しています。

WebAssemblyを生成する処理についてはRust単体でWebAssemblyをコンパイルする(Emscripten無し)が大変参考になりました。Cargoを使っていると crate-type=cdylib を指定する方法が分からず、 rustc でライブラリを引数として与える形でコンパイルを行いました。

コンパイルすると WebAssemblyのバイナリは生成されるのですが、読み込むとメモリエラーになってしまいました。デバッグもしづらいため、ここでRustは諦めました。

C Sharp

元々UnityではC#から WebAssemblyを生成して動かしています。つまりできる可能性があるということです。UnityではMonoを使っています。そこで Mono-wasmというMono用のWebAssemblyコンパイラーを利用します。

こちらのコードもとても簡単です。しかも基本的にすべて標準ライブラリのみで実装できます。

using System;
using System.Text;
using System.Security.Cryptography;
using Mono.WebAssembly;

class Hello
{
    static void Sig(string stringData, string element_id)
    {
      string result = null;
      byte[] secretKeyBArr = Encoding.UTF8.GetBytes ("aaa");
      byte[] contentBArr = Encoding.UTF8.GetBytes (stringData);
      HMACSHA256 HMACSHA256 = new HMACSHA256 ();
      HMACSHA256.Key = secretKeyBArr;
      byte[] final = HMACSHA256.ComputeHash (contentBArr);
      result = System.Convert.ToBase64String (final);
      var elem = HtmlPage.Document.GetElementById(element_id);
      elem.InnerText = result;
    }
    
    static int Main(string[] args)
    {
    }
}

こちらは読み込みまではできるのですが、Sigメソッドを実行してみると new HMACSHA256() のところで例外が発生してしまうのが確認できています。ちなみにUnityで同様の処理を作って試してみた限りではエラーは出ずにちゃんと動作します。Unityすごい!その部分だけ使えないか生成されたWebアプリケーションを見てみたのですが、UI周りのコードもすべて入ってしまっているので無理でした。

まとめ

次の手段として考えられるのは Unity で行っている C# からC++のコードを生成し、そこからEmscriptenで WebAssemblyを生成する方法を真似することです。 IL2CPP がそういった処理を行っているので、これを学んでC++に変換する手があります。これがダメの場合(すでに symbol(s) not found for architecture x86_64 が出てハマっているのですが…)、C++でコードを組んで試す方法があります。ただし外部ライブラリを使う分、Rustと同じ結果になりそうな気もします。

もしいい手法がありましたらぜひ@nifcloud_mb または コミュニティまでご連絡ください!

中津川 篤司

中津川 篤司

NCMBエヴァンジェリスト。プログラマ、エンジニアとしていくつかの企業で働き、28歳のときに独立。 2004年、まだ情報が少なかったオープンソースソフトの技術ブログ「MOONGIFT」を開設し、毎日情報を発信している。2013年に法人化、ビジネスとエンジニアを結ぶDXエージェンシー「DevRel」活動をスタート。