XORによる簡単な暗号
1. プログラムの概要
crypt - ファイルに簡単な暗号をかける
1.1. 使い方
$ crypt file1 file2 key
file1は、入力ファイル。 file2は、出力ファイル。 keyには、「鍵」となる適当なテキストを指定する。復号するときに必要になるので覚えておかないといけない。 復号するには、暗号化するときに使用したkeyで、再度cryptにかければ良い。
$ crypt file2 file3 key
file2は、入力ファイルで、暗号化済。 file3は、出力ファイルで、復号された内容が書き込まれる。 keyには、暗号化するときに使用したものと同じ数値を指定しなければ元の内容にならない。
1.2. 注意
あまりに簡単な方法なので、実用性は低い。 気休め程度にはなるかもしれない。
2. C
テキストの最初のプログラムです。 掲載されているコードほぼそのままです。
オリジナルのソースは次のサイトから入手できます。
/* * 出典元「C言語による最新アルゴリズム事典」奥村晴彦著 - P3 * インデントは変更してあります。 * 他にも、メッセージや変数名や処理を変更している場合があります。 */ #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int c, r; FILE *infile, *outfile; if (argc < 3 || argc > 4) { fprintf(stderr, "usage: crypt infile outfile [key\n]"); return EXIT_FAILURE; } if ((infile = fopen(argv[1], "rb")) == NULL) { fprintf(stderr, "cannot open file %s\n", argv[1]); return EXIT_FAILURE; } if ((outfile = fopen(argv[2], "wb")) == NULL) { fprintf(stderr, "cannot open file %s\n", argv[2]); return EXIT_FAILURE; } if (argc == 4) { srand(atoi(argv[3])); } while ((c = getc(infile)) != EOF) { do { r = rand() / ((RAND_MAX + 1u) / 256); } while (r >= 256); putc(c ^ r, outfile); } return 0; }
プログラムの前半部分は、コマンドライン引数のチェック、ファイルのオープンとそのチェックをしているだけです。
暗号化に直接関連しているのは putc(c ^ r, outfile)
のところです。
c
がファイルから読み込んだ1バイトで r
が暗号に使う値です。
ここで r
は、コマンドライン引数で受け取った鍵を使うのではなく、乱数で生成しているところがポイントです。
鍵は乱数のシードとして使用しています。
復号するときは、暗号化のときと同じ鍵をコマンドライン引数に渡して、それをシードとして与えてやることで、暗号化のときと同じ r
が対応するバイトに適用されます。
XORは、2回適用すると元に戻るという性質があるので、それを利用して暗号化されたものを復号化することができます。
r
を生成している do-while ところはちょっと分かりづらいかもしれません。
ここは0から255の整数をしています。
単に rand() % 256
のようにしてしまうと、RANDMAXの範囲によっては、偏りができてしまうため、このような処理が必要になります。
3. Emacs Lisp
(defun string-from-file (file-path) (with-temp-buffer (insert-file-contents file-path) (buffer-string))) (defun crypt-char (c) (logxor c (random 256))) (defun crypt-string (s) (mapcar 'crypt-char s)) (defun crypt-file (file) (crypt-string (string-from-file file))) (defun write-crypted (infile outfile key) (with-temp-file outfile (if (not (stringp key)) (error key)) (random key) ;;(mapc 'insert (mapcar 'char-to-string (crypt-file infile))))) (mapc 'insert (crypt-file infile)))) ;; test (write-crypted "crypt.el" "crypt.el-output" "hello") (write-crypted "crypt.el-output" "crypt.el-output-output" "hello") ;decrypt ;; こんにちは、世界 ;test multibyte characters
かなり細かく関数に分割してあります。 ここまで分ける必要はなかったかもしれません。
ファイルのオープンに対するエラーチェックを省いているため、Cのものよりも短くなっています。 write-crypted関数で鍵が文字列かどうかチェックしているのは、randomの引数を文字列とすることで、シードと扱われるためです。 Cのバージョンでは、コマンドライン引数で整数を指定する必要があるようになっていました。 こちらは任意の文字列を鍵として使用できるため、むしろ便利になっているのではないかと思います。
参考:
4. Bash
bashでもやってみました。 かなり苦戦しました。
#!/bin/bash if [ $# -ne 3 ] then echo "usage: crypt INFILE OUTFILE KEY" exit 1 fi function rand { # return $(( $RANDOM % 256 )) # biased # assumes max $RANDOM is 32767 return $(( $RANDOM / ((32767 + 1) / 256) )) } infile=$1 outfile=$2 RANDOM=$3 # seed with key # if pipe to while, RANDOM seed not works as expected bytes=$(hexdump -v -e '/1 "%u\n"' $infile) while read c do rand r=$? crypted=$( printf %x $(( $c ^ $r )) ) printf "%b" "\\x$crypted" done <<<$bytes >$outfile
ポイント
- XOR演算は可能か
- シードを与えて、乱数を生成するにはどうすればよいか
- ファイルから1バイト読み込むにはどうすればよいか
- ファイルから1バイトずつ読み込みながらファイルの終りまでループするにはどうすればよいか
- ファイルに1バイト書き込むにはどうすればよいか
奇妙なところは bytes=$(hexdump -v -e '/1 "%u\n"' $infile)
のところでしょう。
これは、infileの内容をバイナリデータとして、改行区切で10進数の値にして、bytesという変数に割り当てています。
本来なら、whileのところでリダイレクト、すなわち hexdump -v -e '/1 "%u\n"' $infile | whilte read c
としたいところでした。
ところが、このようにリダイレクトすると、whileの本体では、RANDOMのシードが無視されてしまうという事態になってしまいました。
回避策として、リダイレクトではなく、here string (<<<)という機能を利用するために一旦変数に割り当てています。
参考:
- Advanced Bash-Scripting Guide: 9.3. $RANDOM: generate random integer
- 疑似乱数
- Bash Reference Manual: 3.6 Redirections
- Here String (<<<)
- How to write binary data in Bash (Stack Overflow)
- バイナリデータとしてファイルに出力する
- How to use bash script to read binary file content? (StackExchange)
- バイナリデータとしてファイルを読み込む
- How Hexdump works
- hexdump のフォーマットオプション
- How to perform a for loop on each character in a string in Bash? (Stack Overflow)
- read
- How To Use The Bash read Command
- read
- Bash Functions
- 関数
- Bash Bitwise Operators
- ビット演算
5. Fish
fishはbashに比べてずいぶん簡単でした。
#!/usr/bin/fish if test (count $argv) -ne 3 echo "usage: crypt INFILE OUTFILE KEY" exit 1 end set infile $argv[1] set outfile $argv[2] set key $argv[3] random $key # seed hexdump -v -e '/1 "%u\n"' $infile | while read c set r (random 0 255) set crypted (printf "%x" (math bitxor $c, $r)) printf "%b" "\\x$crypted" end >$outfile
疑似乱数は、randomという組み込み関数が利用できます。 bashのときのような、randomのシードが無視される問題はありませんでした。 そのため、ストレートにhexdumpからwhite readにリダイレクトすることができました。