# frozen_string_literal: true # # Copyright (c) 2006-2024 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 . # module Ronin module Support class Encoding < ::Encoding # # [Base32] encoding. # # [Base32]: https://datatracker.ietf.org/doc/html/rfc3548#page-6 # # ## Core-Ext Methods # # * {String#base32_encode} # * {String#base32_decode} # # @api public # # @since 1.0.0 # module Base32 # # Base32 encodes the given String. # # @param [String] data # The String to encode. # # @return [String] # The Base32 encoded String. # def self.encode(data) encoded = String.new each_chunk(data,5) do |chunk| chunk.encode(encoded) end return encoded end # # Base32 decodes the given String. # # @param [String] data # The String to decode. # # @return [String] # The Base32 decoded String. # def self.decode(data) decoded = String.new(encoding: Encoding::UTF_8) each_chunk(data,8) do |chunk| chunk.decode(decoded) end return decoded end # Base32 alphabet # # @api private TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' # # Represents a chunk of data. # # @api private # class Chunk # # Initializes the chunk. # # @param [Array] bytes # The bytes for the chunk. # def initialize(bytes) @bytes = bytes end # # Decodes the chunk. # # @param [String] output # Optional output buffer. # # @return [String] # The Base32 decoded chunk. # def decode(output=String.new) bytes = @bytes.take_while { |b| b != 61 } # strip padding n = ((bytes.length * 5.0) / 8.0).floor p = if bytes.length < 8 5 - ((n * 8) % 5) else 0 end c = bytes.reduce(0) { |m,o| unless (i = Base32::TABLE.index(o.chr)) raise ArgumentError, "invalid character '#{o.chr}'" end (m << 5) + i } >> p (0..(n - 1)).reverse_each do |i| output << ((c >> (i * 8)) & 0xff).chr end return output end # # Encodes the chunk. # # @param [String] output # Optional output buffer. # # @return [String] # The Base32 encoded chunk. # def encode(output=String.new) n = ((@bytes.length * 8.0) / 5.0).ceil p = if n < 8 5 - ((@bytes.length * 8) % 5) else 0 end c = @bytes.inject(0) { |m,o| (m << 8) + o } << p (0..(n - 1)).reverse_each do |i| output << Base32::TABLE[(c >> (i * 5)) & 0x1f].chr end return output << ("=" * (8 - n)) end end # # Enumerates over the consecutive chunks within the given data. # # @param [String] data # # @param [Integer] size # # @yield [chunk] # # @yieldparam [Chunk] chunk # # @api private # def self.each_chunk(data,size) data.bytes.each_slice(size) do |byte_slice| yield Chunk.new(byte_slice) end end end end end end require 'ronin/support/encoding/base32/core_ext'