<commit_before>require 'tokie/errors'

module Tokie
  class Encryptor
    def initialize(claims, secret:, digest: 'SHA1', serializer: nil, cipher: nil)
      @claims = claims
      @secret = secret
      @digest = digest
      @cipher = cipher || 'aes-256-cbc'
      @serializer = serializer || Tokie.serializer
    end

    def encrypt
      cipher = build_cipher.encrypt
      iv = cipher.random_iv

      encrypted_data = cipher.update(encoded_claims) + cipher.final

      header = ::Base64.strict_encode64(encoded_header)
      auth_tag = cipher.auth_tag header.size

      header << '.' << Tokie.encode(@secret, iv, encrypted_data, auth_tag).join('.')
    end

    class << self
      def decrypt(encrypted_token, options = {})
        Claims.parse new(encrypted_token, options).send(:decrypt_claims), options
      end
    end

    private
      def encoded_header
        { 'typ' => 'JWT', 'alg' => @digest.to_s, 'enc' => @cipher.to_s }.to_s
      end

      def encoded_claims
        @serializer.dump @claims.to_h
      end

      def build_cipher
        OpenSSL::Cipher::Cipher.new(@cipher).tap do |c|
          c.key = @secret
        end
      end

      def decrypt_claims
        header, key, iv, encrypted_data, auth_tag = Tokie.decode encrypted_token.split('.')

        cipher = build_cipher.decrypt
        cipher.iv = iv
        decrypted_data = cipher.update(encrypted_data) + cipher.final

        @serializer.load(decrypted_data)
      rescue OpenSSL::Cipher::CipherError, TypeError, ArgumentError
        raise InvalidMessage
      end
  end
end<commit_msg>Include concerns and generate auth tag correctly.<commit_after>require 'tokie/concerns/digestable'
require 'tokie/concerns/secure_comparable'
require 'tokie/errors'

module Tokie
  class Encryptor
    include Digestable, SecureComparable

    def initialize(claims, secret:, digest: 'SHA1', serializer: nil, cipher: nil)
      @claims = claims
      @secret = secret
      @digest = digest
      @cipher = cipher || 'aes-256-cbc'
      @serializer = serializer || Tokie.serializer
    end

    def encrypt
      cipher = build_cipher.encrypt
      iv = cipher.random_iv

      encrypted_data = cipher.update(encoded_claims) + cipher.final

      header = ::Base64.strict_encode64(encoded_header)
      auth_tag = generate_auth_tag header, iv, encrypted_data

      header << '.' << Tokie.encode(@secret, iv, encrypted_data, auth_tag).join('.')
    end

    class << self
      def decrypt(encrypted_token, options = {})
        Claims.parse new(encrypted_token, options).send(:decrypt_claims), options
      end
    end

    private
      def encoded_header
        { 'typ' => 'JWT', 'alg' => @digest.to_s, 'enc' => @cipher.to_s }.to_s
      end

      def encoded_claims
        @serializer.dump @claims.to_h
      end

      def build_cipher
        OpenSSL::Cipher::Cipher.new(@cipher).tap do |c|
          c.key = @secret
        end
      end

      def generate_auth_tag(header, iv, data)
        auth_length = [header.length * 8].pack("Q>")
        generate_digest [header, iv, data, auth_length].join
      end

      def decrypt_claims
        header, key, iv, encrypted_data, auth_tag = Tokie.decode encrypted_token.split('.')

        cipher = build_cipher.decrypt
        cipher.iv = iv
        decrypted_data = cipher.update(encrypted_data) + cipher.final

        @serializer.load(decrypted_data)
      rescue OpenSSL::Cipher::CipherError, TypeError, ArgumentError
        raise InvalidMessage
      end
  end
end
{'fraction_non_alphanumeric': 0.10344827586206896, 'fraction_numerical': 0.004844685095468795, 'mean_word_length': 2.652445369406868, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4000847', 'n_tokens_mistral': 1159, 'n_tokens_neox': 1065, 'n_words': 247}