lib/miscreant/internals/util.rb in miscreant-0.1.0 vs lib/miscreant/internals/util.rb in miscreant-0.2.0

- old
+ new

@@ -5,79 +5,75 @@ module Internals # Internal utility functions module Util # :nodoc: module_function - # Perform a doubling operation as described in the CMAC and SIV papers - def dbl(value) - overflow = 0 - words = value.unpack("N4").reverse + # :nodoc: Lookup table for the number of trailing zeroes in a byte + CTZ_TABLE = [ + 8, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 + ].freeze - words.map! do |word| - new_word = (word << 1) & 0xFFFFFFFF - new_word |= overflow - overflow = (word & 0x80000000) >= 0x80000000 ? 1 : 0 - new_word - end + # Perform a constant time-ish comparison of two bytestrings + def ct_equal(a, b) + return false unless a.bytesize == b.bytesize - result = words.reverse.pack("N4") - result[-1] = (result[-1].ord ^ select(overflow, 0x87, 0)).chr - result - end + l = a.unpack("C*") + r = 0 + i = -1 - # Pad value with a 0x80 value and zeroes up to the given length - def pad(message, length) - padded_length = message.length + length - (message.length % length) - message += "\x80" - message.ljust(padded_length, "\0") + b.each_byte { |v| r |= v ^ l[i += 1] } + r.zero? end # Perform a constant time(-ish) branch operation - def select(subject, result_if_one, result_if_zero) + def ct_select(subject, result_if_one, result_if_zero) (~(subject - 1) & result_if_one) | ((subject - 1) & result_if_zero) end - # Perform an xor on arbitrary bytestrings - def xor(a, b) - length = [a.length, b.length].min - output = "\0" * length - length.times do |i| - output[i] = (a[i].ord ^ b[i].ord).chr - end - output + # Count the number of zeros in a given 8-bit integer + # + # @param value [Integer] an integer in the 8-bit unsigned range (0-255) + def ctz(value) + CTZ_TABLE.fetch(value) end - # XOR the second value into the end of the first - def xorend(a, b) - difference = a.length - b.length - - left = a.slice(0, difference) - right = a.slice(difference..-1) - - left + xor(right, b) + # Pad value with a 0x80 value and zeroes up to the given length + def pad(message, length) + padded_length = message.length + length - (message.length % length) + message += "\x80" + message.ljust(padded_length, "\0") end - # Zero out the top bits in the last 32-bit words of the IV - def zero_iv_bits(iv) - # "We zero-out the top bit in each of the last two 32-bit words - # of the IV before assigning it to Ctr" - # -- http://web.cs.ucdavis.edu/~rogaway/papers/siv.pdf - iv = iv.dup - iv[8] = (iv[8].ord & 0x7f).chr - iv[12] = (iv[12].ord & 0x7f).chr - iv - end + # Ensure a string is a valid bytestring (potentially of a given length) + def validate_bytestring(name, string, length: nil) + raise TypeError, "expected String for #{name}, got #{string.class}" unless string.is_a?(String) + raise ArgumentError, "#{name} must be Encoding::BINARY" unless string.encoding == Encoding::BINARY - # Perform a constant time-ish comparison of two bytestrings - def ct_equal(a, b) - return false unless a.bytesize == b.bytesize + case length + when Array + raise ArgumentError, "#{name} must be #{length.join(' or ')} bytes long" unless length.include?(string.bytesize) + when Integer + raise ArgumentError, "#{name} must be #{length}-bytes long" unless string.bytesize == length + else + raise TypeError, "bad length parameter: #{length.inspect} (#{length.class}" unless length.nil? + end - l = a.unpack("C*") - r = 0 - i = -1 - - b.each_byte { |v| r |= v ^ l[i += 1] } - r.zero? + string end end end end