タイトルは釣りです。必ず守れるというものではありません。
また、守り方を公開する必要もありません。
守る方は全ての穴を防ぐ必要があるのに対し、攻める方は1つだけ穴を見つければいいからです。
JavaScript言語はメモリのお掃除が任意のタイミングで実行できない言語となっており、
いったん秘密鍵で署名を行ったりすると、アプリを終了するまでは
その情報はメモリ上に残ったままとなっている可能性があります。
このメモリ上に残った秘密鍵は、万が一PCがマルウェアに感染していた場合、
メモリダンプという手法で取り出すことが出来てしまいます。
もちろん、マルチシグやハードウェアウォレットを使用していれば万全です。
ただし、どちらにしても送金先のアドレスは多要素で再確認するのが絶対です。
今回は、マルチシグやハードウォレットを使うにしても
署名を行うアプリなどで必須となる秘密鍵の扱についての提案です。
前回、秘密鍵を16進数表記ではなく、バイト配列という形でメモリ上に扱うべきという話をしました。
秘密鍵というのはとにかく目立つ形をしているのです。
悪いことを考えるやつなら、キーワード検索でぱっとひっかけることができるぐらいの手軽さです。
画面上に表示された情報をもとにツールを使って探索されることも多いので、
少し形を変えてやるだけでも、多少のハッキング防止効果は出てくると思います。
ここではさらに一歩すすめて、排他的論理和を用いて秘密鍵のバイト配列をそのままでは使用できないように変換する方法をご紹介します。
ビット演算の排他的論理和は2度繰り返すと元に戻るという性質があります。
もちろんそれを復元する鍵となるものが必要ですので、それはサービスプロバイダーがサーバに保管しておくなりなんなりする必要があります。
今回は公開鍵を用います。
var privateKeyBytes = getChildKeyBytes(masterHash,networkId,"m/0");
var publicKeyBytes = hex2ua(account.publicKey.toString());
console.log(ua2hex(privateKeyBytes));
var masterSeed = Array();
for( var i = 0; i < privateKeyBytes.length; i++) {
masterSeed.push(privateKeyBytes[i] ^ publicKeyBytes[i]);
}
let masterKeyBytes = Array();
for( var i = 0; i < masterSeed.length; i++) {
masterKeyBytes.push(masterSeed[i] ^ publicKeyBytes[i]);
}
console.log(ua2hex(masterKeyBytes));
//16進数に変換
var ua2hex = function ua2hex(ua) {
var s = '';
for (var i = 0; i < ua.length; i++) {
var code = ua[i];
s += _hexEncodeArray[code >>> 4];
s += _hexEncodeArray[code & 0x0F];
}
return s;
};
//バイト配列に変換
var hex2ua = function hex2ua(hexx) {
var hex = hexx.toString(); //force conversion
var ua = new Uint8Array(hex.length / 2);
for (var i = 0; i < hex.length; i += 2) {
ua[i / 2] = parseInt(hex.substr(i, 2), 16);
}
return ua;
};
まず、バイト配列化した秘密鍵と公開鍵を準備します。(privateKeyBytes , publicKeyBytes )
マスターシードとして、それぞれの配列に対して排他的論理和をとり、それをマスターシードの配列に詰め込んでいきます。
これでマスターシードの完成です。簡単ですね。
同一セッション上の簡単な権限管理はこの値を持って認証するようにして、わざわざ秘密鍵を確認しないようにします。
再び秘密鍵を取り出したい場合は、さいどマスターシードのバイト配列に公開鍵の排他的論理和を取っていきます。
最後に16進数表示にすれば、同じ秘密鍵が復元できたことが確認できるでしょう。