Railsの可逆暗号化　初心者→中級者へのSTEP11/25

#Railsの可逆暗号化
**はじめに**
今回はセキュアなテーマです。ハッシュ化と可逆暗号化などがありますが、今回は可逆暗号化です。ハッシュ化はRailsチュートリアルで少し出てきたので割愛します。

##可逆暗号とハッシュ化
確か[Railsチュートリアル](https://railstutorial.jp/)でも出てきたと思います。パスワードをセキュアなものにしてましたね。また、ハッシュ化は暗号化ではない、との記事があったのですが、読めば納得。暗号化とは当事者間だけでわかるように情報を秘匿することで、ハッシュ化は誰も元に戻せず元の情報がわからなくなることです。しっかり、違いを認識しましょう。

→[[セキュリティ] ハッシュ化と暗号化は違いますよ！！](https://www.marineroad.com/staff-blog/17723.html)


**可逆暗号**
復号可能なデータの変換のこと。個人情報など、DBに保存するときは見られたくないが、復号が必要なものに用いる。

**ハッシュ化**
復号不可能なデータの変換のこと。パスワードなど、誰にも見られてはならないものに使う。認証時はどうするかというと、送られてきたデータを同じくハッシュ化して、DBにあるハッシュ化されたパスワードと比較する。（同じ値をハッシュ化したら、同じ値が得られる。しかし、ハッシュ化した値から元の値を復号するのは不可能。）

今回は可逆暗号化について説明します。

##Railsで可逆暗号
実際にRailsコンソールでやってみましょう。

```rb
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!`です。

```rb
secret = SecureRandom::hex(128) 
```

上記の箇所では乱数を生成しています。

```rb
encryptor = ::ActiveSupport::MessageEncryptor.new(secret, cipher: 'aes-256-cbc')`
```
chipherとは暗号の意味で、ここでは`aes-256-cbc`を採用しています。（暗号化のアルゴリズム等は話がそれるので割愛。）鍵として先ほどの乱数を用います。
ちなみに右辺の`::ActiveSupport`の部分、これはトップレベルでの`ActiveSupport`を呼び出しています。詳しくは参考記事見てください。ちょっとした小技らしいです。

```rb
encrypt_message = encryptor.encrypt_and_sign(message)
```
先ほどのオブジェクトに`message`を渡して暗号化します。

```rb
encryptor.decrypt_and_verify(encrypt_message)
```
これで復号。


##試してみたこと
AESは共通鍵暗号方式って書いてあったから`secret`さえ覚えておけば誰でも復号可能。逆に、`secret`を忘れちゃうと、戻せない。てな訳で`secret`を一回上書きして違う値を入れて見た。

```rb
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!"
```

先ほど同様、暗号化した。ここから鍵を忘れてみる。

```rb
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
```
案の定復号できない。
じゃあ鍵を思い出して見る。

```rb
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