irb で覚える共通鍵暗号 (AES)
いよいよ暗号化に挑戦。 今日は暗号と復号で同じ鍵を使う、共通鍵暗号を試してみる。
OpenSSL で使える共通鍵暗号アルゴリズムを調べる。
$ openssl enc - Cipher Types -aes-128-cbc -aes-128-cfb -aes-128-cfb1 -aes-128-cfb8 -aes-128-ecb -aes-128-ofb -aes-192-cbc -aes-192-cfb -aes-192-cfb1 -aes-192-cfb8 -aes-192-ecb -aes-192-ofb -aes-256-cbc -aes-256-cfb -aes-256-cfb1 -aes-256-cfb8 -aes-256-ecb -aes-256-ofb -aes128 -aes192 -aes256 -bf -bf-cbc -bf-cfb -bf-ecb -bf-ofb -blowfish -cast -cast-cbc -cast5-cbc -cast5-cfb -cast5-ecb -cast5-ofb -des -des-cbc -des-cfb -des-cfb1 -des-cfb8 -des-ecb -des-ede -des-ede-cbc -des-ede-cfb -des-ede-ofb -des-ede3 -des-ede3-cbc -des-ede3-cfb -des-ede3-ofb -des-ofb -des3 -desx -desx-cbc -rc2 -rc2-40-cbc -rc2-64-cbc -rc2-cbc -rc2-cfb -rc2-ecb -rc2-ofb -rc4 -rc4-40
aes や des や rc4 が暗号化アルゴリズム (AES, DES, RC4) で、ハイフンの後ろの 256-cbc などが鍵の長さや暗号処理方式 (EDE, CBC, OFBなど) を表す。 とりあえず迷ったら、アルゴリズムは AES で、処理方式は CBC を選んでおけばいいみたい。
irb で挑戦
インスタンスの生成方法は、以下のとおり。 さっきアルゴリズム一覧を大文字にしたものが、クラス名や引数となっている。 クラス名がアルゴリズムで、引数が鍵の長さや処理方式。
irb(main):001:0> require 'openssl' => true irb(main):002:0> encoder = OpenSSL::Cipher::AES.new("256-CBC") => #<OpenSSL::Cipher::AES:0x4030f2f0>
Cipher クラスにアルゴリズム名を含めて渡してもいい。
irb(main):004:0> encoder = OpenSSL::Cipher::Cipher.new("AES-256-CBC") => #<OpenSSL::Cipher::Cipher:0x402fb098>
暗号化と復号のどちらをやるかを決める。 暗号化する場合は、encryptメソッドを呼ぶ。
irb(main):005:0> encoder.encrypt => #<OpenSSL::Cipher::Cipher:0x402fb098>
復号の場合は、decryptメソッド。
irb(main):006:0> encoder.decrypt => #<OpenSSL::Cipher::Cipher:0x402fb098>
次に、暗号・復号に使う鍵を生成する。 今回は文字列(パスワード)として渡す。 いくら256ビットの鍵を使っていても、ここのパスワードが短ければ意味が無い。
irb(main):008:0> encoder.pkcs5_keyivgen("password") => nil
平文 (plain) と暗号文 (crypt) の変数を宣言。
irb(main):009:0> plain = 'test text' => "test text" irb(main):011:0> crypt = '' => ""
update メソッドを使って暗号化する。 ハッシュ関数や HMAC と使い方は似ている。
irb(main):015:0> crypt << encoder.update(plain) => "" irb(main):016:0> crypt => ""
でもハッシュ関数と違って、入力サイズが規定値に満たない場合は暗号文が返ってこない。 そこで、もう暗号化は終わりだよ、ということを伝えるために final メソッドを呼ぶ。
irb(main):017:0> crypt << encoder.final => "\v\035\262~\312\372\317H\212:\257\002H?7\256"
暗号文が返ってきた。
encrypt.rb, decrypt.rb
パスワードを使ってファイルを暗号化するプログラム。 暗号文をHEXエンコーディングした結果を返す。 (本当はBase64のほうがいいかも)
$ cat encrypt.rb #!/usr/bin/env ruby require 'openssl' key = ARGV.shift aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC") aes.encrypt aes.pkcs5_keyivgen(key) value = "" ARGF.each do |line| value << aes.update(line) end puts value.unpack("H*")[0]
自分自身をパスワード password で暗号化する。
$ ruby encrypt.rb password encrypt.rb > crypt.txt $ cat crypt.txt 87fd67df007356b32879460327a(略)
次は復号するプログラム。 やり方はほぼ同じなので省略。
#!/usr/bin/env ruby require 'openssl' key = ARGV.shift aes = OpenSSL::Cipher::AES.new("256-CBC") aes.decrypt aes.pkcs5_keyivgen(key) value = "" ARGF.each do |line| value << aes.update([line].pack("H*")) end puts value
さっきの暗号文を元に戻す。
$ ruby decrypt.rb password crypt.txt #!/usr/bin/env ruby require 'openssl' key = ARGV.shift aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC") aes.encrypt aes.pkcs5_keyivgen(key) value = "" ARGF.each do |line| value << aes.update(line) end puts value.unpack("H
なぜか、最後の行が切れてる…。 一度にデータを渡しすぎなのかな?
追記
あぁ、分かった。 encrypt.rb のループの最後で
value << aes.final
を呼ばないとダメだ。 こんな感じ。
value = "" ARGF.each do |line| value << aes.update(line) end value << aes.final