Brainf*ckで暗号を作ってみる【C++】
はじめに
2回続けてBrainfuckの記事を書くとは私も思ってもみなかったのですが,ふとやりたくなったのでやってみました,という記事です.
なにをやりたくなったかというと「Brainfuck使えばイイ感じの暗号文が作れるのでは???」というもの.
暗号文については詳しくないです.
「イミテーションゲーム」を見て,アラン・チューリングかっけー,とかエニグマ暗号って名前かっこいい,とかそんな知識です.
でも意外と,やってみたら面白そうということでやってみました.
先駆者もいないんじゃないかな??
脆弱だから誰もやっていないんだと思うけど...笑
Brainfuck
この説明いるのかな...
簡単に言えば,8つの命令で成り立つチューリング完全な,難読プログラミング言語です.
詳しくは他の記事を見てくれると嬉しいです笑
この暗号みたいなBrainfuckを暗号文として使おうという企画?です.(日本語がややこしい)
先に紹介した,私の記事の1つ目で,Brainfuckのインタプリタを作成したんですが,これをデコーダとして使います.
エンコーダの実装の前に
これがこの記事の核です.
やりたいことは,「1.オリジナルの平文を受け取る」「2.暗号化(エンコード)」です.
これだけ,といえばこれだけです.
もちろん,暗号化したものは復元(デコード)できなければ意味ないのですが,今回はBrainfuckベースのインタプリタがあるので,そちらは既に手元にあるわけですね.
暗号化のフロー
C++でコーディングしますが,まずは暗号化のフローを簡単に考えてみます.
- 平文読み込み
- 平文をASCIIコードに変換
- ASCIIコードを元にBrainfuckコードを生成
- ファイル出力
というシンプル4ステップです.
多分3のコード生成をいかにそれっぽくやるかが問題ですね.
というのも,例えば「A」をBrainfuckのコードに落とした時,ASCIIコードは「65」なので,"+"を65個と"."一つを出力すれば,OKなわけです.
でも,これって面白くないですよね.
暗号文なのに,ポインタ移動の">"を加えて3種類しかないので,見た目のインパクトも欠けます.
ということで,せめてループ("["と"]")を使いこなしたいところです.
さらに,Brainfuckでは命令以外はコメントとしてみなされるので,たくさんノイズも加えられます.
これで,良い感じの暗号がつくれそうです!!
実装してみる
大きく分けて,エンコード部分とノイズ部分,そしてその他の雑処理含む全体のコードです.
それぞれ見ていきます.
ノイズ部分
これは特に,難しいこともありません.
ただ,命令と同じ文字はノイズとして加えられないので,そういったノイズが生成された場合は弾いて他のものに置き換える必要があります.
今回は,そういったものは改行コードとしました.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | char noise(){ char n = char(rand() % 256); if(n != PTR_INCREMENT && n != PTR_DECREMENT && n != BYTE_INCREMENT && n != BYTE_DECREMENT && n != OUTPUT && n != INPUT && n != LOOP_START && n != LOOP_END) return n; return '\n'; } |
あ,ちなみに各命令はマクロで前もって定義しています.
1 2 3 4 5 6 7 8 | #define PTR_INCREMENT '>' #define PTR_DECREMENT '<' #define BYTE_INCREMENT '+' #define BYTE_DECREMENT '-' #define OUTPUT '.' #define INPUT ',' #define LOOP_START '[' #define LOOP_END ']' |
エンコード部分
エンコード部分は,基本軸としてはASCIIコード分だけ"+"を書く方針ですが,ループを使うために少し工夫を加えます.
今回は,ループカウンタを10としたループのみに固定し,ループできそうなものはループさせる方針としました.
こうすることで,ASCIIコードを10で割った商と余りを使えば簡単にコーディングできそうです.
で,実装してみるとこんな感じ.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | vector<char> encode(int ascii){ vector<char> res; res.reserve(256); res.emplace_back(PTR_INCREMENT); int loop = 0; int rest = ascii; if(ascii > 10){ loop = ascii / 10; // used as a loop counter rest = ascii % 10; } if(loop > 0) { // if loop is existed for (int i = 0; i < loop; ++i) { res.emplace_back(BYTE_INCREMENT); // ++ ... + loop counter } res.emplace_back(LOOP_START); // [ loop start res.emplace_back(BYTE_DECREMENT); // - decrement loop counter res.emplace_back(PTR_INCREMENT); // > move the pointer to main for (int j = 0; j < 10; ++j) { res.emplace_back(BYTE_INCREMENT); // 10 times + } res.emplace_back(PTR_DECREMENT); // move to loop counter pointer res.emplace_back(LOOP_END); // ] res.emplace_back(PTR_INCREMENT); // move next pointer } for(int i = 0; i < rest; ++i){ res.emplace_back(BYTE_INCREMENT); } res.emplace_back(OUTPUT); return res; } |
そして,これに適当にノイズをちりばめてください.笑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | vector<char> encode(int ascii){ vector<char> res; res.reserve(256); res.emplace_back(PTR_INCREMENT); res.emplace_back(noise()); int loop = 0; int rest = ascii; if(ascii > 10){ loop = ascii / 10; // used as a loop counter rest = ascii % 10; } if(loop > 0) { // if loop is existed for (int i = 0; i < loop; ++i) { res.emplace_back(noise()); res.emplace_back(BYTE_INCREMENT); // ++ ... + loop counter } res.emplace_back(noise()); res.emplace_back(LOOP_START); // [ loop start res.emplace_back(BYTE_DECREMENT); // - decrement loop counter res.emplace_back(PTR_INCREMENT); // > move the pointer to main res.emplace_back(noise()); res.emplace_back(noise()); for (int j = 0; j < 10; ++j) { res.emplace_back(noise()); res.emplace_back(BYTE_INCREMENT); // 10 times + res.emplace_back(noise()); } res.emplace_back(PTR_DECREMENT); // move to loop counter pointer res.emplace_back(noise()); res.emplace_back(LOOP_END); // ] res.emplace_back(PTR_INCREMENT); // move next pointer } for(int i = 0; i < rest; ++i){ res.emplace_back(BYTE_INCREMENT); res.emplace_back(noise()); } res.emplace_back(OUTPUT); return res; } |
あとはメイン関数だけです.
最終的なコード
注:命令のマクロ定義は違うヘッダファイルで管理しているのを前提としたコードです.(詳しくはGithubのコードを見てね)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | #include <iostream> #include <fstream> #include <string> #include <vector> #include "orders.h" using namespace std; char noise(){ char n = char(rand() % 256); if(n != PTR_INCREMENT && n != PTR_DECREMENT && n != BYTE_INCREMENT && n != BYTE_DECREMENT && n != OUTPUT && n != INPUT && n != LOOP_START && n != LOOP_END) return n; return '\n'; } vector<char> encode(int ascii){ vector<char> res; res.reserve(256); res.emplace_back(PTR_INCREMENT); res.emplace_back(noise()); int loop = 0; int rest = ascii; if(ascii > 10){ loop = ascii / 10; // used as a loop counter rest = ascii % 10; } if(loop > 0) { // if loop is existed for (int i = 0; i < loop; ++i) { res.emplace_back(noise()); res.emplace_back(BYTE_INCREMENT); // ++ ... + loop counter } res.emplace_back(noise()); res.emplace_back(LOOP_START); // [ loop start res.emplace_back(BYTE_DECREMENT); // - decrement loop counter res.emplace_back(PTR_INCREMENT); // > move the pointer to main res.emplace_back(noise()); res.emplace_back(noise()); for (int j = 0; j < 10; ++j) { res.emplace_back(noise()); res.emplace_back(BYTE_INCREMENT); // 10 times + res.emplace_back(noise()); } res.emplace_back(PTR_DECREMENT); // move to loop counter pointer res.emplace_back(noise()); res.emplace_back(LOOP_END); // ] res.emplace_back(PTR_INCREMENT); // move next pointer } for(int i = 0; i < rest; ++i){ res.emplace_back(BYTE_INCREMENT); res.emplace_back(noise()); } res.emplace_back(OUTPUT); return res; } int main(int argc, char* argv[]){ try{ if(argc < 2){ throw invalid_argument("Oops! There are no files."); } string filename = argv[1]; ifstream ifs(filename); if(ifs.fail()){ throw invalid_argument("The file does not exist."); } // Read file int begin = static_cast<int>(ifs.tellg()); ifs.seekg(0, ifs.end); int end = static_cast<int>(ifs.tellg()); int size = end - begin; ifs.clear(); // clear EOF flag ifs.seekg(0, ifs.beg); char *original = new char[size + 1]; original[size] = '\0'; ifs.read(original, size); ifs.close(); cout << "Original : " << original << endl; cout << "Size : " << size << endl; // convert to ascii codes int ascii[size]; for(int i = 0; i < size; ++i){ ascii[i] = int(original[i]); } // out string out_file; if(argc < 3) { out_file = "a.bf"; }else{ out_file = argv[2]; } ofstream ofs(out_file); if(ofs.fail()){ throw invalid_argument("Failed to open output file."); } for(auto a : ascii){ auto code = encode(a); for(auto c : code) ofs << c; } ofs << '\n'; ofs.close(); cout << "Done!" << endl; }catch (exception& e){ cerr << e.what() << endl; return -1; } return 0; } |
動作確認
早速動かしてみましょう.
今回は命令を以下のようにセットします.
1 2 3 4 5 6 7 8 | #define PTR_INCREMENT 'a' #define PTR_DECREMENT 'b' #define BYTE_INCREMENT 'c' #define BYTE_DECREMENT 'd' #define OUTPUT 'e' #define INPUT 'f' #define LOOP_START 'g' #define LOOP_END 'h' |
オリジナルテキストは,
1 | How are you? |
です.
コンパイルして変換してみましょう!
1 2 3 4 5 6 7 8 9 | $ cd [PROJECT_ROOT] $ mkdir build $ cd build $ cmake .. $ make $ ./enc original.txt Original : How are you? Size : 12 Done! |
変換し終わったものを見てみましょう!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | a��c�c*c�c�c�c�cCgdaM�Uc��c�Gcc�Tc/c-cX�ck�c�c�b�hacHc3ea Ac�c c#c�c_c0c�c�c�c c gdaK5c9�c��c�c��cZ c*�c�Hc��c�c�bhac�ea�cMc9c�c�c�c�cc�c�c�c�gda���c�[c�lc�Qc� c� c�c�mc �c4�cbhac�c6cPc�c�c�c�c�c:eac�c,cDgdaUz�c�Pcɲcc��c�LcO�c��c �c�c�b hacc�eax�c�cc4c cc�c^cGc�gdaX)�c[�c� cF�c��c.6c*csc �c�c�bShac c�ccScOcQc�ea �c�c�c�c�c�cc�c�c�c�cgda�O�c|�c7�c��c��c3�c:�c�c!�c��c�b�hac�c/cc�ea��cmcHc�ccHcc-cOc�cqgdaq�_c��cӞc��c�c��c�c��c��cs~cb'hac�ea�9cWcc gdaA�5c�c�<c��c��cc �c� c��cY2cbhac c9ea cc�c�cqc cc�c6c.c�c�c�gda��jc��c%�c�c��c�&cCcݯc�:c��cb<hacmea�c8c�cc�c`c�c{c�crc�c�gda�`cދc�Yc�c2c �c�Ac�scv�c��c�b�hac�ea��cycc�c�c�cc^c�cpcLc�gda�qc�c kc �c�c��c�>c|Gc�c�Nc b#hac c�cc�c�c�c�ea��c�c2cc>cc8gda Tc�c�c��ctWc��c��c�jcA�cX1cbhac�c�cHe |
なんということでしょう.
ひと昔のウェブサイトを覗いたら文字化けしてた,という時の感情がこみ上げてきます.
え...
これ,本当に戻せるのかしら...
1 2 | $ ./bf a.bf How are you? |
うおおおおおおお!!!
少し感動しました.
ちょっとそれっぽくできたので,私は嬉しいです.
これ意外と,解読難しくない?
うーん,でも8つの命令しかないから,総当たりで負けるかな.
今,命令は1文字ですけど,これが複数文字の組み合わせになれば,結構難しい暗号になるかもしれませんね...
(分からんけど)
さいごに
はい,今回はBrainfuckで暗号文を作ってみようという企画でした!
意外とイイ感じな気がします!
ちなみにコードはGithubに全てあるので遊んでみてください.
HiroshiARAKI/esolang_interpreter: Let’s make your Esolang!
もちろん,Brainfuckのコードの生成できますよ!