何でも屋エンジニアのブログ

ソフトウェア関連技術、コミュニティ、日々の雑貨

ActiveSupport::MessageEncryptor について調べたのでメモ

IV(初期化ベクトル)をどこに保存してあるのだろう、どうやって復号化しているのだろうか?と調べたのでメモ。

#encrypt_and_signの返り値は、--区切りで暗号化データ・初期化ベクトル・認証タグとなっている(それぞれBase64符号化を行っている)。

# Rely on OpenSSL for the initialization vector
iv = cipher.random_iv
cipher.auth_data = "" if aead_mode?

encrypted_data = cipher.update(Messages::Metadata.wrap(@serializer.dump(value), metadata_options))
encrypted_data << cipher.final

blob = "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}"
blob = "#{blob}--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode?
blob

ref: active_support/message_encryptor.rb

実行結果

crypt = ActiveSupport::MessageEncryptor.new('a'*32)
=> #<ActiveSupport::MessageEncryptor:0x00007f9fc8fefaa0 @secret="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", @sign_secret=nil, @cipher="aes-256-gcm", @aead_mode=true, @verifier=ActiveSupport::MessageEncryptor::NullVerifier, @serializer=Marshal, @options={}, @rotations=[]>

crypt.encrypt_and_sign('secret data that needs to be concealed')
=> "PnsLv1E/iLi0ZZxE4NKAavBO3UJc4Xe3KUZnvsJfqmA61Rt/iGPjtYbxy55/OUng--HcmSJhaaOrSoBGVL--oGfjcvq66Kw4i2Yk3A6neQ=="
irb(main):054:0>

上に示したように返り値は PnsLv1E/iLi0ZZxE4NKAavBO3UJc4Xe3KUZnvsJfqmA61Rt/iGPjtYbxy55/OUng--HcmSJhaaOrSoBGVL--oGfjcvq66Kw4i2Yk3A6neQ==となっており、--で分解するとそれぞれ

  • PnsLv1E/iLi0ZZxE4NKAavBO3UJc4Xe3KUZnvsJfqmA61Rt/iGPjtYbxy55/OUng (暗号化データ)
  • HcmSJhaaOrSoBGVL(IV)
  • oGfjcvq66Kw4i2Yk3A6neQ==(認証タグ)

となっている。

したがって初期化ベクトルをDBに保存する必要がない。以下に引用するように、実際の処理ではは encrypted_data, iv, auth_tag = encrypted_message.split("--").map { |v| ::Base64.strict_decode64(v) } としている。ここが attr-encrypted gemとの違いで気軽に使いやすい。

def _decrypt(encrypted_message, purpose)
        cipher = new_cipher
        encrypted_data, iv, auth_tag = encrypted_message.split("--").map { |v| ::Base64.strict_decode64(v) }

        # Currently the OpenSSL bindings do not raise an error if auth_tag is
        # truncated, which would allow an attacker to easily forge it. See
        # https://github.com/ruby/openssl/issues/63
        raise InvalidMessage if aead_mode? && (auth_tag.nil? || auth_tag.bytes.length != 16)

        cipher.decrypt
        cipher.key = @secret
        cipher.iv  = iv
        if aead_mode?
          cipher.auth_tag = auth_tag
          cipher.auth_data = ""
        end

        decrypted_data = cipher.update(encrypted_data)
        decrypted_data << cipher.final

        message = Messages::Metadata.verify(decrypted_data, purpose)
        @serializer.load(message) if message
      rescue OpenSSLCipherError, TypeError, ArgumentError
        raise InvalidMessage
end

ref: active_support/message_encryptor.rb

参考