at posts/single.html

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

関連する日記