mirror of
https://github.com/dkam/hsmr.git
synced 2025-12-28 08:44:53 +00:00
Migrate to test::unit. Start moving functionality into HSMR Module and mixin to key and component models to reduce duplication.
This commit is contained in:
4
Gemfile
4
Gemfile
@@ -10,4 +10,8 @@ group :development do
|
|||||||
gem "bundler", "~> 1.0.0"
|
gem "bundler", "~> 1.0.0"
|
||||||
gem "jeweler", "~> 1.5.2"
|
gem "jeweler", "~> 1.5.2"
|
||||||
gem "rcov", ">= 0"
|
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
|
end
|
||||||
|
|||||||
18
Gemfile.lock
18
Gemfile.lock
@@ -1,13 +1,25 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: http://rubygems.org/
|
remote: http://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
|
activesupport (3.1.1)
|
||||||
|
multi_json (~> 1.0)
|
||||||
diff-lcs (1.1.2)
|
diff-lcs (1.1.2)
|
||||||
|
factory_girl (2.2.0)
|
||||||
|
activesupport
|
||||||
git (1.2.5)
|
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)
|
jeweler (1.5.2)
|
||||||
bundler (~> 1.0.0)
|
bundler (~> 1.0.0)
|
||||||
git (>= 1.2.5)
|
git (>= 1.2.5)
|
||||||
rake
|
rake
|
||||||
|
multi_json (1.0.3)
|
||||||
rake (0.8.7)
|
rake (0.8.7)
|
||||||
|
rb-fsevent (0.4.3.1)
|
||||||
rcov (0.9.9)
|
rcov (0.9.9)
|
||||||
rspec (2.3.0)
|
rspec (2.3.0)
|
||||||
rspec-core (~> 2.3.0)
|
rspec-core (~> 2.3.0)
|
||||||
@@ -17,12 +29,18 @@ GEM
|
|||||||
rspec-expectations (2.3.0)
|
rspec-expectations (2.3.0)
|
||||||
diff-lcs (~> 1.1.2)
|
diff-lcs (~> 1.1.2)
|
||||||
rspec-mocks (2.3.0)
|
rspec-mocks (2.3.0)
|
||||||
|
test-unit (2.4.1)
|
||||||
|
thor (0.14.6)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
bundler (~> 1.0.0)
|
bundler (~> 1.0.0)
|
||||||
|
factory_girl
|
||||||
|
growl
|
||||||
|
guard-test
|
||||||
jeweler (~> 1.5.2)
|
jeweler (~> 1.5.2)
|
||||||
|
rb-fsevent
|
||||||
rcov
|
rcov
|
||||||
rspec (~> 2.3.0)
|
rspec (~> 2.3.0)
|
||||||
|
|||||||
8
Guardfile
Normal file
8
Guardfile
Normal file
@@ -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
|
||||||
36
lib/component.rb
Normal file
36
lib/component.rb
Normal file
@@ -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
|
||||||
79
lib/hsmr.rb
79
lib/hsmr.rb
@@ -8,6 +8,81 @@ module HSMR
|
|||||||
DOUBLE=128
|
DOUBLE=128
|
||||||
TRIPLE=192
|
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)
|
def self.encrypt_pin(key, pin)
|
||||||
@pin = pin.unpack('a2'*(pin.length/2)).map{|x| x.hex}.pack('c'*(pin.length/2))
|
@pin = pin.unpack('a2'*(pin.length/2)).map{|x| x.hex}.pack('c'*(pin.length/2))
|
||||||
des = OpenSSL::Cipher::Cipher.new("des-ede")
|
des = OpenSSL::Cipher::Cipher.new("des-ede")
|
||||||
@@ -75,6 +150,10 @@ module HSMR
|
|||||||
decimalise(result, :visa)[0..3].join
|
decimalise(result, :visa)[0..3].join
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.cvv(key_left, key_right, account, exp, service_code)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
def self.xor(component1, *rest)
|
def self.xor(component1, *rest)
|
||||||
return if rest.length == 0
|
return if rest.length == 0
|
||||||
|
|
||||||
|
|||||||
68
lib/key.rb
68
lib/key.rb
@@ -1,5 +1,7 @@
|
|||||||
module HSMR
|
module HSMR
|
||||||
class Key
|
class Key
|
||||||
|
include HSMR
|
||||||
|
|
||||||
attr_reader :key
|
attr_reader :key
|
||||||
attr_reader :length
|
attr_reader :length
|
||||||
attr_reader :parity
|
attr_reader :parity
|
||||||
@@ -22,20 +24,12 @@ module HSMR
|
|||||||
key=init.gsub(/ /,'')
|
key=init.gsub(/ /,'')
|
||||||
@key = key.unpack('a2'*(key.length/2)).map{|x| x.hex}.pack('c'*(key.length/2))
|
@key = key.unpack('a2'*(key.length/2)).map{|x| x.hex}.pack('c'*(key.length/2))
|
||||||
elsif key.nil?
|
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))
|
@key = key.unpack('a2'*(key.length/2)).map{|x| x.hex}.pack('c'*(key.length/2))
|
||||||
end
|
end
|
||||||
@length = @key.length
|
@length = @key.length
|
||||||
end
|
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)
|
def xor(other)
|
||||||
other=Component.new(other) if other.is_a? String
|
other=Component.new(other) if other.is_a? String
|
||||||
other=Component.new(other.key) if other.is_a? Key
|
other=Component.new(other.key) if other.is_a? Key
|
||||||
@@ -52,61 +46,5 @@ module HSMR
|
|||||||
@key = xor(_key).key
|
@key = xor(_key).key
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
137
test/hsmr_test.rb
Normal file
137
test/hsmr_test.rb
Normal file
@@ -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
|
||||||
4
test/test_helper.rb
Normal file
4
test/test_helper.rb
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
require "test/unit"
|
||||||
|
require "./lib/hsmr"
|
||||||
|
require "./lib/key"
|
||||||
|
require "./lib/component"
|
||||||
Reference in New Issue
Block a user