artis3n
artis3n

Reputation: 840

Ruby XOR bit wise manipulation cryptography exercise

I am trying to convert a message into a ASCII hex value string and back again. However, I am having a lot of trouble with my ^ bit wise XOR operator. I've spent the last 4 hours searching stackoverflow's similar questions on XOR and bit wise manipulation, but no suggestion I've seen has fixed this issue yet.

I have a RakeTest file, which the following made up test:

def test_xor
    key = 'hi'
    msg = 'Hello There, how are you?'
    key_trunc = key
    key_trunc << key while key.length < msg.length
    key_trunc = Decrypt.truncate_key key_trunc, msg.length

    ct = Decrypt.xor msg, key_trunc
    assert_equal('200c040507493c010d1b0d454801071e48081a0c4810071c57', ct)
end

I've worked out by hand (and verified with an online hex converter) that the correct ASCII hex result should be what is above. Here is my Decrypt module:

module Decrypt

  # Returns an array of ASCII Hex values
  def self.to_hex_array(str_hex)
    raise ArgumentError 'Argument is not a string.' unless str_hex.is_a? String

    result = str_hex.unpack('C*').map { |e| e.to_s 16 }

    result
  end

  def self.to_str_from_hex_array(hex_str)
    return [hex_str].pack 'H*'
  end

  def self.xor(msg, key)

    msg = self.to_hex_array msg
    key = self.to_hex_array key
    xor = []

    (0...msg.length).each do |i|
      xor.push msg[i] ^ key[i]
    end

    return xor.join
  end

  def self.truncate_key(str, len)
    str = str.to_s[0...len] if str.length > len

    return str

  end

end

I've confirmed in two separate rake test functions that to_hex_array and to_str_from_hex_array are working correctly. When I run the above rake test, I get a 'NoMethodError: undefined method '^' for "48":String. 48 is the beginning hex value, and obviously Strings cannot undergo bit wise operations, but I have tried every method I could find to convert the values such that '^' will operate correctly.

The closest I can get (no error thrown) is to change the operation inside the loop to msg[i].hex ^ key[i].hex, but that outputs an ASCII dec value.Can anyone help me out?


EDIT: Thanks to the suggestions below, I am able to run the following tests successfully:

def test_xor
    key = 'hi'
    msg = 'Hello There, how are you?'
    key_trunc = key
    key_trunc << key while key.length < msg.length
    key_trunc = Decrypt.truncate key_trunc, msg.length

    ct = Decrypt.xor msg, key_trunc
    assert_equal(['200c040507493c010d1b0d454801071e48081a0c4810071c57'], ct)
end

def test_decrypt
    msg = 'attack at dawn'
    key = '6c73d5240a948c86981bc294814d'
    key = [key].pack('H*')

    new_key = Decrypt.xor msg, key
    assert_equal(['0d07a14569fface7ec3ba6f5f623'], new_key)
    ct = Decrypt.xor 'attack at dusk', new_key.pack('H*')
    assert_equal(['6c73d5240a948c86981bc2808548'], ct)
end

For those interested, here is the successful Decrypt module:

module Decrypt

  # Returns an array of ASCII Hex values
  def self.to_dec_array(str_hex)
    raise ArgumentError, 'Argument is not a string!' unless str_hex.is_a? String

    dec_array = str_hex.unpack('C*')

    dec_array
  end

  def self.to_str_from_dec_array(dec_array)
    raise ArgumentError, 'Argument is not an array!' unless dec_array.is_a? Array

    return dec_array.pack 'C*'
  end

  def self.print_dec_array(dec_array)
    return [dec]
  end

  def self.xor(msg, key)

    msg = self.to_dec_array msg
    key = self.to_dec_array key
    xor = []

    (0...msg.length).each do |i|
      xor.push msg[i] ^ key[i]
    end

    xor = xor.pack('C*').unpack('H*')

    xor
  end

  def self.truncate(str, len)
    str = str.to_s[0...len] if str.length > len

    return str

  end

end

Upvotes: 2

Views: 928

Answers (1)

Frederick Cheung
Frederick Cheung

Reputation: 84124

In your to_hex_array method you shouldn't be converting the bytes to strings (your call to to_s 16) - that's why you end up trying to xor strings rather than integers

That does mean that your xor method will need an extra step to turn the result from an array of integers to a string - something like

array_of_integers.map {|int| int.to_s(16)}.join

Upvotes: 2

Related Questions