From 837c03ca86177f4d55790c7444d0b32e98b2b026 Mon Sep 17 00:00:00 2001 From: Sharon Hao Date: Thu, 5 Jan 2012 17:02:08 -0500 Subject: [PATCH] Ruby version of the PS5 code. --- 5/code/rsa/.gitignore | 1 + 5/code/rsa/big_num.py | 1 + 5/code/rsa/big_num.rb | 331 +++++++++++++++++++++++++ 5/code/rsa/big_num_test.rb | 153 ++++++++++++ 5/code/rsa/ks_primitives.py | 2 +- 5/code/rsa/ks_primitives_test.rb | 182 ++++++++++++++ 5/code/rsa/ks_primitives_unchecked.pyc | Bin 0 -> 14805 bytes 5/code/rsa/ks_primitives_unchecked.rb | 251 +++++++++++++++++++ 5/code/rsa/rsa.rb | 148 +++++++++++ 5/code/rsa/rsa_test.rb | 39 +++ 10 files changed, 1107 insertions(+), 1 deletion(-) create mode 100644 5/code/rsa/.gitignore create mode 100644 5/code/rsa/big_num.rb create mode 100644 5/code/rsa/big_num_test.rb create mode 100644 5/code/rsa/ks_primitives_test.rb create mode 100644 5/code/rsa/ks_primitives_unchecked.pyc create mode 100644 5/code/rsa/ks_primitives_unchecked.rb create mode 100644 5/code/rsa/rsa.rb create mode 100644 5/code/rsa/rsa_test.rb diff --git a/5/code/rsa/.gitignore b/5/code/rsa/.gitignore new file mode 100644 index 0000000..a634797 --- /dev/null +++ b/5/code/rsa/.gitignore @@ -0,0 +1 @@ +*.jsonp \ No newline at end of file diff --git a/5/code/rsa/big_num.py b/5/code/rsa/big_num.py index b41055b..0fa6494 100644 --- a/5/code/rsa/big_num.py +++ b/5/code/rsa/big_num.py @@ -413,3 +413,4 @@ def normalize(self): def is_normalized(self): '''False if the number has at least one trailing 0 (zero) digit.''' return len(self.d) == 1 or self.d[-1] != Byte.zero() + diff --git a/5/code/rsa/big_num.rb b/5/code/rsa/big_num.rb new file mode 100644 index 0000000..70f3831 --- /dev/null +++ b/5/code/rsa/big_num.rb @@ -0,0 +1,331 @@ +require 'ks_primitives_unchecked' + +class BigNum + include Comparable + attr_accessor :d + + # Creates a BigNum from a sequence of digits. + # Args: + # digits:: the Bytes used to populate the BigNum + # size:: if set, the BigNum will only use the first 'size' elements of digits + # no_copy:: uses the 'digits' argument as the backing store for BigNum, + # if appropraite (meant for internal use inside BigNum) + def initialize(digits, size = nil, no_copy = false) + digits ||= [] + size ||= digits.length + raise 'BigNums cannot hold a negative amount of digits' if size < 0 + size = 1 if size == 0 + if no_copy and digits.size == size + @d = digits + else + @d = digits.dup + end + while @d.length < size + @d << Byte.zero + end + # Used by the Newton-Raphson division code. + @inverse = nil + @inverse_precision = nil + end + + # BigNum representing the number 0 (zero). + def self.zero(size = 1) + BigNum.new Array.new(size, Byte.zero), size, true + end + + # BigNum representing the number 1 (one). + def self.one(size = 1) + digits = Array.new size, Byte.zero + digits[0] = Byte.one + BigNum.new digits, size, true + end + + # BigNum representing the given hexadecimal number. + # Args: + # hex_string:: string containing the desired number in hexadecimal; the + # allowed digits are 0-9, A-F, a-f + def self.from_hex(hex_string) + digits = [] + hex_string.length.step 1, -2 do |i| + byte_string = i == 1 ? "0#{hex_string[0,1]}" : hex_string[i - 2, 2] + digits << Byte.from_hex(byte_string) + end + BigNum.new digits + end + + # Shorthand for from_hex(hex_string). + def self.h(hex_string) + BigNum.from_hex hex_string + end + + # Hexadecimal string representing this BigNum. + # This method does not normalize the BigNum, because it is used during debugging. + def to_hex + start = @d.length - 1 + while start > 0 and @d[start] == Byte.zero + start -= 1 + end + string = '' + start.downto(0) { |i| string += @d[i].hex } + string + end + + # == for BigNums. + # Comparing BigNums normalizes them. + def ==(other) + self.normalize + other.normalize + @d == other.d + end + + def <=>(other) + self.normalize + other.normalize + if @d.length == other.d.length + (@d.length - 1).downto 0 do |i| + return @d[i] < other.d[i] ? -1 : 1 if @d[i] != other.d[i] + end + 0 + else + @d.length < other.d.length ? -1 : 1 + end + end + + # This BigNum, with "digits" 0 digits appended at the end. + # Shifting to the left multiplies the BigNum by 256^digits. + def <<(digits) + new_digits = Array.new digits, Byte.zero + new_digits += @d + BigNum.new new_digits, nil, true + end + + # This BigNum, without the last "digits" digits. + # Shifting to the left multiplies the BigNum by 256^digits. + def >>(digits) + return digits >= @d.length ? BigNum.zero : BigNum.new(@d[digits, @d.length - digits], nil, true) + end + + # Adding numbers does not normalize them. However the result is normalized. + def +(other) + result = BigNum.zero 1 + [@d.length, other.d.length].max + carry = Byte.zero + 0.upto result.d.length - 1 do |i| + a = i < @d.length ? @d[i] + carry : carry.word + b = i < other.d.length ? other.d[i].word : Word.zero + word = a + b + result.d[i] = word.lsb + carry = word.msb + end + result.normalize + end + + # Subtraction is done using 2s complement. + # Subtracting numbers does not normalize them. However the result is normalized. + def -(other) + result = BigNum.zero [@d.length, other.d.length].max + carry = Byte.zero + 0.upto result.d.length - 1 do |i| + a = i < @d.length ? @d[i].word : Word.zero + b = i < other.d.length ? other.d[i] + carry : carry.word + word = a - b + result.d[i] = word.lsb + carry = a < b ? Byte.one : Byte.zero + end + result.normalize + end + + # Multiplying numbers does not normalize them. However, the result is normalized. + def *(other) + return @d.length <= 64 || other.d.length <= 64 ? self.slow_mul(other) : self.fast_mul(other) + end + + # Asymptotically slow method for multiplying two numbers w/ good constant factors. + def slow_mul(other) + c = BigNum.zero @d.length + other.d.length + 0.upto @d.length - 1 do |i| + carry = Byte.zero + 0.upto other.d.length - 1 do |j| + digit = @d[i] * other.d[j] + Word.from_byte(c.d[i + j]) + Word.from_byte(carry) + c.d[i + j] = digit.lsb + carry = digit.msb + end + c.d[i + other.d.length] = carry + end + c + end + + # Asymptotically fast method for multiplying two numbers. + def fast_mul(other) + in_digits = [@d.length, other.d.length].max + if in_digits == 1 + product = @d[0] * other.d[0] + return BigNum.new [product.lsb, product.msb], 2, true + end + split = in_digits/2 + self_low = BigNum.new @d[0, split], nil, true + self_high = BigNum.new @d[split, @d.length - split], nil, true + other_low = BigNum.new other.d[0, split], nil, true + other_high = BigNum.new other.d[split, other.d.length - split], nil, true + + result_high_high = self_high * other_high + result_low = self_low * other_low + result_high = (self_low + self_high) * (other_low + other_high) - (result_high_high + result_low) + ((result_high_high << (2 * split)) + (result_high << split) + result_low).normalize + end + + # Dividing numbers normalizes them. The result is also normalized. + def /(other) + self.divmod(other)[0] + end + + # Multiplying numbers does not normalize them. However, the result is normalized. + def %(other) + self.divmod(other)[1] + end + + # divmod for BigNums + # Dividing numbers normalizes them. The result is also normalized. + def divmod(other) + self.normalize + other.normalize + return self.slow_divmod(other) if @d.length <= 256 || other.d.length <= 256 + self.fast_divmod(other) + end + + # Asymptotically slow method for dividing two numbers w/ good constant factors. + def slow_divmod(other) + return self, BigNum.zero if other.d.length == 1 && other.d[0] == Byte.one + n = [@d.length, other.d.length].max + q = BigNum.zero n + r = BigNum.new @d + s = [] + s << BigNum.new(other.d, n) + i = 0 + loop do + i += 1 + s << s[i - 1] + s[i - 1] + break if s[i] > self + end + + i.downto 0 do |j| + q += q + if r >= s[j] + r -= s[j] + q += BigNum.one + end + end + return q, r + end + + # Asymptotically fast method for dividing two numbers. + def fast_divmod(other) + return self, BigNum.zero if other.d.length == 1 and other.d[0] == Byte.one + if !other.inverse + base = Word.from_bytes Byte.one, Byte.zero + msb_plus = (other.d[-1] + Byte.one).lsb + if msb_plus == Byte.zero + msb_inverse = (base - Word.one).lsb + other.inverse_precision = other.d.length + 1 + else + msb_inverse = base / msb_plus + other.inverse_precision = other.d.length + end + other.inverse = BigNum.new [msb_inverse], 1, true + end + bn_one = BigNum.one + # Division using other's multiplicative inverse. + loop do + quotient = (self * other.inverse) >> other.inverse_precision + product = other * quotient + + if product > self + product -= other + quotient -= bn_one + end + if product <= self + remainder = self - product + if remainder >= other + remainder -= other + quotient += bn_one + end + return quotient, remainder if remainder < other + end + # other needs a better multiplicative inverse approximation + other.improve_inverse + end + end + + protected + def improve_inverse + old_inverse = @inverse + old_precision = @inverse_precision + @inverse = ((old_inverse + old_inverse) << old_pecision) - (self * old_inverse * old_inverse) + @inverse.normalize + @inverse_precision *= 2 + zero_digits = 0 + while @inverse.d[zero_digits] == Byte.zero + zero_digits += 1 + end + if zero_digits > 0 + @inverse = @inverse >> zero_digits + @inverse_precision -= zero_digits + end + end + + def inverse + @inverse + end + + def inverse_precision + @inverse_precision + end + + # Modular ^ + # + # Args: + # exponent:: the exponent that this number will be raised to + # modulus:: the modulus + # + # Returns (self ^ exponent) mod modulus. + public + def powmod(exponent, modulus) + multiplier = BigNum.new @d + result = BigNum.one + exp = BigNum.new exponent.d + exp.normalize + two = (Byte.one + Byte.one).lsb + 0.upto exp.d.length - 1 do |i| + mask = Byte.one + 0.upto 7 do |j| + result = (result * multiplier) % modulus if (exp.d[i] & mask) != Byte.zero + mask = (mask * two).lsb + multiplier = (multiplier * multiplier) % modulus + end + end + result + end + + # Debugging help: returns the BigNum formatted as "0x????...". + def to_s + "0x#{self.to_hex}" + end + + # Debugging help: returns an expression that can create this BigNum. + def inspect + "BigNum.h('#{self.to_hex}', #{@d.length.to_s})" + end + + # Removes all the trailing 0 (zero) digits in this number. + # Returns self, for easy call chaining. + def normalize + while @d.length > 1 and @d[-1] == Byte.zero + @d.pop + end + self + end + + # False if the number has at least one trailing 0 (zero) digit. + def is_normalized + @d.length == 1 || @d[-1] != Byte.zero + end +end # class BigNum diff --git a/5/code/rsa/big_num_test.rb b/5/code/rsa/big_num_test.rb new file mode 100644 index 0000000..15eb717 --- /dev/null +++ b/5/code/rsa/big_num_test.rb @@ -0,0 +1,153 @@ +require 'test/unit' +require 'big_num' + +class BigNumTest < Test::Unit::TestCase + def test_equality + assert_equal BigNum.zero(1), BigNum.zero(1) + assert_equal BigNum.zero(1), BigNum.zero(2) + assert_equal BigNum.one(1), BigNum.one(1) + assert_equal BigNum.one(1), BigNum.one(2) + assert_not_equal BigNum.zero(1), BigNum.one(1) + assert_not_equal BigNum.zero(1), BigNum.one(2) + assert_not_equal BigNum.zero(2), BigNum.one(1) + assert_not_equal BigNum.zero(2), BigNum.one(2) + end + + def test_strings + assert_equal BigNum.zero(1).to_hex, '00' + assert_equal BigNum.zero(3).to_hex, '00' + assert_equal BigNum.one(1).to_hex, '01' + assert_equal BigNum.one(3).to_hex, '01' + assert_equal BigNum.one(3).to_s, '0x01' + assert_equal BigNum.one(1).inspect, "BigNum.h('01', 1)" + assert_equal BigNum.one(3).inspect, "BigNum.h('01', 3)" + assert_equal BigNum.from_hex('01'), BigNum.one + assert_equal BigNum.h('00'), BigNum.zero + assert_equal BigNum.from_hex('00F1E2D3C4B5').to_hex, 'F1E2D3C4B5' + end + + def test_is_normalized + assert BigNum.zero(1).is_normalized + assert !BigNum.zero(2).is_normalized + assert BigNum.one(1).is_normalized + assert !BigNum.one(2).is_normalized + assert BigNum.h('100').is_normalized + assert BigNum.h('0100').is_normalized + assert !BigNum.h('00101').is_normalized + end + + def test_normalize + assert_equal BigNum.one(1).normalize.inspect, "BigNum.h('01', 1)" + assert_equal BigNum.one(3).normalize.inspect, "BigNum.h('01', 1)" + assert_equal BigNum.zero(1).normalize.inspect, "BigNum.h('00', 1)" + assert_equal BigNum.zero(3).normalize.inspect, "BigNum.h('00', 1)" + end + + def test_comparisons + assert_operator BigNum.zero, :<, BigNum.one + assert_operator BigNum.zero, :<, BigNum.one + assert_operator BigNum.zero, :<=, BigNum.zero + assert_operator BigNum.one, :<=, BigNum.one + assert_operator BigNum.one, :>, BigNum.zero + assert_operator BigNum.one, :>=, BigNum.zero + assert_operator BigNum.zero, :>=, BigNum.zero + assert_operator BigNum.one, :>=, BigNum.one + + assert_operator BigNum.h('11FF'), :< ,BigNum.h('1200') + assert_operator BigNum.h('11FE'), :<, BigNum.h('11FF') + assert_operator BigNum.h('10FE'), :<, BigNum.h('1100') + assert_operator BigNum.h('FF11'), :<, BigNum.h('10000') + assert !(BigNum.h('1200') < BigNum.h('001200')) + assert_operator BigNum.h('11FF'), :<=, BigNum.h('1200') + assert_operator BigNum.h('11FE'), :<=, BigNum.h('11FF') + assert_operator BigNum.h('10FE'), :<=, BigNum.h('1100') + assert_operator BigNum.h('FF11'), :<=, BigNum.h('10000') + assert_operator BigNum.h('1200'), :<=, BigNum.h('001200') + end + + def test_shifting + assert_equal BigNum.h('1234567') >> 2, BigNum.h('123') + assert_equal BigNum.h('1234567') >> 0, BigNum.h('1234567') + assert_equal BigNum.h('1234567') >> 4, BigNum.zero() + assert_equal BigNum.h('1234567') >> 5, BigNum.zero() + assert_equal BigNum.h('12345') << 1, BigNum.h('1234500') + assert_equal BigNum.h('12345') << 2, BigNum.h('123450000') + assert_equal BigNum.h('12345') << 0, BigNum.h('12345') + assert_equal BigNum.one << 6, BigNum.h('1000000000000') + end + + def test_addition + assert_equal BigNum.zero + BigNum.zero, BigNum.zero + assert_equal BigNum.one + BigNum.zero, BigNum.one + assert_equal BigNum.zero + BigNum.one, BigNum.one + assert_equal BigNum.h('1234') + BigNum.h('5678'), BigNum.h('68AC') + assert_equal BigNum.h('1234') + BigNum.h('56789A'), BigNum.h('568ACE') + assert_equal BigNum.one() + BigNum.h('FFFFFF'), BigNum.h('1000000') + assert_equal BigNum.h('FEFDFC') + BigNum.h('FBFAF9F8'), BigNum.h('FCF9F7F4') + end + + def test_subtraction + assert_equal BigNum.zero - BigNum.zero, BigNum.zero + assert_equal BigNum.one - BigNum.zero, BigNum.one + assert_equal BigNum.one - BigNum.one, BigNum.zero + assert_equal BigNum.zero - BigNum.one, BigNum.h('FF') + assert_equal BigNum.h('5678') - BigNum.h('4321'), BigNum.h('1357') + assert_equal BigNum.h('4321') - BigNum.h('5678'), BigNum.h('ECA9') + assert_equal BigNum.h('56789A') - BigNum.h('4321'), BigNum.h('563579') + assert_equal BigNum.h('4321') - BigNum.h('56789A'), BigNum.h('A9CA87') + assert_equal BigNum.h('4321') - BigNum.h('056789A'), BigNum.h('FFA9CA87') + assert_equal BigNum.one - BigNum.h('FFFFFF'), BigNum.h('2') + assert_equal BigNum.zero - BigNum.h('FFFFFF'), BigNum.one() + assert_equal BigNum.one - BigNum.h('1000000'), BigNum.h('FF000001') + assert_equal BigNum.zero - BigNum.one(4), BigNum.h('FFFFFFFF') + end + + def test_multiplication + assert_equal BigNum.zero * BigNum.zero, BigNum.zero + assert_equal BigNum.one * BigNum.zero, BigNum.zero + assert_equal BigNum.one * BigNum.one, BigNum.one + assert_equal BigNum.h('1234') * BigNum.h('5678'), BigNum.h('06260060') + assert_equal BigNum.h('1234') * BigNum.h('56789A'), BigNum.h('06260B5348') + assert_equal BigNum.h('FFFFFF') * BigNum.h('FFFFFF'), BigNum.h('FFFFFE000001') + assert_equal BigNum.h('FEFDFC') * BigNum.h('FBFAF9F8'), BigNum.h('FAFD0318282820') + end + + def test_division + assert_equal BigNum.one / BigNum.one, BigNum.one, '1 / 1 == 1' + assert_equal BigNum.zero / BigNum.one, BigNum.zero, '0 / 1 == 0' + assert_equal BigNum.h('42') / BigNum.h('03'), BigNum.h('16') + assert_equal BigNum.h('43') / BigNum.h('03'), BigNum.h('16') + assert_equal BigNum.h('06260060') / BigNum.h('1234'), BigNum.h('5678') + assert_equal BigNum.h('06263F29') / BigNum.h('5678'), BigNum.h('1234') + assert_equal BigNum.h('06260FE3C9') / BigNum.h('56789A'), BigNum.h('1234') + assert_equal BigNum.h('FFFFFE000001') / BigNum.h('FFFFFF'), BigNum.h('FFFFFF') + assert_equal BigNum.h('FFFFFE0CFEDC') / BigNum.h('FFFFFF'), BigNum.h('FFFFFF') + assert_equal BigNum.h('FAFD0318282820') / BigNum.h('FEFDFC'), BigNum.h('FBFAF9F8') + assert_equal BigNum.h('FAFD0318C3D9EF') / BigNum.h('FEFDFC'), BigNum.h('FBFAF9F8') + assert_equal BigNum.h('100000000') / BigNum.h('20000'), BigNum.h('8000') + end + + def test_modulo + assert_equal BigNum.one % BigNum.one, BigNum.zero, '1 % 1 == 0' + assert_equal BigNum.zero % BigNum.one, BigNum.zero, '0 % 1 == 0' + assert_equal BigNum.h('42') % BigNum.h('03'), BigNum.zero + assert_equal BigNum.h('43') % BigNum.h('03'), BigNum.one + assert_equal BigNum.h('44') % BigNum.h('03'), BigNum.h('02') + assert_equal BigNum.h('06260060') % BigNum.h('1234'), BigNum.zero + assert_equal BigNum.h('06263F29') % BigNum.h('5678'), BigNum.h('3EC9') + assert_equal BigNum.h('06260FE3C9') % BigNum.h('56789A'), BigNum.h('49081') + assert_equal BigNum.h('FFFFFE000001') % BigNum.h('FFFFFF'), BigNum.zero() + assert_equal BigNum.h('FFFFFE0CFEDC') % BigNum.h('FFFFFF'), BigNum.h('CFEDB') + assert_equal BigNum.h('FAFD0318282820') % BigNum.h('FEFDFC'), BigNum.zero() + assert_equal BigNum.h('FAFD0318C3D9EF') % BigNum.h('FEFDFC'), BigNum.h('9BB1CF') + end + + def test_powmod + modulo = BigNum.h '100000000' + assert_equal BigNum.h('42').powmod(BigNum.zero, modulo), BigNum.one + assert_equal BigNum.h('42').powmod(BigNum.one, modulo), BigNum.h('42') + assert_equal BigNum.h('42').powmod(BigNum.h('2'), modulo), BigNum.h('1104') + assert_equal BigNum.h('42').powmod(BigNum.h('5'), modulo), BigNum.h('4AA51420') + assert_equal BigNum.h('41').powmod(BigNum.h('BECF'), modulo), BigNum.h('C73043C1') + end +end # class BigNumTest \ No newline at end of file diff --git a/5/code/rsa/ks_primitives.py b/5/code/rsa/ks_primitives.py index 02e6656..a56d1bb 100644 --- a/5/code/rsa/ks_primitives.py +++ b/5/code/rsa/ks_primitives.py @@ -321,4 +321,4 @@ def __init__(self, value): # Private: link Byte instances to their corresponding Words. for i in range(0, 0x100): - Byte._bytes[i]._word = Word._words[i] + Byte._bytes[i]._word = Word._words[i] \ No newline at end of file diff --git a/5/code/rsa/ks_primitives_test.rb b/5/code/rsa/ks_primitives_test.rb new file mode 100644 index 0000000..6338733 --- /dev/null +++ b/5/code/rsa/ks_primitives_test.rb @@ -0,0 +1,182 @@ +require 'test/unit' +require 'ks_primitives_unchecked' + +class ByteTest < Test::Unit::TestCase + def test_equality + assert_equal Byte.zero, Byte.zero, '0 == 0' + assert_equal Byte.one, Byte.one, '1 == 1' + assert_not_equal Byte.zero, Byte.one, '0 != 1' + assert_not_equal Byte.one, Byte.zero, '1 != 0' + end + + def test_comparisons + assert_operator Byte.one, :>, Byte.zero, '1 > 0' + assert !(Byte.zero > Byte.one), 'not (0 > 1)' + assert_operator Byte.one, :>=, Byte.zero, '1 >= 0' + assert_operator Byte.one, :>=, Byte.one, '1 >= 1' + assert_operator Byte.zero, :<, Byte.one, '0 < 1' + assert_operator Byte.zero, :<=, Byte.one, '0 <= 1' + assert_operator Byte.one, :<=, Byte.one, '1 <= 1' + end + + def test_strings + assert_equal Byte.one.hex, '01' + assert_equal Byte.one.to_s, '0x01' + assert_equal Byte.one.inspect, "Byte.h('01')" + assert_equal Byte.from_hex('01'), Byte.one + assert_equal Byte.h('00'), Byte.zero + assert_equal Byte.h('F1').hex, 'F1' + end + + def test_addition + assert_equal Byte.zero + Byte.zero, Word.zero, '0 + 0 == 0' + assert_equal Byte.one + Byte.zero, Word.one, '1 + 0 == 1' + assert_equal Byte.zero + Byte.one, Word.one, '0 + 1 == 1' + assert_equal Byte.h('5A') + Byte.h('A5'), Word.h('00FF') + assert_equal Byte.h('FF') + Byte.h('FF'), Word.h('01FE') + end + + def test_subtraction + assert_equal Byte.zero - Byte.zero, Word.zero, '0 - 0 == 0' + assert_equal Byte.one - Byte.zero, Word.one, '1 - 0 == 1' + assert_equal Byte.one - Byte.one, Word.zero, '1 - 1 == 0' + assert_equal Byte.h('A5') - Byte.h('5A'), Word.h('004B') + assert_equal Byte.h('5A') - Byte.h('A5'), Word.h('FFB5') + assert_equal Byte.h('FF') - Byte.h('FF'), Word.h('0000') + end + + def test_multiplication + assert_equal Byte.zero * Byte.zero, Word.zero, '0 * 0 == 0' + assert_equal Byte.zero * Byte.one, Word.zero, '0 * 1 == 0' + assert_equal Byte.one * Byte.one, Word.one, '1 * 1 == 0' + assert_equal Byte.h('A5') * Byte.h('5A'), Word.h('3A02') + assert_equal Byte.h('FF') * Byte.h('FF'), Word.h('FE01') + end + + def test_division + assert_equal Byte.one / Byte.one, Byte.one, '1 / 1 == 1' + assert_equal Byte.zero / Byte.one, Byte.zero, '0 / 1 == 0' + assert_equal Byte.h('42') / Byte.h('03'), Byte.h('16') + assert_equal Byte.h('43') / Byte.h('03'), Byte.h('16') + end + + def test_modulo + assert_equal Byte.one % Byte.one, Byte.zero, '1 % 1 == 0' + assert_equal Byte.zero % Byte.one, Byte.zero, '0 % 1 == 0' + assert_equal Byte.h('42') % Byte.h('03'), Byte.zero + assert_equal Byte.h('43') % Byte.h('03'), Byte.one + assert_equal Byte.h('44') % Byte.h('03'), Byte.h('02') + end + + def test_and + assert_equal Byte.one & Byte.one, Byte.one, '1 & 1 == 1' + assert_equal Byte.one & Byte.zero, Byte.zero, '1 & 0 == 0' + assert_equal Byte.zero & Byte.zero, Byte.zero, '0 & 0 == 0' + assert_equal Byte.h('5F') & Byte.h('6A'), Byte.h('4A') + end + + def test_or + assert_equal Byte.one | Byte.one, Byte.one, '1 | 1 == 1' + assert_equal Byte.one | Byte.zero, Byte.one, '1 | 0 == 1' + assert_equal Byte.zero | Byte.zero, Byte.zero, '0 | 0 == 0' + assert_equal Byte.h('5F') | Byte.h('6A'), Byte.h('7F') + end + + def test_xor + assert_equal Byte.one ^ Byte.one, Byte.zero, '1 ^ 1 == 0' + assert_equal Byte.one ^ Byte.zero, Byte.one, '1 ^ 0 == 1' + assert_equal Byte.zero ^ Byte.zero, Byte.zero, '0 ^ 0 == 0' + assert_equal Byte.h('5F') ^ Byte.h('6A'), Byte.h('35') + end +end # class Byte Test + + +class WordTest < Test::Unit::TestCase + def test_equality + assert_equal Word.zero, Word.zero, '0 == 0' + assert_equal Word.one, Word.one, '1 == 1' + assert_not_equal Word.zero, Word.one, '0 != 1' + assert_not_equal Word.one, Word.zero, '1 != 0' + end + + def test_equality_vs_byte + assert_not_equal Word.zero, Byte.zero, '(Word)0 != (Byte)0' + assert_not_equal Word.one, Byte.one, '(Word)1 != (Byte)1' + end + + def test_comparisons + assert_operator Word.one, :>, Word.zero, '1 > 0' + assert !(Word.zero > Word.one), 'not (0 > 1)' + assert_operator Word.one, :>=, Word.zero, '1 >= 0' + assert_operator Word.one, :>=, Word.one, '1 >= 1' + assert_operator Word.zero, :<, Word.one, '0 < 1' + assert !(Word.one < Word.zero), 'not (0 < 1)' + assert_operator Word.zero, :<=, Word.one, '0 <= 1' + assert_operator Word.one, :<=, Word.one, '1 <= 1' + end + + def test_strings + assert_equal Word.one.hex, '0001' + assert_equal Word.one.to_s, '0x0001' + assert_equal Word.one.inspect, "Word.h('0001')" + assert_equal Word.from_hex('0001'), Word.one + assert_equal Word.h('0000'), Word.zero + assert_equal Word.h('FE12').hex, 'FE12' + end + + def test_addition + assert_equal Word.zero + Word.zero, Word.zero, '0 + 0 == 0' + assert_equal Word.one + Word.zero, Word.one, '1 + 0 == 1' + assert_equal Word.zero + Word.one, Word.one, '0 + 1 == 1' + assert_equal Word.h('1234') + Word.h('5678'), Word.h('68AC') + assert_equal Word.h('FFFF') + Word.h('FFFF'), Word.h('FFFE') + end + + def test_subtraction + assert_equal Word.zero - Word.zero, Word.zero, '0 - 0 == 0' + assert_equal Word.one - Word.zero, Word.one, '1 - 0 == 1' + assert_equal Word.one - Word.one, Word.zero, '1 - 1 == 0' + assert_equal Word.zero - Word.one, Word.h('FFFF') + assert_equal Word.h('5678') - Word.h('4321'), Word.h('1357') + assert_equal Word.h('4321') - Word.h('5678'), Word.h('ECA9') + end + + def test_division + assert_equal Word.one / Byte.one, Byte.one, '1 // 1 == 1' + assert_equal Word.zero / Byte.one, Byte.zero, '0 // 1 == 0' + assert_equal Word.h('4321') / Byte.h('56'), Byte.h('C7') + assert_equal Word.h('3A02') / Byte.h('A5'), Byte.h('5A') + assert_equal Word.h('FE01') / Byte.h('FF'), Byte.h('FF') + assert_equal Word.h('4321') / Byte.one, Byte.h('21') + end + + def test_modulo + assert_equal(Word.one % Byte.one, Byte.zero, '1 % 1 == 0') + assert_equal(Word.zero % Byte.one, Byte.zero, '0 % 1 == 0') + assert_equal(Word.h('4321') % Byte.h('56'), Byte.h('47')) + assert_equal(Word.h('3A02') % Byte.h('A5'), Byte.zero) + assert_equal(Word.h('4321') % Byte.one, Byte.zero) + assert_equal(Word.h('FEFF') % Byte.h('FF'), Byte.h('FE')) + end + + def test_and + assert_equal Word.one & Word.one, Word.one, '1 & 1 == 1' + assert_equal Word.one & Word.zero, Word.zero, '1 & 0 == 0' + assert_equal Word.zero & Word.zero, Word.zero, '0 & 0 == 0' + assert_equal Word.h('125F') & Word.h('346A'), Word.h('104A') + end + + def test_or + assert_equal Word.one | Word.one, Word.one, '1 | 1 == 1' + assert_equal Word.one | Word.zero, Word.one, '1 | 0 == 1' + assert_equal Word.zero | Word.zero, Word.zero, '0 | 0 == 0' + assert_equal Word.h('125F') | Word.h('346A'), Word.h('367F') + end + + def test_xor + assert_equal Word.one ^ Word.one, Word.zero, '1 ^ 1 == 0' + assert_equal Word.one ^ Word.zero, Word.one, '1 ^ 0 == 1' + assert_equal Word.zero ^ Word.zero, Word.zero, '0 ^ 0 == 0' + assert_equal Word.h('125F') ^ Word.h('346A'), Word.h('2635') + end +end # class WordTest diff --git a/5/code/rsa/ks_primitives_unchecked.pyc b/5/code/rsa/ks_primitives_unchecked.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59ce7051bf12422f2b4bded59c64de1c353b5c19 GIT binary patch literal 14805 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 4, 1] + Byte.hex_digits[value & 0xF, 1] + @word = nil + end + + # Private: string of valid hex characters + @@hex_digits = '0123456789ABCDEF' + + # Private: maps hexadecimal digit strings to nibbles + @@nibbles = {'0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, + '6' => 6, '7' => 7, '8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, + 'D' => 13, 'E' => 14, 'F' => 15} + + # Private: array of singleton Byte instances. + @@bytes = [] + + def self.hex_digits + @@hex_digits + end + + def self.nibbles + @@nibbles + end + + def self.bytes + @@bytes + end + + def self.bytes=(value) + @@bytes = value + end + + # A byte initialized to 0. + def self.zero + Byte.bytes[0] + end + + # A byte initialized to 1. + def self.one + Byte.bytes[1] + end + + # A byte initialized to the value in the given hexadecimal number. + # + # Args: + # string:: a 2-character string containing the hexadecimal digits 0-9, a-f, + # and/or A-F + def self.from_hex(hex_string) + #a byte initialized to the value in the given hexadecimal number + #args + # string: a 2-char string contaiting the hexadecmial digits 0-9, a-f, + # and/or A-F + # + + raise 'Invalid hexadecimal string' if hex_string.length != 2 + d1 = hex_string[0, 1] + d2 = hex_string[1, 1] + raise 'Invalid hexadecimal string' if !Byte.nibbles.has_key?(d1) || !Byte.nibbles.has_key?(d2) + Byte.bytes[(Byte.nibbles[d1] << 4) | Byte.nibbles[d2]] + end + + # Shorthand for from_hex(hex_string). + def self.h(hex_string) + Byte.from_hex hex_string + end + + # <=> for Bytes. + def <=>(other) + @byte <=> other.byte + end + + # Returns a Word with the result of adding 2 Bytes. + def +(other) + Word.words[(@byte + other.byte) & 0xFFFF] + end + + # Returns a Word with the result of subtracting 2 Bytes. + def -(other) + Word.words[(0x10000 + @byte - other.byte) & 0xFFFF] + end + + # Returns a Word with the result of multipling 2 Bytes. + def *(other) + Word.words[@byte * other.byte] + end + + # Returns a Byte with the division quotient of 2 Bytes. + def /(other) + self.word/other + end + + # Returns a Byte with the division remainder of 2 Bytes. + def %(other) + self.word() % other + end + + # Returns the logical AND of two Bytes. + def &(other) + Byte.bytes[@byte & other.byte] + end + + # Returns the logical OR of two Bytes. + def |(other) + Byte.bytes[@byte | other.byte] + end + + # Returns the logical XOR of two Bytes. + def ^(other) + Byte.bytes[@byte ^ other.byte] + end + + # Debugging help: returns the Byte formatted as "0x??". + def to_s + "0x#{self.hex}" + end + + # Debuging help: returns a Ruby expression that can create this Byte. + def inspect + "Byte.h('#{self.hex}')" + end +end # class Byte + +# A 16-bit digit. (base 65536) +class Word + include Comparable + attr_reader :lsb, :msb, :word, :hex + + # Do not call the Word constructor directly. + # Use Word.zero, Byte.one, Byte.from_hex instead + def initialize(value) + raise 'Do not call the Word constructor directly!' if Word.words.length == 0x100000 + @word = value + @lsb = Byte.bytes[@word & 0xFF] + @msb = Byte.bytes[@word >> 8] + @hex = self.msb.hex + self.lsb.hex + end + + # Private: array of singleton Word instances + @@words = [] + + def self.words + @@words + end + + def self.words=(value) + @@words = value + end + + # A word initialized to 0. + def self.zero + Word.words[0] + end + + # A word initialized to 1. + def self.one + Word.words[1] + end + + # A word initialized to the value of a Byte. + def self.from_byte(byte) + Word.words[byte.byte] + end + + # A word initialized from two Bytes. (msb:lsb) + def self.from_bytes(msb, lsb) + Word.words[(msb.byte << 8) | lsb.byte] + end + + # A word initialized to the value in the given hexadecimal number. + # Args: + # string:: a 2-char string containing the hexadecimal digits 0-9, a-f, + # and/or A-F + def self.from_hex(hex_string) + raise 'Invalid hexadecimal string' if hex_string.length != 4 + Word.from_bytes Byte.from_hex(hex_string[0, 2]), Byte.from_hex(hex_string[2,2]) + end + + def self.h(hex_string) + Word.from_hex hex_string + end + + # <=> for Words. + def <=>(other) + @word <=> other.word + end + + # Returns a Word with the result of adding 2 Words modulo 65,536 + def +(other) + Word.words[(@word + other.word) & 0xFFFF] + end + + # Returns a Word with the result of subtracting 2 Words modulo 65,536 + def -(other) + Word.words[(0x10000 + @word - other.word) & 0xFFFF] + end + + # Returns a Byte with the division quotient between this Word and a Byte. + def /(other) + Byte.bytes[(@word / other.byte) & 0xFF] + end + + # Returns a Byte with the division remainder between this Word and a Byte. + def %(other) + Byte.bytes[@word % other.byte] + end + + # Returns the logical AND of two Words. + def &(other) + Word.words[@word & other.word] + end + + # Returns the logical OR of two Words. + def |(other) + Word.words[@word | other.word] + end + + # Returns the logical XOR of two Words. + def ^(other) + Word.words[@word ^ other.word] + end + + # Debugging help: returns the Byte formatted as "0x????". + def to_s + "0x#{self.hex()}" + end + + # Debuging help: returns a Ruby expression that can create this Word. + def inspect + "Word.h('#{self.hex()}')" + end +end # class Word + + +# Private: initialize singleton Byte instances.s +0.upto(0xFF) { |i| Byte.bytes << Byte.new(i) } + +# Private: initialize singleton Word instances +0.upto(0xFFFF) { |i| Word.words << Word.new(i) } + +# Private: link Byte instances to their corresponding Words. +0.upto(0xFF) { |i| Byte.bytes[i].word = Word.words[i] } diff --git a/5/code/rsa/rsa.rb b/5/code/rsa/rsa.rb new file mode 100644 index 0000000..4cf1650 --- /dev/null +++ b/5/code/rsa/rsa.rb @@ -0,0 +1,148 @@ +#!/usr/bin/env ruby + +require 'rubygems' +require 'json' +require 'big_num' + +# Public or private RSA key. +class RsaKey + # Initializes a key from a public or private exponent and the modulus. + def initialize(exponent_hex_string, modulus_hex_string) + @e = BigNum.from_hex exponent_hex_string + @n = BigNum.from_hex modulus_hex_string + @size = (@n.to_hex.length + 1) /2 + @chunk_cache = {} + end + + # Performs ECB RSA encryption / decryption. + def raw_crypt(number) + number.powmod @e, @n + end + + # Decrypts a bunch of data stored as a hexadecimal string. + # Returns a hexadecimal string with the decrypted data. + def decrypt(hex_string) + out_chunks = [] + i = 0 + in_chunk_size = @size * 2 + out_chunk_size = (@size - 1) * 2 + while i < hex_string.size + in_chunk = hex_string[i, in_chunk_size] + if @chunk_cache.has_key? in_chunk + out_chunk = @chunk_cache[in_chunk] + else + out_chunk = self.raw_crypt(BigNum.from_hex(in_chunk)).to_hex + out_chunk = out_chunk[0, out_chunk_size] if out_chunk.size > out_chunk_size + @chunk_cache[in_chunk] = out_chunk + end + out_chunks << ('0' * (out_chunk_size - out_chunk.size)) if out_chunk.size < out_chunk_size + out_chunks << out_chunk + i += in_chunk_size + end + out_chunks.join '' + end +end # class RsaKey + +# Processes an image encrypted with an RSA key. +class EncryptedImage + attr_accessor:columns + + def initialize + @key = nil + @encrypted_rows = [] + @rows = nil + @columns = nil + end + + # Sets the RSA key to be used for decrypting the image. + def set_key(exponent_hex_string, modulus_hex_string) + @key = RsaKey.new exponent_hex_string, modulus_hex_string + end + + # Append a row of encrypted pixel data to the image. + def add_row(encrypted_row_data) + @encrypted_rows << encrypted_row_data + end + + # Decrypts the encrypted image. + def decrypt_image + #Decrypts the encrypted image + return if @rows + rows = [] + @encrypted_rows.each do |encrypted_row| + row = @key.decrypt encrypted_row + row_size = @columns * 6 + row = row[0, row_size] if row_size + row = row.upcase + rows << row + end + @rows = rows + end + + # Returns a list of strings representing the image data. + def to_line_list + self.decrypt_image + @rows + end + + # Writes a textual description of the image data to a file. + # Args: + # file:: A file object that receives the image data. + def to_file(file) + self.to_line_list.each { |line| file.puts("#{line}\n") } + end + + # A dict that obeys the JSON format, representing the image. + def as_json + self.decrypt_image + jso = {} + jso['image'] = + { + 'rows' => @rows.size, + 'cols' => @rows[0].size / 6, + 'data' => @rows + } + jso['encrypted'] = + { + 'data' => @encrypted_rows, + 'rows' => @rows.size, + 'cols' => @encrypted_rows[0].size / 6 + } + jso + end + + # Reads an encrypted image description from a file. + # Args: + # file:: A File object supplying the input. + # + # Returns a new RsaImageDecrypter instance. + def self.from_file(file) + image = EncryptedImage.new + loop do + command = file.gets.split + case command[0] + when 'end' then break + when 'key' then image.set_key command[1], command[2] + when 'sx' then image.columns = command[1].to_i + when 'row' then image.add_row command[1] + end + end + image + end +end # class EncryptedImage + +# Command-line controller. +class Cli + def run(args) + image = EncryptedImage.from_file STDIN + if ENV['TRACE'] == 'jsonp' + STDOUT.write 'onJsonp(' + JSON.dump image.as_json, STDOUT + STDOUT.write ");\n" + else + image.to_file STDOUT + end + end +end # class Cli + +Cli.new.run(ARGV) if __FILE__ == $0 diff --git a/5/code/rsa/rsa_test.rb b/5/code/rsa/rsa_test.rb new file mode 100644 index 0000000..bb501c5 --- /dev/null +++ b/5/code/rsa/rsa_test.rb @@ -0,0 +1,39 @@ +require 'test/unit' +require 'rsa' + +class RSATest < Test::Unit::TestCase + def setup + basedir = '.' + @in_files = Dir.glob './tests/*in' + @in_files.sort() + end + + def cmp_files(file, lines) + lines.each do |line| + if file.gets.strip() != line + return false + end + end + return file.gets == nil + end + + define_method(:test_a_file) do |in_filename| + test_name = File.basename in_filename + STDOUT.write "Testing #{test_name}......." + STDOUT.flush + in_file = File.open in_filename + image = EncryptedImage.from_file in_file + out_lines = image.to_line_list + gold_filename = in_filename.sub "in", "gold" + gold_file = File.open gold_filename + same = self.cmp_files gold_file, out_lines + STDOUT.write same ? "OK\n" : "Failed\n" + assert same + end + + def test_all_filse + @in_files.each do |in_filename| + self.test_a_file(in_filename) + end + end +end # class RSATest