100日後に完成するゲームシステム 30日目「セーブデータの暗号化処理」

2021年2月12日

テクノロジー 特集

eyecatch セキュリティの大事さを、非常によく心得ている、弓削田です。 個人情報や、企業情報の漏洩事件、ネット仮想通貨のハッキングによるデータ改ざん、デジタルの世界は、セキュリティが無いと非効率を被ってしまうことだらけです。 そんなわけで、今回はゲーム内のセーブデータを安全に暗号化処理をしたいと思い、 暗号化処理を施したプログラムを作ったので、紹介しておきます。

暗号化と復号化の基礎知識

まず、セキュリティにつきものの暗号化や復号化、他にも、鍵というキーワードを、よく耳にすると思いますが、 こうした単語についての知識が無いと、今回のブログを読むのがしんどいと思うので、簡単に説明しておきます。 まず、暗号化というのは、データ(何かの文字列)を、別の文字に置き換えて、他の人が読めないようにする処理です。 デジタルの世界では、文字列の1文字毎に持っている文字コードを基準に処理をすることが多いので、文字数の多いデータを暗号化すると、 暗号化されたデータも、それに合わせて膨らんで大きくなっていくという特色があります。 次に、復号化ですが、暗号化されたデータを、もとのデータ(文字列)に戻すという処理です。 そして、鍵というのは、暗号化をする時に、鍵という任意のコードをかけ合わせたコードにすることで、その鍵を知らないと復号化が正常にできないという使い方をする機能です。 もちろん、安易な鍵を使うと、簡単に解析ができてしまうという事なので、より複雑な鍵にするということで、かつてSSLと呼ばれていたインターネットのデータ転送セキュリティも 今では、TLSというより鍵の容量が大きなタイプに変更されています。 今後も、どんどん大きくなっていきますが、量子コンピュータが世の中の標準になったら、このTLSもソッコーで解読できてしまうので、やりかたが全く変わってくるということが言われています。

今回の暗号化処理技術について

暗号化で、かなり一般的なシーザー暗号という処理を使って、仕組みを作ってみました。 このシーザー暗号は、wikipediaに解説が書かれているので、詳しくはそちらを御覧ください。 https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%BC%E3%82%B6%E3%83%BC%E6%9A%97%E5%8F%B7 簡単に説明をすると、暗号化する時の文字コードを、一定数値分、ずらすことで、そのままもとに戻しても、正常な状態に戻らないことになります。 そして、このずらす一定値を「鍵」として扱って、その鍵を使って、一定時もどして、複合化することで、元通りになるという暗号処理です。 かなり初歩的な暗号化ですが、処理がシンプルでに出来るという点と、鍵を工夫することで、複合されにくい仕組みを作ることは可能になるので、 こちらを基本仕様にしたいと思います。

サンプル・コード

