[Rails][Ruby]Blowfishで暗号化してデータをDBに保存する[OpenSSL]

[Rails][Ruby]Blowfishで暗号化してデータをDBに保存する[OpenSSL]


Blowfishで暗号化する記事は沢山あったのだが、
肝心の暗号化後のデータの保存の仕方がなかった。
色々試してみたら、やっとデータを保存することが出来たのでメモ。


問題点 : Blowfishでの暗号化後のデータが保存できない


OpenSSLを利用して暗号化して復号するUnitテストを書いた。
このレベルだと暗号化 => 復号といった処理は問題なかった。
いざ利用してみると、Invalid Valueと出てデータの登録が出来なかった。


とりあえずirbで暗号化してみる。


Unitテスト書いてテストしていってもいいのだが、まずはirbでためしてみた。


以下の条件で暗号化する。



  • 暗号化する文字列 ... [namakesugi.com]

  • 暗号化に用いるパスワード(秘密鍵) ... [private_key_sample]

  • 暗号化時に利用するSaltキー ... [12345678]


以下のように暗号化を実行した。require 'openssl'が通らない場合は適当に調べて
通るようにしてください。



C:\>irb
irb(main):001:0> require 'openssl'
=> true
irb(main):002:0> plain_text = "namakesugi.com"
=> "namakesugi.com"
irb(main):003:0> key = "private_key_sample"
=> "private_key_sample"
irb(main):004:0> salt = "12345678"
=> "12345678"
irb(main):005:0> cipher = OpenSSL::Cipher::Cipher.new("BF-CBC")
=> #
irb(main):006:0> cipher.pkcs5_keyivgen(key.to_s, salt.to_s, 1)
=> nil
irb(main):007:0> cipher.encrypt()
=> #
irb(main):008:0> encrypt_text = cipher.update(plain_text.to_s) + cipher.final
=> "\247\236\301\016\231\237\2149\262f\333\002)\027.\b"
irb(main):009:0> p encrypt_text
"\247\236\301\016\231\237\2149\262f\333\002)\027.\b"
=> nil
irb(main):010:0> p encrypt_text.class.to_s
"String"
=> nil
irb(main):011:0>

namakesugi.comという文字列を暗号化すると以下のようになったことがわかる。
\247\236\301\016\231\237\2149\262f\333\002)\027.\b


上記をそのまま復号すると以下のように復号できることがわかる



irb(main):011:0> cipher = OpenSSL::Cipher::Cipher.new("BF-CBC")
=> #
irb(main):012:0> cipher.pkcs5_keyivgen(key.to_s, salt.to_s, 1)
=> nil
irb(main):013:0> decrypt_text =
irb(main):014:0* cipher.update(encrypt_text) + cipher.final
=> "namakesugi.com"
irb(main):015:0> p decrypt_text
"namakesugi.com"
=> nil
irb(main):016:0>

ここまでは何にも問題がない。
しかし、Rails上で上記を実行すると、\247\236...の部分を無理やりUTF8に変換しようとしているらしく、
Invalid Valueが出てしまう。


Invalid Valueエラーの回避策


いくつか方法を考えたのだが、手っ取り早いのが文字を16進数の文字列に変換して格納すること。


RubyにはArray#pack、String#unpackというメソッドが用意されているのこれを利用することにする。


Array#packとString#unpackを利用して暗号化データを保存する


以下のようにすることによって保存することができるようになりました。



#
#= OpenSSL を利用して暗号化・復号を実行するクラスライブラリ
#
#Authors:: Namakesugi
#Version:: 1.0 2010/09/19
#Copyright:: Copyright (C) Namakesugi, 2009-2010. All rights reserved.
#License:: Ruby ライセンスに準拠
class CryptUtil
require 'openssl'
# 暗号化方式としてのAES-256-CBCを定義します
AES_256_CBC = 0
# 暗号化方式としてのBlowfish-CBCを定義します
BF_CBC = 1

#== ランダムパスワードを生成します
# _return_size_を指定することで、任意の桁数のパスワードを生成できます
# _return_size_がFixnumオブジェクトでない場合はnilを返します
# _return_size_が0以下の場合はnilを返します
#_return_size_ ... 生成するパスワードの桁数
def self.create_password(return_size = 8)
# return_sizeの型がFixnumで無い or return_sizeが0以下ならばnilを返す
return nil if return_size.class.to_s != "Fixnum" or return_size <= 0
# OpennSSLを利用してランダム値を生成する
return OpenSSL::Random.random_bytes(return_size)
end

