Rustの練習として、今流行の古典暗号であるアフィン暗号、スキュタレー暗号、シーザー暗号をRustで実装しました。
記事はまだ無い。(後で書きます)
シーザー暗号について書く予定。本当は暗号を使ったファンタジーが書きたい。
(タイトルだけ決まったw:アルル・カトレイと守りの数秘術)
自分でもどうしてRustを使ってみようと思ったのかよく分からない。
シーザー暗号以外はまだ中途半端な実装なのでそれを終わらせる必要があるかも知れないが、
暗号化関数以外のIO部分は共通しているのであまり面白くない。
思い出した。
リードソロモン符号で何かしようとしたのだ。
でも、なぜRust?
シーザー暗号については私が解説を書くまでもなく、良記事があるのでそちらをご覧ください。
Rustはまだ触り始めてから一週間くらいしかたってません。
最初はリードソロモン符号なんかを作るために行列をいじっていました。
なぜRustを選んだかと言えば、メモリーセーフでどんなに下手でもそれなりに安全なプログラムが作れるらしいと思ったからです。
自分としてはCの次に新しく言語を覚えるのならC++よりRustの方が意外に簡単でした。
オブジェクト指向が理解できなかった私にも少しずつ書けるようになりました。
C++で挫折した人にはお勧めです。
メモリ管理がしっかりしているので、いいことがあるかも知れない。
それともどんな言語で書いてもハッキングに対しては安全じゃない元凶があるのだろうか。
恥ずかしいけど、コードをさらします。
下のコードにあるgf,fgという数字は有限体というものですが、解らなければ魔法の数字と思ってください。
シーザー暗号はシフト暗号とも言われ、アルファベットの表の位置を秘密の数だけスライドした文字に置き換えるだけの単純な関数です。
ここではgf.fgをアルファベットとして8ビットサイズの変換表に基づいてスライドさせることを考えます。
アルファベットは26文字ですが、今やっているバージョンではアルファベットが256個存在するのと同じ状況です。
gf,fgは、それぞれ暗号化、復号化に使う変換表に使っています。
追記:20220510
有限体の変換テーブルを暗号化関数と復号化関数のそれぞれに分けました。
追記:20220511
更に成形しました。
大体できあがった感じはしますが、古典暗号は計算機で攻撃できる今となっては暗号として明らかによくない点がいくつかあります。
つまり、
・暗号文のサイズから平文のサイズが解ってしまうこと。
・暗号変換が確率的でないこと。
・安全なパディング処理をしていない。
・暗号文が壊れていないかどうか認証する機能が無いこと、
などです。
これからはそれを意識して、さらなる改良を為ていく予定です。
多分この記事のシリーズになると思います。
まあこれらの機能を全部備えていれば、もはやそれはシーザー暗号とは言えないものになるのですが・・・w
#![allow(non_snake_case)]
use base64::{decode, encode};
use std::str;
fn enc(data: &String, a: usize) -> String {
let gf: [i32; 256] = [
0, 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180,
117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212,
181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111,
222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253,
231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67,
134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199,
147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158,
33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213,
183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255,
227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100,
200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239,
195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235,
203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142,
];
let mut c: [u8; 257] = [0; 257];
let byte: &[u8] = data.as_bytes();
println!("origin: {}", str::from_utf8(data.as_bytes()).unwrap()); //pro
let j = byte.len();
for ii in 0..j {
c[ii] = gf[(((byte[ii]) as usize) + (a)) % 256] as u8;
}
println!("encryptod = {:?}", &c[0..j]);
let encoded = encode(&c[0..j]); //pro
println!("cipher text:");
println!("{}", encoded);
encoded //pro
}
fn dec(encoded: String, a: usize) -> String {
let mut buf: [i32; 257] = [0; 257];
let fg: [i32; 256] = [
0, 1, 2, 26, 3, 51, 27, 199, 4, 224, 52, 239, 28, 105, 200, 76, 5, 101, 225, 15, 53, 142,
240, 130, 29, 194, 106, 249, 201, 9, 77, 114, 6, 139, 102, 48, 226, 37, 16, 34, 54, 148,
143, 219, 241, 19, 131, 70, 30, 182, 195, 126, 107, 40, 250, 186, 202, 155, 10, 121, 78,
229, 115, 167, 7, 192, 140, 99, 103, 222, 49, 254, 227, 153, 38, 180, 17, 146, 35, 137, 55,
209, 149, 207, 144, 151, 220, 190, 242, 211, 20, 93, 132, 57, 71, 65, 31, 67, 183, 164,
196, 73, 127, 111, 108, 59, 41, 85, 251, 134, 187, 62, 203, 95, 156, 160, 11, 22, 122, 44,
79, 213, 230, 173, 116, 244, 168, 88, 8, 113, 193, 248, 141, 129, 100, 14, 104, 75, 223,
238, 50, 198, 255, 25, 228, 166, 154, 120, 39, 185, 181, 125, 18, 69, 147, 218, 36, 33,
138, 47, 56, 64, 210, 92, 150, 189, 208, 206, 145, 136, 152, 179, 221, 253, 191, 98, 243,
87, 212, 172, 21, 43, 94, 159, 133, 61, 58, 84, 72, 110, 66, 163, 32, 46, 68, 217, 184,
124, 165, 119, 197, 24, 74, 237, 128, 13, 112, 247, 109, 162, 60, 83, 42, 158, 86, 171,
252, 97, 135, 178, 188, 205, 63, 91, 204, 90, 96, 177, 157, 170, 161, 82, 12, 246, 23, 236,
123, 118, 45, 216, 80, 175, 214, 234, 231, 232, 174, 233, 117, 215, 245, 235, 169, 81, 89,
176,
];
let decoded = decode(&encoded).unwrap(); //pro
for i in 0..decoded.len() {
buf[i] = (fg[(decoded[i] as usize)]) - (a as i32);
}
let mut w: [u8; 257] = [0; 257];
println!("plain text:");
for i in 0..decoded.len() {
w[i] = (buf[i] % 256) as u8;
}
println!("decrypted = {:?}", &w[0..decoded.len()]);
match String::from_utf8(w.to_vec()) { //pro
Err(_why) => {
println!("復号できませんでした");
"復号失敗".to_string()
}
Ok(str) => str,
} //pro
}
fn main() {
let a = 1010;
let mut data = String::new(); //from("日本語入力");
println!("何か入力を");
std::io::stdin().read_line(&mut data).ok(); //コピペ
data = data.trim_end().to_owned();
println!("{}", data);
let cc = enc(&data, a);
println!(" ");
let l = dec(cc, a);
println!("back to origin: {}", l);
}
かなり綺麗になりましたね。2割くらいはプロに助けてもらいました。
エラーハンドリングの方法などを伝授されました。
残りは所有権とかライフタイム、参照など何も解らずコピペだけで組み立てました。
それらを理解できてやっとRust書きとして認められるのでしょうが、
やりたいことがあるうちは好きなようにやらせてもらいます。
やってることは単純です。
平文を読み込んだら配列に変換して、一文字ずつ変換表で数値化してやり、
秘密のシフト番号で配列の添字番号をずらして1バイト数字として暗号化します。
暗号文の実行結果は1バイト数字になります。
とは言っても暗号化したメッセージはバイナリなのでメールでは送れないので、
base64でテキストに直してます。
いくら文法的に厳しいといえども、Cで書いたときとほとんど変わらないというのが驚きです。
果たしてこれで本当に安全なコードになったと言えるのでしょうか?
このほかにもアフィン暗号や換字暗号なんかも作ったので、見たい人はGitHubの方を見てね。
プログラムの説明はまた後で書きます。(続く)