irb で覚える共通鍵暗号 (AES)
2006-07-27
いよいよ暗号化に挑戦。 今日は暗号と復号で同じ鍵を使う、共通鍵暗号を試してみる。
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