実際作ったプログラムは、以下のコードになります。 crypt.jsというファイル名にコピーして、以下のようにHTMLにセットすることで使用でいます。 window.$$crypt = (function(){ let MAIN = function(key){ this.key = this.set_key(key); }; MAIN.prototype.set_key = function(key){ if(typeof key === "number"){ key = String(key); } let res = 0; for (let i = 0; i < key.length; i++) { res += key.charCodeAt(i); } return res; }; MAIN.prototype.encode = function(val) { return this.encode_bin(val); }; MAIN.prototype.decode = function(val) { return this.decode_bin(val); }; // << 62進数・パターン /* 暗号化 */ MAIN.prototype.delimitor_decimal = ":"; MAIN.prototype.encode_decimal = function(val) { let key = this.key !== undefined ? this.key : 0; val = encodeURIComponent(val); let arr = []; for (let i = 0; i < val.length; i++) { let conv = this.decimal_encode(val.charCodeAt(i) + key); arr.push(conv); } return btoa(arr.join(this.delimitor_decimal)); }; /* 復号化 */ MAIN.prototype.decode_decimal = function(enc_val) { let key = this.key !== undefined ? this.key : 0; let arr = atob(enc_val).split(this.delimitor_decimal); let res = ""; for (let i = 0; i < arr.length; i++) { res += String.fromCharCode(this.decimal_decode(arr[i]) - key); } return decodeURIComponent(res); }; // バイナリコード・パターン >> // << 16進数・パターン /* 暗号化 */ MAIN.prototype.delimitor_bin = ":"; MAIN.prototype.encode_bin = function(val) { let key = this.key !== undefined ? this.key : 0; val = encodeURIComponent(val); let arr = []; for (let i = 0; i < val.length; i++) { let hex = (val.charCodeAt(i) + key).toString(16); arr.push(hex); } return btoa(arr.join(this.delimitor_bin)); }; /* 復号化 */ MAIN.prototype.decode_bin = function(enc_val) { let key = this.key !== undefined ? this.key : 0; let arr = atob(enc_val).split(this.delimitor_bin); let res = ""; for (let i = 0; i < arr.length; i++) { res += String.fromCharCode(parseInt(arr[i],16) - key); } return decodeURIComponent(res); }; // バイナリコード・パターン >> // << 文字列変換パターン /* 暗号化 */ MAIN.prototype.encode_code = function(val) { let key = this.key !== undefined ? this.key : 0; val = encodeURIComponent(val); let res = ""; for (let i = 0; i < val.length; i++) { res += String.fromCharCode(val.charCodeAt(i) + key); } return btoa(encodeURIComponent(res)); }; /* 復号化 */ MAIN.prototype.decode_code = function(enc_val) { let key = this.key !== undefined ? this.key : 0; let val = decodeURIComponent(atob(enc_val)); let res = ""; for (let i = 0; i < val.length; i++) { res += String.fromCharCode(val.charCodeAt(i) - key); } return decodeURIComponent(res); }; // 文字列変換パターン >> // Lib /** * 62進数 **/ MAIN.prototype.decimal_chars = function(){ if(this.chars === undefined){ var chars = "0123456789"; chars += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; chars += "abcdefghijklmnopqrstuvwxyz"; chars += "!\"#$%&'()*+,-./"; chars += ";<=>?@"; chars += "[\\]^_`"; chars += "{|}~"; chars += "¥¦§¨©ª"; this.chars = chars; } return this.chars; }; MAIN.prototype.decimal_encode = function(num){ var chars = this.decimal_chars(); var cn = chars.length; var str = []; var a1 , a2; while (num != 0) { a1 = parseInt(num / cn); a2 = num - (a1 * cn); str.unshift(chars.substr(a2,1)); num = a1; } var res = str.join(""); res = (!res)?"0":res; return res; }; MAIN.prototype.decimal_decode = function(num){ var chars = this.decimal_chars(); var char2 = {}; var cn = chars.length; for (var i=0; i< cn; i++) { char2[chars[i]] = i; } var str = 0; for (var i=0; i<num.toString().length; i++) { str += char2[num.substr((i+1)*-1, 1)] * Math.pow(cn, i); } return str; }; return MAIN; })()

実行結果

<h1>独自暗号化処理</h1> <script src="crypt.js"></script> <div id="value"></div>> <script> let val = "あいうえおabcde"; document.getElementById("value").textContent = val; console.log("-- decimal"); let res_enc0 = new $$crypt("test").encode_decimal(val); console.log(res_enc0); let res_dec0 = new $$crypt("test").decode_decimal(res_enc0); console.log(res_dec0); console.log("-- bin"); let res_enc1 = new $$crypt("test").encode_bin(val); console.log(res_enc1); let res_dec1 = new $$crypt("test").decode_bin(res_enc1); console.log(res_dec1); console.log("-- str"); let res_enc2 = new $$crypt("test").encode_code(val); console.log(res_enc2); let res_dec2 = new $$crypt("test").decode_code(res_enc2); console.log(res_dec2); </script> 上記ページを実行すると、コンソールに結果が表示されます。 "あいうえおancde"という文字列を変換すると、 -- decimalNHs6NU06NTQ6NHs6NTk6NTI6NHs6NTk6NTM6NHs6NU06NTQ6NHs6NTk6NTI6NHs6NTk6NTU6NHs6NU06NTQ6NHs6NTk6NTI6NHs6NTk6NTc6NHs6NU06NTQ6NHs6NTk6NTI6NHs6NTk6NTk6NHs6NU06NTQ6NHs6NTk6NTI6NHs6NTk6NUk6NW86NXA6NXE6NXI6NXM= あいうえおabcde -- bin MWU1OjIwNToxZjM6MWU1OjFmODoxZjE6MWU1OjFmODoxZjI6MWU1OjIwNToxZjM6MWU1OjFmODoxZjE6MWU1OjFmODoxZjQ6MWU1OjIwNToxZjM6MWU1OjFmODoxZjE6MWU1OjFmODoxZjY6MWU1OjIwNToxZjM6MWU1OjFmODoxZjE6MWU1OjFmODoxZjg6MWU1OjIwNToxZjM6MWU1OjFmODoxZjE6MWU1OjFmODoyMDE6MjIxOjIyMjoyMjM6MjI0OjIyNQ== あいうえおabcde -- str JUM3JUE1JUM4JTg1JUM3JUIzJUM3JUE1JUM3JUI4JUM3JUIxJUM3JUE1JUM3JUI4JUM3JUIyJUM3JUE1JUM4JTg1JUM3JUIzJUM3JUE1JUM3JUI4JUM3JUIxJUM3JUE1JUM3JUI4JUM3JUI0JUM3JUE1JUM4JTg1JUM3JUIzJUM3JUE1JUM3JUI4JUM3JUIxJUM3JUE1JUM3JUI4JUM3JUI2JUM3JUE1JUM4JTg1JUM3JUIzJUM3JUE1JUM3JUI4JUM3JUIxJUM3JUE1JUM3JUI4JUM3JUI4JUM3JUE1JUM4JTg1JUM3JUIzJUM3JUE1JUM3JUI4JUM3JUIxJUM3JUE1JUM3JUI4JUM4JTgxJUM4JUExJUM4JUEyJUM4JUEzJUM4JUE0JUM4JUE1 あいうえおabcde こんな感じになりました。

解説

複数の結果を用意したのは、 通常、暗号化すると、文字のサイズが大きく膨らんでしまうので、それをできるだけ抑えようと、いくつかのパターンを設けてみました。 -- str こちらは、ストレートに、文字コードを鍵分シフトして暗号化したストレートな変換です。 -- bin こちらは、シフトした文字コードを16進数に変換して、それを暗号化した変換です。10進数を16進数に変換しているので、3/4ぐらいの容量になる簡易計算です。 -- decimal そして、一番文字数が少ないこれは、100進数というモードを以前に構築したことがあり、それを挿入して、1/10のサイズに変換しています。 実際は、デリミタ文字列が加わるので、1/10とまではいきませんが、かなり文字数が削減されていることが確認できます。 とりあえず、この仕様を採用してみようと思います。

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