# frozen_string_literal: true class String # UTF-8 XOR with key, padding left the shortest element # @param key [String] the element to xor the string with # @return [String] the xored string (UTF-8) # @example # 'hello'.ulxor('key') # => "he\a\t\u0016" # 'key'.ulxor('hello') # => "he\a\t\u0016" def ulxor(key) b1 = unpack('U*') b2 = key.unpack('U*') longest = [b1.length, b2.length].max b1 = ([0] * (longest - b1.length)) + b1 b2 = ([0] * (longest - b2.length)) + b2 b1.zip(b2).map { |a, b| a ^ b }.pack('U*') end # UTF-8 XOR with key (padding left) in place as described for {String#ulxor}. def ulxor!(key) replace(ulxor(key)) end # ASCII XOR with key, padding left the shortest element # @param key [String] the element to xor the string with # @return [String] the xored string (ASCII) # @example # 'hello'.alxor('key') # => "he\a\t\x16" # 'key'.alxor('hello') # => "he\a\t\x16" def alxor(key) b1 = force_encoding('UTF-8') b2 = key.force_encoding('UTF-8') raise 'The string is not ASCII' unless b1.ascii_only? raise 'The key is not ASCII' unless b2.ascii_only? b1 = b1.chars.map(&:ord) b2 = b2.chars.map(&:ord) longest = [b1.length, b2.length].max b1 = ([0] * (longest - b1.length)) + b1 b2 = ([0] * (longest - b2.length)) + b2 b1.zip(b2).map { |a, b| (a ^ b).chr }.join end # ASCII XOR with key (padding left) in place as described for {String#alxor}. def alxor!(key) replace(alxor(key)) end # UTF-8 XOR with key, padding right the shortest element # @param key [String] the element to xor the string with # @return [String] the xored string (UTF-8) # @example # 'hello'.urxor('key') # => "\u0003\u0000\u0015lo" # 'key'.urxor('hello') # => "\u0003\u0000\u0015lo" def urxor(key) b1 = unpack('U*') b2 = key.unpack('U*') longest = [b1.length, b2.length].max b1 += [0] * (longest - b1.length) b2 += [0] * (longest - b2.length) b1.zip(b2).map { |a, b| a ^ b }.pack('U*') end # UTF-8 XOR with key (padding right) in place as described for {String#urxor}. def urxor!(key) replace(urxor(key)) end # ASCII XOR with key, padding right the shortest element # @param key [String] the element to xor the string with # @return [String] the xored string (ASCII) # @example # 'hello'.arxor('key') # => "\x03\x00\x15lo" # 'key'.arxor('hello') # => "\x03\x00\x15lo" def arxor(key) b1 = force_encoding('UTF-8') b2 = key.force_encoding('UTF-8') raise 'The string is not ASCII' unless b1.ascii_only? raise 'The key is not ASCII' unless b2.ascii_only? b1 = b1.chars.map(&:ord) b2 = b2.chars.map(&:ord) longest = [b1.length, b2.length].max b1 += [0] * (longest - b1.length) b2 += [0] * (longest - b2.length) b1.zip(b2).map { |a, b| (a ^ b).chr }.join end # ASCII XOR with key (padding right) in place as described for {String#arxor}. def arxor!(key) replace(arxor(key)) end end