#== 文字列を暗号化します。
# 暗号化された16進数変換された文字列を返します
#_plain_text_ ... 暗号化する対象の文字列
#_key_ ... 暗号化するための秘密鍵
#_salt_ ... 暗号化するためのサルトキー(8bit)
#_type_ ... 暗号化方式
def self.encrypt(plain_text, key, salt, type = CryptUtil::BF_CBC)
result = ""
case type
when CryptUtil::BF_CBC
result = CryptUtil.encrypt_blowfish(plain_text, key, salt)
when CryptUtil::AES_256_CBC
result = CryptUtil.encrypt_aes256(plain_text, key, salt)
end

raise "Invalid parameter" if result == nil
# unpackでH*形式に変換します
return result.unpack("H*").join
end

#== 文字列を複号します。
# 16進数変換された暗号化された文字列を渡すことにより、復号された文字列を返します。
# 正常に復号できない場合はraiseされます
#_encrypt_text_ ... 暗号化する対象の文字列(16進数文字列である必要があります)
#_key_ ... 復号するための秘密鍵
#_salt_ ... 復号するためのサルトキー(8bit)
#_type_ ... 復号方式
def self.decrypt(encrypt_text, key, salt, type = CryptUtil::BF_CBC)
# Array#packで16進数文字列を再度パック化します。
convert_text = [encrypt_text].pack("H*")
result = ""
case type
when CryptUtil::BF_CBC
result = CryptUtil.decrypt_blowfish(convert_text, key, salt)
when CryptUtil::AES_256_CBC
result = CryptUtil.decrypt_aes256(convert_text, key, salt)
end

raise "Invalid parameter" if result == nil
return result
end

#== AES256で文字列を暗号化します
# 暗号化にはAES-256-CBCを用います
# saltは8byteである必要があります
def self.encrypt_aes256(plain_text, key, salt)
begin
cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
cipher.encrypt
cipher.pkcs5_keyivgen(key.to_s, salt.to_s)
return cipher.update(plain_text.to_s) + cipher.final
rescue
return nil
end
end

#== AES-256-CBCで暗号化された文字列を復号します
# 復号にはAES-256-CBCを用います。
# saltは8byteである必要があります
def self.decrypt_aes256(encrypt_text, key, salt)
begin
cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
cipher.decrypt
cipher.pkcs5_keyivgen(key.to_s, salt.to_s)
return cipher.update(encrypt_text.to_s) + cipher.final
rescue
return nil
end
end

#== Blowfishで文字列を暗号化します
# 暗号化にはBF-CBCを用います。
# saltは8byteである必要があります
def self.encrypt_blowfish(plain_text, key, salt)
begin
cipher = OpenSSL::Cipher::Cipher.new("BF-CBC")
cipher.pkcs5_keyivgen(key.to_s, salt.to_s, 1)
cipher.encrypt()
return cipher.update(plain_text.to_s) + cipher.final
rescue
return nil
end
end

#== Blowfish暗号で暗号化されたデータを復号します
# 復号にはBF-CBCを用います。
# saltは8byteである必要があります
def self.decrypt_blowfish(encrypt_text, key, salt)
begin
cipher = OpenSSL::Cipher::Cipher.new("BF-CBC")
cipher.pkcs5_keyivgen(key.to_s, salt.to_s, 1)
cipher.decrypt()
return cipher.update(encrypt_text.to_s) + cipher.final
rescue
return nil
end
end
end

CryptUtilの使い方


上記をcrypt_util.rbという名前でRAILSのlib以下に保存します。


暗号化したい場合



plain_text = "namakesugi.com"
key = CryptUtil.create_password
salt = CryptUtil.create_password
encrypt_text = CryptUtil.encrypt(plain_text, key, salt)

encrypt_textを任意のオブジェクトのString(MySQLならばVARCHAR)のメンバーにセットしてsaveを
実行すれば正常に保存できます。
なお、keyとsaltに関してはASCII文字ならばなんでもいいです。
ASCII文字にならないのであればとかでDigest::SHA256.hexdigest(data.to_s + salt.to_s)とかでハッシュ化しましょう。
なお、上記例だと適当にパスワードを作っています。


復号したい場合


encrypt_text, key, saltは既に設定されているものとします



decrypt_text = CryptUtil.decrypt(encrypt_text, key, salt)

上記部分を暗号化したいオブジェクト(app/models以下のクラス)に適当にメソッド化して登録したり
before_saveなどに登録するなどして、自動的に暗号化・復号が実行されるようにすればいいと思います。
なお、AES256がよければ、第4引数にCryptUtil::AES_256_CBCと指定すれば、AES256暗号になります。


OpenSSLに関して


多分これは色々記事がありますので、探していただければいいかと思います。
pkcs5_keyivgenで色々いじれるみたいなので、それを参考にするといいかと思います。
なお、暗号化回数は1でハードコードしていますので、変更したければpkcs5_keyivgenの第3引数を
変更してください。


大分冗長だけど、まぁいいか...

関連記事

コメントの投稿

非公開コメント

RSS
記事検索

全記事を表示する

最新記事
カテゴリ
買ったモノ
楽天ランキング