#Railsの可逆暗号化
はじめに
今回はセキュアなテーマです。ハッシュ化と可逆暗号化などがありますが、今回は可逆暗号化です。ハッシュ化はRailsチュートリアルで少し出てきたので割愛します。
##可逆暗号とハッシュ化
確かRailsチュートリアルでも出てきたと思います。パスワードをセキュアなものにしてましたね。また、ハッシュ化は暗号化ではない、との記事があったのですが、読めば納得。暗号化とは当事者間だけでわかるように情報を秘匿することで、ハッシュ化は誰も元に戻せず元の情報がわからなくなることです。しっかり、違いを認識しましょう。
可逆暗号
復号可能なデータの変換のこと。個人情報など、DBに保存するときは見られたくないが、復号が必要なものに用いる。
ハッシュ化
復号不可能なデータの変換のこと。パスワードなど、誰にも見られてはならないものに使う。認証時はどうするかというと、送られてきたデータを同じくハッシュ化して、DBにあるハッシュ化されたパスワードと比較する。(同じ値をハッシュ化したら、同じ値が得られる。しかし、ハッシュ化した値から元の値を復号するのは不可能。)
今回は可逆暗号化について説明します。
##Railsで可逆暗号
実際にRailsコンソールでやってみましょう。
irb(main):006:0> message = 'Hello!'
=> "Hello!"
irb(main):007:0> secret = SecureRandom::hex(128)
=> "0ed29b6ac428d1f8c4826ad0c586f1dfb79902d5d6b3a9e1a8db923d2bbbcd2a123475758196dc6424f1c2bcf9475912b27902d809cc9ac5b89e5ec7f5942d8979f813ba8a12e3e19303e9a4c918985c93e2eb381b0f0be91d1e8c5aa099d76e55e3c6901b625eaedcd9f2e6da1b4a0cade784f1fb8f2b385b100743f5d7a829"
irb(main):008:0> encryptor = ::ActiveSupport::MessageEncryptor.new(secret, cipher: 'aes-256-cbc')
=> #<ActiveSupport::MessageEncryptor:0x007fc2031f2f30 @secret="0ed29b6ac428d1f8c4826ad0c586f1dfb79902d5d6b3a9e1a8db923d2bbbcd2a123475758196dc6424f1c2bcf9475912b27902d809cc9ac5b89e5ec7f5942d8979f813ba8a12e3e19303e9a4c918985c93e2eb381b0f0be91d1e8c5aa099d76e55e3c6901b625eaedcd9f2e6da1b4a0cade784f1fb8f2b385b100743f5d7a829", @sign_secret=nil, @cipher="aes-256-cbc", @aead_mode=false, @digest="SHA1", @verifier=#<ActiveSupport::MessageVerifier:0x007fc2031f2b20 @secret="0ed29b6ac428d1f8c4826ad0c586f1dfb79902d5d6b3a9e1a8db923d2bbbcd2a123475758196dc6424f1c2bcf9475912b27902d809cc9ac5b89e5ec7f5942d8979f813ba8a12e3e19303e9a4c918985c93e2eb381b0f0be91d1e8c5aa099d76e55e3c6901b625eaedcd9f2e6da1b4a0cade784f1fb8f2b385b100743f5d7a829", @digest="SHA1", @serializer=ActiveSupport::MessageEncryptor::NullSerializer>, @serializer=Marshal>
irb(main):009:0> encrypt_message = encryptor.encrypt_and_sign(message)
=> "L3FIRGdPam0vTW1ZZWxWaVRFdXRMRUN4QVphWEN3M2hxaHhYUFFMMmZkVT0tLXQvaXFVekQ5MGgxOEc1b2txaGM4MWc9PQ==--81e36973a11ee8148384d55be4f901a0c2ce7b43"
irb(main):010:0> encryptor.decrypt_and_verify(encrypt_message)
=> "Hello!"
irb(main):011:0>
流れは上記の通りです。解説してきます。
まず暗号化したい情報はmessage
のHello!
です。
secret = SecureRandom::hex(128)
上記の箇所では乱数を生成しています。
encryptor = ::ActiveSupport::MessageEncryptor.new(secret, cipher: 'aes-256-cbc')`
chipherとは暗号の意味で、ここではaes-256-cbc
を採用しています。(暗号化のアルゴリズム等は話がそれるので割愛。)鍵として先ほどの乱数を用います。
ちなみに右辺の::ActiveSupport
の部分、これはトップレベルでのActiveSupport
を呼び出しています。詳しくは参考記事見てください。ちょっとした小技らしいです。
encrypt_message = encryptor.encrypt_and_sign(message)
先ほどのオブジェクトにmessage
を渡して暗号化します。
encryptor.decrypt_and_verify(encrypt_message)
これで復号。
##試してみたこと
AESは共通鍵暗号方式って書いてあったからsecret
さえ覚えておけば誰でも復号可能。逆に、secret
を忘れちゃうと、戻せない。てな訳でsecret
を一回上書きして違う値を入れて見た。
irb(main):018:0> secret = SecureRandom::hex(128)
=> "dbdc9c44db27e70fdcb7a15ac4e794111e20b38b9569abe08f79c336d241871d20f4d327a0a413ac50f80953df11a5f67b3e2137bfc4386744611f35e3f14d56dc388aa4afef5113f4c5177eb546f7b106a6920db6d85318d5d02602c82b61d2d5735dcfd1306c0aa3b0e98f1ebf2dcd8f07744db86f35e86ebe4f99c541e48c"
irb(main):019:0> encryptor = ::ActiveSupport::MessageEncryptor.new(secret, cipher: 'aes-256-cbc')
=> #<ActiveSupport::MessageEncryptor:0x007fc2050925f8 @secret="dbdc9c44db27e70fdcb7a15ac4e794111e20b38b9569abe08f79c336d241871d20f4d327a0a413ac50f80953df11a5f67b3e2137bfc4386744611f35e3f14d56dc388aa4afef5113f4c5177eb546f7b106a6920db6d85318d5d02602c82b61d2d5735dcfd1306c0aa3b0e98f1ebf2dcd8f07744db86f35e86ebe4f99c541e48c", @sign_secret=nil, @cipher="aes-256-cbc", @aead_mode=false, @digest="SHA1", @verifier=#<ActiveSupport::MessageVerifier:0x007fc205092468 @secret="dbdc9c44db27e70fdcb7a15ac4e794111e20b38b9569abe08f79c336d241871d20f4d327a0a413ac50f80953df11a5f67b3e2137bfc4386744611f35e3f14d56dc388aa4afef5113f4c5177eb546f7b106a6920db6d85318d5d02602c82b61d2d5735dcfd1306c0aa3b0e98f1ebf2dcd8f07744db86f35e86ebe4f99c541e48c", @digest="SHA1", @serializer=ActiveSupport::MessageEncryptor::NullSerializer>, @serializer=Marshal>
irb(main):020:0> encrypt_message = encryptor.encrypt_and_sign(message)
=> "VmVRRklGT3Z3cW9iclFGV2tZeGYvalU0VUJ6SkhpMUxBN3pjSlF0VTExZz0tLU92TzhVcDc5Yk1UWVJLb1hOZktZQ3c9PQ==--d2900b7e0754a3a6b3d7b95039f7ef47d6d274bc"
irb(main):021:0> encryptor.decrypt_and_verify(encrypt_message)
=> "Hello!"
先ほど同様、暗号化した。ここから鍵を忘れてみる。
irb(main):022:0> secret = "0ed29b6ac428d1f8c4826ad0c586f1dfb79902d5d6b3a9e1a8db923d2bbbcd2a123475758196dc6424f1c2bcf9475912b27902d809cc9ac5b89e5ec7f5942d8979f813ba8a12e3e19303e9a4c918985c93e2eb381b0f0be91d1e8c5aa099d76e55e3c6901b625eaedcd9f2e6da1b4a0cade784f1fb8f2b385b100743f5d7a829"
=> "0ed29b6ac428d1f8c4826ad0c586f1dfb79902d5d6b3a9e1a8db923d2bbbcd2a123475758196dc6424f1c2bcf9475912b27902d809cc9ac5b89e5ec7f5942d8979f813ba8a12e3e19303e9a4c918985c93e2eb381b0f0be91d1e8c5aa099d76e55e3c6901b625eaedcd9f2e6da1b4a0cade784f1fb8f2b385b100743f5d7a829"
irb(main):023:0> encryptor = ::ActiveSupport::MessageEncryptor.new(secret, cipher: 'aes-256-cbc')
=> #<ActiveSupport::MessageEncryptor:0x007fc2017c08d8 @secret="0ed29b6ac428d1f8c4826ad0c586f1dfb79902d5d6b3a9e1a8db923d2bbbcd2a123475758196dc6424f1c2bcf9475912b27902d809cc9ac5b89e5ec7f5942d8979f813ba8a12e3e19303e9a4c918985c93e2eb381b0f0be91d1e8c5aa099d76e55e3c6901b625eaedcd9f2e6da1b4a0cade784f1fb8f2b385b100743f5d7a829", @sign_secret=nil, @cipher="aes-256-cbc", @aead_mode=false, @digest="SHA1", @verifier=#<ActiveSupport::MessageVerifier:0x007fc2017c0720 @secret="0ed29b6ac428d1f8c4826ad0c586f1dfb79902d5d6b3a9e1a8db923d2bbbcd2a123475758196dc6424f1c2bcf9475912b27902d809cc9ac5b89e5ec7f5942d8979f813ba8a12e3e19303e9a4c918985c93e2eb381b0f0be91d1e8c5aa099d76e55e3c6901b625eaedcd9f2e6da1b4a0cade784f1fb8f2b385b100743f5d7a829", @digest="SHA1", @serializer=ActiveSupport::MessageEncryptor::NullSerializer>, @serializer=Marshal>
irb(main):024:0> encryptor.decrypt_and_verify(encrypt_message)
ActiveSupport::MessageVerifier::InvalidSignature: ActiveSupport::MessageVerifier::InvalidSignature
from (irb):24
案の定復号できない。
じゃあ鍵を思い出して見る。
irb(main):027:0> secret = "dbdc9c44db27e70fdcb7a15ac4e794111e20b38b9569abe08f79c336d241871d20f4d327a0a413ac50f80953df11a5f67b3e2137bfc4386744611f35e3f14d56dc388aa4afef5113f4c5177eb546f7b106a6920db6d85318d5d02602c82b61d2d5735dcfd1306c0aa3b0e98f1ebf2dcd8f07744db86f35e86ebe4f99c541e48c"
irb(main):028:0> encryptor = ::ActiveSupport::MessageEncryptor.new(secret, cipher: 'aes-256-cbc')
=> #<ActiveSupport::MessageEncryptor:0x007fc202534d20 @secret="dbdc9c44db27e70fdcb7a15ac4e794111e20b38b9569abe08f79c336d241871d20f4d327a0a413ac50f80953df11a5f67b3e2137bfc4386744611f35e3f14d56dc388aa4afef5113f4c5177eb546f7b106a6920db6d85318d5d02602c82b61d2d5735dcfd1306c0aa3b0e98f1ebf2dcd8f07744db86f35e86ebe4f99c541e48c", @sign_secret=nil, @cipher="aes-256-cbc", @aead_mode=false, @digest="SHA1", @verifier=#<ActiveSupport::MessageVerifier:0x007fc202534a50 @secret="dbdc9c44db27e70fdcb7a15ac4e794111e20b38b9569abe08f79c336d241871d20f4d327a0a413ac50f80953df11a5f67b3e2137bfc4386744611f35e3f14d56dc388aa4afef5113f4c5177eb546f7b106a6920db6d85318d5d02602c82b61d2d5735dcfd1306c0aa3b0e98f1ebf2dcd8f07744db86f35e86ebe4f99c541e48c", @digest="SHA1", @serializer=ActiveSupport::MessageEncryptor::NullSerializer>, @serializer=Marshal>
irb(main):029:0> encryptor.decrypt_and_verify(encrypt_message)
=> "Hello!"
(鍵はさっきのコピペ)これで復号できました。
##参考にしたの
Railsで簡単可逆暗号(ActiveSupport::MessageEncryptor)
https://qiita.com/kengos@github/items/e8ea8f71c47852fde48b
[小ネタ] トップレベルの定数(クラス/モジュール)の取得
https://doruby.jp/users/everyday_is_everyday/entries/1