diff --git a/.document b/.document deleted file mode 100644 index 3d618dd..0000000 --- a/.document +++ /dev/null @@ -1,5 +0,0 @@ -lib/**/*.rb -bin/* -- -features/**/*.feature -LICENSE.txt diff --git a/.rspec b/.rspec deleted file mode 100644 index 4e1e0d2..0000000 --- a/.rspec +++ /dev/null @@ -1 +0,0 @@ ---color diff --git a/Gemfile b/Gemfile index 9d9ab10..e909fb0 100644 --- a/Gemfile +++ b/Gemfile @@ -10,4 +10,8 @@ group :development do gem "bundler", "~> 1.0.0" gem "jeweler", "~> 1.5.2" gem "rcov", ">= 0" + gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i + gem 'growl', :require => false if RUBY_PLATFORM =~ /darwin/i + gem 'guard-test' + gem 'factory_girl' end diff --git a/Gemfile.lock b/Gemfile.lock index f8dbcc9..d212e4f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,13 +1,25 @@ GEM remote: http://rubygems.org/ specs: + activesupport (3.1.1) + multi_json (~> 1.0) diff-lcs (1.1.2) + factory_girl (2.2.0) + activesupport git (1.2.5) + growl (1.0.3) + guard (0.8.8) + thor (~> 0.14.6) + guard-test (0.4.1) + guard (>= 0.4) + test-unit (~> 2.2) jeweler (1.5.2) bundler (~> 1.0.0) git (>= 1.2.5) rake + multi_json (1.0.3) rake (0.8.7) + rb-fsevent (0.4.3.1) rcov (0.9.9) rspec (2.3.0) rspec-core (~> 2.3.0) @@ -17,12 +29,18 @@ GEM rspec-expectations (2.3.0) diff-lcs (~> 1.1.2) rspec-mocks (2.3.0) + test-unit (2.4.1) + thor (0.14.6) PLATFORMS ruby DEPENDENCIES bundler (~> 1.0.0) + factory_girl + growl + guard-test jeweler (~> 1.5.2) + rb-fsevent rcov rspec (~> 2.3.0) diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000..9c489be --- /dev/null +++ b/Guardfile @@ -0,0 +1,8 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +guard :test do + watch(%r{lib/(.+)\.rb}) { |m| "test/#{m[1]}_test.rb" } + watch(%r{test/.+_test\.rb}) + watch('test/test_helper.rb') { "test" } +end diff --git a/lib/component.rb b/lib/component.rb new file mode 100644 index 0000000..ca3cc63 --- /dev/null +++ b/lib/component.rb @@ -0,0 +1,36 @@ +module HSMR + class Component + include HSMR + attr_reader :key + attr_reader :length + attr_reader :parity + + def component + @key + end + + def initialize(key=nil, length=DOUBLE) + ## Should check for odd parity + if key.nil? + key = generate(length) + else + key=key.gsub(/ /,'') + #raise TypeError, "Component argument expected" unless other.is_a? Component + end + @key = key.unpack('a2'*(key.length/2)).map{|x| x.hex}.pack('c'*(key.length/2)) + @length = @key.length + @key = @key + end + + def xor(other) + other = Component.new(other) unless other.is_a? Component + raise TypeError, "Component argument expected" unless other.is_a? Component + + @a = @key.unpack('C2'*(@key.length/2)) + @b = other.component.unpack('C2'*(other.component.length/2)) + result = @a.zip(@b).map {|x,y| x^y}.map {|z| z.to_s(16) }.join.upcase + Key.new(result) + end + + end +end diff --git a/lib/hsmr.rb b/lib/hsmr.rb index fee3723..b7a00f9 100644 --- a/lib/hsmr.rb +++ b/lib/hsmr.rb @@ -8,6 +8,81 @@ module HSMR DOUBLE=128 TRIPLE=192 + ## Mixin functionality + + def kcv() + des = OpenSSL::Cipher::Cipher.new("des-cbc") if @key.length == 8 + des = OpenSSL::Cipher::Cipher.new("des-ede-cbc") if @key.length == 16 + des.encrypt + des.key=@key + des.update("\x00"*8).unpack('H*').first[0...6].upcase + end + + def generate(length) + (0...(length/4)).collect { rand(16).to_s(16).upcase }.join + end + + + def to_s + @key.unpack('H4'*(@key.length/2)).join(" ").upcase + end + + def odd_parity? + # http://csrc.nist.gov/publications/nistpubs/800-67/SP800-67.pdf + # + # The eight error detecting bits are set to make the parity of each 8-bit + # byte of the key odd. That is, there is an odd number of "1"s in each 8-bit byte. + + #3.to_s(2).count('1') + #@key.unpack("H2").first.to_i(16).to_s(2) + + working=@key.unpack('H2'*(@key.length)) + working.each do |o| + freq = o.to_i(16).to_s(2).count('1').to_i + if( freq%2 == 0) + #puts "#{o} is #{o.to_i(16).to_s(2).count('1').to_i } - even" + return false + else + return true + #puts "#{o} is #{o.to_i(16).to_s(2).count('1').to_i } - odd" + end + end + end + + def set_odd_parity + return true if self.odd_parity? == true + + working=@key.unpack('H2'*(@key.length)) + working.each_with_index do |o,i| + freq = o.to_i(16).to_s(2).count('1').to_i + if( freq%2 == 0) + c1 = o[0].chr + c2 = case o[1].chr + when "0" then "1" + when "1" then "0" + when "2" then "3" + when "3" then "2" + when "4" then "5" + when "5" then "4" + when "6" then "7" + when "7" then "6" + when "8" then "9" + when "9" then "8" + when "a" then "b" + when "b" then "a" + when "c" then "d" + when "d" then "c" + when "e" then "f" + when "f" then "e" + end + working[i]="#{c1}#{c2}" + end + end + @key = working.join.unpack('a2'*(working.length)).map{|x| x.hex}.pack('c'*(working.length)) + end + + ## Module Methods + def self.encrypt_pin(key, pin) @pin = pin.unpack('a2'*(pin.length/2)).map{|x| x.hex}.pack('c'*(pin.length/2)) des = OpenSSL::Cipher::Cipher.new("des-ede") @@ -74,6 +149,10 @@ module HSMR result = des.update(@tsp).unpack('H*').first.upcase decimalise(result, :visa)[0..3].join end + + def self.cvv(key_left, key_right, account, exp, service_code) + + end def self.xor(component1, *rest) return if rest.length == 0 diff --git a/lib/key.rb b/lib/key.rb index 752ff02..56ca49d 100644 --- a/lib/key.rb +++ b/lib/key.rb @@ -1,5 +1,7 @@ module HSMR class Key + include HSMR + attr_reader :key attr_reader :length attr_reader :parity @@ -22,20 +24,12 @@ module HSMR key=init.gsub(/ /,'') @key = key.unpack('a2'*(key.length/2)).map{|x| x.hex}.pack('c'*(key.length/2)) elsif key.nil? - key = (0...(length/4)).collect { rand(16).to_s(16).upcase }.join + key = generate(length) @key = key.unpack('a2'*(key.length/2)).map{|x| x.hex}.pack('c'*(key.length/2)) end @length = @key.length end - def kcv() - des = OpenSSL::Cipher::Cipher.new("des-cbc") if @key.length == 8 - des = OpenSSL::Cipher::Cipher.new("des-ede-cbc") if @key.length == 16 - des.encrypt - des.key=@key - des.update("\x00"*8).unpack('H*').first[0...6].upcase - end - def xor(other) other=Component.new(other) if other.is_a? String other=Component.new(other.key) if other.is_a? Key @@ -52,61 +46,5 @@ module HSMR @key = xor(_key).key end - def odd_parity? - # http://csrc.nist.gov/publications/nistpubs/800-67/SP800-67.pdf - # - # The eight error detecting bits are set to make the parity of each 8-bit - # byte of the key odd. That is, there is an odd number of "1"s in each 8-bit byte. - - #3.to_s(2).count('1') - #@key.unpack("H2").first.to_i(16).to_s(2) - - working=@key.unpack('H2'*(@key.length)) - working.each do |o| - freq = o.to_i(16).to_s(2).count('1').to_i - if( freq%2 == 0) - #puts "#{o} is #{o.to_i(16).to_s(2).count('1').to_i } - even" - return false - else - return true - #puts "#{o} is #{o.to_i(16).to_s(2).count('1').to_i } - odd" - end - end - end - def set_odd_parity - return true if self.odd_parity? == true - - working=@key.unpack('H2'*(@key.length)) - working.each_with_index do |o,i| - freq = o.to_i(16).to_s(2).count('1').to_i - if( freq%2 == 0) - c1 = o[0].chr - c2 = case o[1].chr - when "0" then "1" - when "1" then "0" - when "2" then "3" - when "3" then "2" - when "4" then "5" - when "5" then "4" - when "6" then "7" - when "7" then "6" - when "8" then "9" - when "9" then "8" - when "a" then "b" - when "b" then "a" - when "c" then "d" - when "d" then "c" - when "e" then "f" - when "f" then "e" - end - working[i]="#{c1}#{c2}" - end - end - @key = working.join.unpack('a2'*(working.length)).map{|x| x.hex}.pack('c'*(working.length)) - end - - def to_s - @key.unpack('H4 '* (@length/2) ).join(" ").upcase - end end end diff --git a/test/hsmr_test.rb b/test/hsmr_test.rb new file mode 100644 index 0000000..f88cc89 --- /dev/null +++ b/test/hsmr_test.rb @@ -0,0 +1,137 @@ +require 'test_helper' + +class TestHSMR < Test::Unit::TestCase + test "generate a component " do + component_1 = HSMR::Component.new(nil, HSMR::SINGLE) + component_2 = HSMR::Component.new(nil, HSMR::DOUBLE) + component_3 = HSMR::Component.new(nil, HSMR::TRIPLE) + + assert_equal 8, component_1.length + assert_equal 16, component_2.length + assert_equal 24, component_3.length + end + + test "caclulation of single length component KCV values" do + component_1 = HSMR::Component.new("6DBF C180 4A01 5BAD") + assert_equal "029E60", component_1.kcv + + component_2 = HSMR::Component.new("5D80 0497 B319 8591") + assert_equal "3B86C3", component_2.kcv + + component_3 = HSMR::Component.new("B0C7 7CDC 7354 97C7") + assert_equal "7A77BC", component_3.kcv + + component_4 = HSMR::Component.new("DAE0 86FE D6EA 0BEA") + assert_equal "2E6191", component_4.kcv + + component_5 = HSMR::Component.new("682C 8315 F4BF FBC1") + assert_equal "62B336", component_5.kcv + + component_6 = HSMR::Component.new("5715 F289 04BC B62F") + assert_equal "3CBA88", component_6.kcv + + component_7 = HSMR::Component.new("D0C4 29AE C4A8 02B5") + assert_equal "33AF02", component_7.kcv + + component_8 = HSMR::Component.new("7049 D0F7 4A97 15B6") + assert_equal "AC1399", component_8.kcv + + component_9 = HSMR::Component.new("BC91 D698 157A A4E5") + assert_equal "295491", component_9.kcv + + component_10 = HSMR::Component.new("64AB 8568 7A0E 322F") + assert_equal "D9F7B3", component_10.kcv + end + + test "should calculate double length key KCV values" do + key_1 = HSMR::Key.new("ADE3 9B38 0DBC DF38 AE02 AECE 64B3 4373") + assert_equal "3002D5", key_1.kcv + + key_2 = HSMR::Key.new("B64A EF86 460D DF5B 57B3 D53D AD37 52A1") + assert_equal "1F7C07", key_2.kcv + + key_3 = HSMR::Key.new("5B89 6E29 76EC 9745 15B5 238C 8CFE 3D23") + assert_equal "DE78F2", key_3.kcv + + key_4 = HSMR::Key.new("5E61 CB20 D540 1FC7 58EC CDC8 B558 E9B9") + assert_equal "FED957", key_4.kcv + + key_5 = HSMR::Key.new("23DF CEB9 BF94 ADA8 91E9 580B 8C8F 5BEF") + assert_equal "902085", key_5.kcv + + key_6 = HSMR::Key.new("DFEF 61C8 2037 3DA4 CE9B 92CD 89E9 B334") + assert_equal "E45EB7", key_6.kcv + + key_7 = HSMR::Key.new("6746 9E4C FE83 F831 F23E 9D9E 9D9E 9DB3") + assert_equal "813B7B", key_7.kcv + + key_8 = HSMR::Key.new("23E5 496E DF94 0BD5 9734 B07A BF26 B9E6") + assert_equal "E7C48F", key_8.kcv + + key_9 = HSMR::Key.new("974F 26BC CB2A ECD5 434F 1CDC 64DF A275") + assert_equal "E27250", key_9.kcv + + key_10 = HSMR::Key.new("E57A DF5B CEA7 F42A DFD9 E554 07A2 F891") + assert_equal "50E3F8", key_10.kcv + end + + test "should detect odd_parity in a key" do + odd_key = HSMR::Key.new("41A2AC14A90C583741A2AC14A90C5837") + assert_false odd_key.odd_parity? + end + + test "should set double length key parity to odd" do + odd_key = HSMR::Key.new("41A2AC14A90C583741A2AC14A90C5837") + + odd_key.set_odd_parity + + even_key = HSMR::Key.new("40A2AD15A80D583740A2AD15A80D5837") + + assert_equal odd_key.key, even_key.key + end + + test "should detect odd_parity in a component" do + odd_component = HSMR::Component.new("41A2AC14A90C583741A2AC14A90C5837") + assert_false odd_component.odd_parity? + end + + test "should set double length component parity to odd" do + odd_component = HSMR::Component.new("41A2AC14A90C583741A2AC14A90C5837") + + odd_component.set_odd_parity + + even_component = HSMR::Component.new("40A2AD15A80D583740A2AD15A80D5837") + + assert_equal odd_component.component, even_component.component + end + + test "Converting string to ascii works" do + key_string = "E57A DF5B CEA7 F42A DFD9 E554 07A2 F891" + key = HSMR::Key.new(key_string) + + assert_equal key.to_s, key_string + + key_string = "E57A DF5B CEA7 F42A DFD9 E554 07A2 F891" + comp = HSMR::Component.new(key_string) + + assert_equal comp.to_s, key_string + end + + test "tests CVC / CVC2 calculations" do + # Component 1 Component 2 PAN EXP SCode CVC + # 1234567890ABCDEF FEDCBA1234567890 5656565656565656 1010 ___ 922 + # 1234567890ABCDEF FEDCBA1234567890 5656565656565656 1010 000 922 + # 1234567890ABCDEF FEDCBA1234567890 5683739237489383838 1010 000 367 + # 1234567890ABCDEF FEDCBA1234567890 568367393472639 1010 000 067 + # 1234567890ABCDEF FEDCBA1234567890 5683673934726394 1010 000 409 + # 1234567890ABCDEF FEDCBA1234567890 5683673934726394 1010 050 CVV248 or CVC409 + # 1234567890ABCDEF FEDCBA1234567890 5683673934726394 1010 101 CVV501 or CVC409 + # 1234567890ABCDEF FEDCBA1234567890 5683673934726394 1010 102 CVV206 or CVC409 + + kl = "0123456789ABCDEF" + kr = "FEDCBA1234567890" + + HSMR.cvv(kl, kr, "4509494222049051", "0907", "1010") + end + +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..3cf9a52 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,4 @@ +require "test/unit" +require "./lib/hsmr" +require "./lib/key" +require "./lib/component"