# frozen_string_literal: true
#
# Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# Ronin Support is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ronin Support is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Ronin Support. If not, see .
#
require 'ronin/support/crypto/openssl'
module Ronin
module Support
module Crypto
#
# Represents a cryptographic cipher.
#
# ## Examples
#
# ### Encrypt Data
#
# aes256 = Crypto::Cipher.new('aes-256-cbc', direction: :encrypt,
# password: 'secret')
# aes256.encrypt("message in a bottle")
# # => "\x18\xC7\x00~\xA2\xA1\x80\x84c\x98,81mo\xBAZ\xDD\xF4\xF2\xEF\xA9\xDE\xB3\xD6!\xB9\xA8WT\x9D\xE0"
#
# ### Decrypt Data
#
# aes256 = Crypto::Cipher.new('aes-256-cbc', direction: :decrypt,
# password: 'secret')
# aes256.decrypt("\x18\xC7\x00~\xA2\xA1\x80\x84c\x98,81mo\xBAZ\xDD\xF4\xF2\xEF\xA9\xDE\xB3\xD6!\xB9\xA8WT\x9D\xE0")
# # => "message in a bottle"
#
# @see https://rubydoc.info/stdlib/openssl/OpenSSL/Cipher.html
#
# @since 1.0.0
#
# @api public
#
class Cipher < OpenSSL::Cipher
#
# Initializes the cipher.
#
# @param [String] name
# The name of the cipher.
#
# @param [:encrypt, :decrypt] direction
# Specifies whether the cipher will be used to encrypt or decrypt
# data.
#
# @param [Symbol, nil] hash
# The algorithm to hash the `:password`.
#
# @param [String, nil] key
# The secret key to use.
#
# @param [String, nil] password
# The password for the cipher.
#
# @param [String, nil] iv
# The optional Initial Vector (IV).
#
# @param [Integer, nil] padding
# Sets the padding for the cipher.
#
# @raise [ArgumentError]
# The `key:` or `password:` keyword arguments were not given.
#
def initialize(name, direction: ,
key: nil,
hash: :sha256,
password: nil,
iv: nil,
padding: nil)
super(name)
case direction
when :encrypt then self.encrypt
when :decrypt then self.decrypt
end
if password && hash
self.key = OpenSSL::Digest.const_get(hash.upcase).digest(password)
elsif key
self.key = key
else
raise(ArgumentError,"the the key: or password: keyword argument must be given")
end
self.iv = iv if iv
self.padding = padding if padding
end
#
# The list of supported ciphers.
#
# @return [Array]
# The list of supported cipher names.
#
def self.supported
ciphers
end
#
# Encrypts the given data.
#
# @param [String] data
# The data to encrypt.
#
# @return [String]
# The encrypted data.
#
def encrypt(data=nil)
if data
update(data) + final
else
super()
end
end
#
# Decrypts the given data.
#
# @param [String] data
# The data to decrypt.
#
# @return [String]
# The decrypted data.
#
def decrypt(data=nil)
if data
update(data) + final
else
super()
end
end
#
# Pipes the IO stream through the cipher.
#
# @param [IO] io
# The IO stream to encrypt or decrypt.
#
# @param [Integer] block_size
# The block size to read the data with.
#
# @param [String, #<<, nil] output
# The optional output buffer to append processed data to.
# Defaults to an empty ASCII 8bit encoded String.
#
# @yield [block]
# If a block is given, each processed block will be passed to it.
#
# @yieldparam [String] block
# A processed block from the file.
#
# @return [String]
# The processed data, if no block was given.
#
def stream(io, block_size: 16384, output: nil)
unless block_given?
output ||= String.new(encoding: Encoding::ASCII_8BIT)
end
until io.eof?
block = update(io.read(block_size))
if block_given? then yield block
else output << block
end
end
if block_given?
yield final
else
output << final
return output
end
end
end
end
end
end