見出し画像

Ruby on Rails6の暗号化データの復号化処理ができない問題で詰まった話

こんにちは

初投稿になるのですが、今回は私が仕事中にハマったRuby on Rails6の復号化処理がうまくできなかった問題と解決方法についてお話ししたいと思います。同じような問題で困っている方のお役に立てればと思います。

目次
・暗号化処理について
 ・ActiveSupport::MessageEncryptorの暗号化、復号化処理コード
 ・ActiveSupport::MessageEncryptorの暗号方式 Base64の特徴
・復号化処理時の問題点
 ・Post通信による問題
 ・csv出力による問題
 ・解決方法
・まとめ

暗号化処理について

ActiveSupport::MessageEncryptorの暗号化、復号化処理コードActiveSupport::MessageEncryptorで暗号化と復号化の処理を行います。
公式のサンプルだと以下のようなコードになります

# キーの生成処理
len   = ActiveSupport::MessageEncryptor.key_len
salt  = SecureRandom.random_bytes(len)
key   = ActiveSupport::KeyGenerator.new('password').generate_key(salt, len) # => "\x89\xE0\x156\xAC..."

# インスタンス生成
crypt = ActiveSupport::MessageEncryptor.new(key)                            # => #<ActiveSupport::MessageEncryptor ...>

# 暗号化処理
encrypted_data = crypt.encrypt_and_sign('my secret data')                   # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."

# 復号化処理
crypt.decrypt_and_verify(encrypted_data)               

ちなみにキーは固定の文字列でも問題ありません。

ActiveSupport::MessageEncryptorの暗号方式 Base64の特徴
まずBase64についてですが、基本になる64文字で暗号化を行う処理になります。その基本となる文字には下記の64文字と"="(イコール)の65文字が含まれます。
・A-Z 26文字
・a-z 26文字
・0-9 10文字
・+(これが問題になる可能性あり)
・/(これが問題になる可能性あり) 
・=

また具体的な説明は参考資料のwikipedia Base64を見ていただければと思うのですが、Base64は暗号化処理の後、文字数が必ず4の倍数になるように不足分が"="でパディングされます。

復号化処理時の問題点

Post通信による問題
実は"+"をPost通信をするときに半角スペースになってしまうようです。なので、暗号化したデータでPost通信を挟む場合に、この変換が起きてしまって復号化ができなくなってしまいます。

csv出力による問題
こちらの問題がどこの記事にも書かれていなかったので、自分で特定する必要がありました。
WordPressに保存された暗号化データをcsv出力して復号化する必要があったのですが、結論から言いますと先頭の"+"が削除されていたのです。
こちらの原因が、下記の処理を経由したことによって発生していました。
・Railsでデータを暗号化
・暗号化したデータをWordPressに送信(Post通信で"+"が半角スペースに)
・送信した暗号化データをWordPress側のDBに保存
・WordPressで暗号化データをcsv出力(先頭の半角スペースが削除)

解決方法
Post通信による問題の解決方法は、暗号化データを確認して半角スペースがあれば、"+"に変換する処理を入れれば大丈夫です。
csv出力による問題は、Base64で変換した文字列が4の倍数になることを利用して、暗号化データの文字列長が4の倍数じゃなかった場合に先頭に"+"を追加して、4の倍数になるように調整すれば復号可能なデータになります。

以下サンプルのコードになるのですが、Rails6の暗号化処理の出力フォーマットが、
<元データのBase64暗号化>--<初期ベクトルのBase64暗号化>--<認証タグのBase64暗号化>
になっているので<元データのBase64暗号化>の部分を抜き出す必要もあります。

BASE64_MULTIPLE_LENGTH = 4

def restore_data(encrypted_data)
  array_data = encrypted_data.split('--')
  lack_length = BASE64_MULTIPLE_LENGTH - array_data[0].size % 4
  if lack_length != 0 && lack_length != BASE64_MULTIPLE_LENGTH
    array_data[0] =  '+' * lack_length + array_data[0]
  end

  array_data.join('--').gsub(/ /, '+')
end

まとめ

開発チーム内でも解決策が見つからず、バグじゃないのか?という意見もあったのですが、無事に問題を特定して、解決することができてよかったと思っています。またそれ以上に、問題に直面した時は基本的な仕組みに目を向けることで解決策を見つけることができるとわかった瞬間でした。

参考資料
Rails 6.0.3 ActiveSupport::MessageEncryptor 公式ドキュメント
wikipedia Base64
wikipedia 初期ベクトル
github ActiveSupport::MessageEncryptorのコード

この記事が気に入ったら、サポートをしてみませんか?
気軽にクリエイターの支援と、記事のオススメができます!