その他
    ホーム技術発信DoRubyRailsの可逆暗号化方法 ActiveSupport::MessageEncryptorの使用方法

    Railsの可逆暗号化方法 ActiveSupport::MessageEncryptorの使用方法

    この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

    Railsで簡単に可逆暗号化方法を提供しています。ActiveSupport::MessageEncryptorです。

    今日は、ActiveSupport::MessageEncryptorの使い方についてご紹介します。


    ①プレーンテキスト(message)、鍵(secret)を以下のようにする。
    2.1.1 :001 > message = ‘test’
     => “test”
    2.1.1 :002 > secret = SecureRandom::hex(50)
     => “06e40eedc81374bbef74bb2ac1c4a5d9928287c5146949d8d56a566495a5c8ecb8499177bce629d55a8e485d7dc66a183d47”
    2.1.1 :003 >

    ②ecryptorの宣言(AES-256, Cipher Block Chainingで暗号化)
    2.1.1 :003 > encryptor = ::ActiveSupport::MessageEncryptor.new(secret, cipher: ‘aes-256-cbc’)
     => #, @serializer=Marshal>

    ③暗号化(encrypt_and_sign)
    2.1.1 :006 > encrypt_message = encryptor.encrypt_and_sign(message)
     => “YjBvd0dOemF5YWN3eHI1OXRUUmk5Zz09LS11OXR6akNlK0IxWUFTdyt5YTlQUWNBPT0=–737d367a2db323c2890084a3139e1e39e7e16c5d”

    ④復号化(decrypt_and_verify)
    2.1.1 :007 > encryptor.decrypt_and_verify(encrypt_message)
     => “test”

    以上は、通常の使い方です。

    でも、ここで、注意しないとできないのは、
    ③暗号化(encrypt_and_sign)
    2.1.1 :006 > encrypt_message = encryptor.encrypt_and_sign(message)
    は、実行するたびに、結果が変わります。
    2.1.1 :012 >   encrypt_message = encryptor.encrypt_and_sign(message)
     => “bjZOekxmZnlqRUtzT3V4NFBNb2lqdz09LS1FVEFVZ0xsTUFRK2U2dWZtUmRQZ0RBPT0=–8f8882f9d87d60d7ac85069cd7d2d58d105bbf25”
    2.1.1 :013 > encrypt_message = encryptor.encrypt_and_sign(message)
     => “am1sQzI5SUhweG43TFVrK05hb3lPdz09LS1Cb0d3SHZKa2dobjQxU3ZLWVRpd3B3PT0=–b951c5800a881b79d309cace046349b41866936b”

    ソースを見れば、原因がわかると思いますが、
    .rvm/gems/ruby-2.1.1/gems/activesupport-4.1.4/lib/active_support/message_encryptor.rb

     67
     68     def _encrypt(value)
     69       cipher = new_cipher
     70       cipher.encrypt
     71       cipher.key = @secret
     72
     73       # Rely on OpenSSL for the initialization vector
     74       iv = cipher.random_iv #### => ここです。ivが実行するたびにランダムで生成する
     75
     76       encrypted_data = cipher.update(@serializer.dump(value))
     77       encrypted_data << cipher.final
     78
     79       “#{::Base64.strict_encode64 encrypted_data}–#{::Base64.strict_encode64 iv}”
     80     end
     81

    こうすると、暗号化の結果を別のところで、使いたい場合、ivを固定させるカスタマイズする必要があります。

    例えば:

     44     def initialize(secret, *signature_key_or_options)
     45       options = signature_key_or_options.extract_options!
     46       sign_secret = signature_key_or_options.first
     47       @secret = secret
     48       @sign_secret = sign_secret
     49       @cipher = options[:cipher] || ‘aes-256-cbc’
     50       @verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer)
     51       @serializer = options[:serializer] || Marshal
     52       @iv = options[:iv]

    53     end

     68
     69     def _encrypt(value)
     70       cipher = new_cipher
     71       cipher.encrypt
     72       cipher.key = @secret
     73
     74       # Rely on OpenSSL for the initialization vector
     75       iv = @iv || cipher.random_iv
     76
     77       encrypted_data = cipher.update(@serializer.dump(value))
     78       encrypted_data << cipher.final
     79
     80       “#{::Base64.strict_encode64 encrypted_data}–#{::Base64.strict_encode64 iv}”
     81     end

    こうすると、実行するたびに結果が同じです。


    irb: warn: can’t alias context from irb_context.
    2.1.1 :009 > message = ‘test’
     => “test”
    2.1.1 :010 >
    2.1.1 :011 > message.encoding
     => #
    2.1.1 :012 >
    2.1.1 :013 >   secret = SecureRandom::hex(50)
     => “b9aa9c2b5af0349c3b334ead4f8b1e2a3ff42921425bf2df1aefd3d3aefd4b4b5ce816e38fdcad231b985dee8dbf40200d06”
    2.1.1 :014 > iv = SecureRandom::hex(20)
     => “fa4ad72743b789ff188c7c50a56b3b563e1e68e9”
    2.1.1 :015 > encryptor = ::ActiveSupport::MessageEncryptor.new(secret, cipher: ‘aes-256-cbc’, iv: iv)
     => #, @serializer=Marshal, @iv=”fa4ad72743b789ff188c7c50a56b3b563e1e68e9″>
    2.1.1 :016 > encrypt_message = encryptor.encrypt_and_sign(message)
     => “b0NGSXRYQVY2OCs4OS8vcDcrYU12Zz09LS1abUUwWVdRM01qYzBNMkkzT0RsbVpqRTRPR00zWXpVd1lUVTJZak5pTlRZelpURmxOamhsT1E9PQ==–c249ada02f27b5482b682537180c751e266bd608”
    2.1.1 :017 >
    2.1.1 :018 >   encrypt_message = encryptor.encrypt_and_sign(message)
     => “b0NGSXRYQVY2OCs4OS8vcDcrYU12Zz09LS1abUUwWVdRM01qYzBNMkkzT0RsbVpqRTRPR00zWXpVd1lUVTJZak5pTlRZelpURmxOamhsT1E9PQ==–c249ada02f27b5482b682537180c751e266bd608”
    2.1.1 :019 >
    2.1.1 :020 >   encrypt_message = encryptor.encrypt_and_sign(message)
     => “b0NGSXRYQVY2OCs4OS8vcDcrYU12Zz09LS1abUUwWVdRM01qYzBNMkkzT0RsbVpqRTRPR00zWXpVd1lUVTJZak5pTlRZelpURmxOamhsT1E9PQ==–c249ada02f27b5482b682537180c751e266bd608”
    2.1.1 :021 >


    もう一つ、目で見るとき、同じ”test”で、encodingが違うと、暗号化の結果が変わります。


    2.1.1 :023 > message.encoding
     => #
    2.1.1 :024 > encrypt_message = encryptor.encrypt_and_sign(message)
     => “b0NGSXRYQVY2OCs4OS8vcDcrYU12Zz09LS1abUUwWVdRM01qYzBNMkkzT0RsbVpqRTRPR00zWXpVd1lUVTJZak5pTlRZelpURmxOamhsT1E9PQ==–c249ada02f27b5482b682537180c751e266bd608”
    2.1.1 :025 >
    2.1.1 :026 > message_sjis = message.force_encode_utf8_to_sjis
    2.1.1 :028 > message_sjis.encoding
     => #
    2.1.1 :031 > message_sjis == message
     => true
    2.1.1 :033 > encrypt_message_sjis = encryptor.encrypt_and_sign(message_sjis)
     => “NkRsUTVWU0M5N3Z4Sm1VUGI3czNsY3poYnRyVGJhdHB3NE5IeE1qMGdQaz0tLVptRTBZV1EzTWpjME0ySTNPRGxtWmpFNE9HTTNZelV3WVRVMllqTmlOVFl6WlRGbE5qaGxPUT09–daf7901c7949383921e3b1b47dc389defd40892d”
    2.1.1 :036 > encrypt_message_sjis == encrypt_message
     => false