この記事はアピリッツの技術ブログ「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