<commit_before>require "rbnacl"
require "openssl"

module BitVault
  module Crypto

    class PassphraseBox
      include BitVault::Encodings
      extend BitVault::Encodings

      # Given passphrase and plaintext as strings, returns a Hash
      # containing the ciphertext and other values needed for later
      # decryption.
      def self.encrypt(passphrase, plaintext)
        box = self.new(passphrase)
        box.encrypt(plaintext)
      end

      # PassphraseBox.decrypt "<PASSWORD>",
      #   :salt => salt, :nonce => nonce, :ciphertext => ciphertext
      #
      def self.decrypt(passphrase, hash)
        salt, nonce, ciphertext =
          hash.values_at(:salt, :nonce, :ciphertext).map {|s| decode_base58(s) }
        box = self.new(passphrase, salt)
        box.decrypt(nonce, ciphertext)
      end

      attr_reader :salt

      def initialize(passphrase, salt=nil)
        @salt = salt || RbNaCl::Random.random_bytes(16)
        key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(
          passphrase, @salt,
          10_000, # number of iterations
          32      # key length in bytes
        )
        @box = RbNaCl::SecretBox.new(key)
      end

      def encrypt(plaintext)
        nonce = RbNaCl::Random.random_bytes(RbNaCl::SecretBox.nonce_bytes)
        ciphertext = @box.encrypt(nonce, plaintext)
        {
          :salt => base58(@salt),
          :nonce => base58(nonce),
          :ciphertext => base58(ciphertext)
        }
      end

      def decrypt(nonce, ciphertext)
        @box.decrypt(nonce, ciphertext)
      end

    end

  end
end<commit_msg>configurable work factor for PassphraseBox<commit_after>require "rbnacl"
require "openssl"

module BitVault
  module Crypto

    class PassphraseBox
      include BitVault::Encodings
      extend BitVault::Encodings

      # PBKDF2 work factor
      ITERATIONS = 100_000

      # Given passphrase and plaintext as strings, returns a Hash
      # containing the ciphertext and other values needed for later
      # decryption.
      def self.encrypt(passphrase, plaintext)
        box = self.new(passphrase)
        box.encrypt(plaintext)
      end

      # PassphraseBox.decrypt "<PASSWORD> password",
      #   :salt => salt, :nonce => nonce, :ciphertext => ciphertext
      #
      def self.decrypt(passphrase, hash)
        salt, nonce, ciphertext =
          hash.values_at(:salt, :nonce, :ciphertext).map {|s| decode_base58(s) }
        box = self.new(passphrase, salt, hash[:iterations])
        box.decrypt(nonce, ciphertext)
      end

      attr_reader :salt

      # Initialize with an existing salt and iterations to allow
      # decryption.  Otherwise, creates new values for these, meaning
      # it creates an entirely new secret-box.
      def initialize(passphrase, salt=nil, iterations=nil)
        @salt = salt || RbNaCl::Random.random_bytes(16)
        @iterations = iterations || ITERATIONS

        key = <KEY>(
          passphrase, @salt,
          # TODO: decide on a very safe work factor
          # https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet
          #
          @iterations, # number of iterations
          32      # key length in bytes
        )
        @box = RbNaCl::SecretBox.new(key)
      end

      def encrypt(plaintext)
        nonce = RbNaCl::Random.random_bytes(RbNaCl::SecretBox.nonce_bytes)
        ciphertext = @box.encrypt(nonce, plaintext)
        {
          :salt => base58(@salt),
          :iterations => @iterations,
          :nonce => base58(nonce),
          :ciphertext => base58(ciphertext)
        }
      end

      def decrypt(nonce, ciphertext)
        @box.decrypt(nonce, ciphertext)
      end

    end

  end
end
{'fraction_non_alphanumeric': 0.09494015233949946, 'fraction_numerical': 0.010609357997823721, 'mean_word_length': 2.3096309630963097, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 19, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, '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': '3357002', 'n_tokens_mistral': 1198, 'n_tokens_neox': 1106, 'n_words': 272}