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:
Dan Milne
2011-11-24 15:43:47 +11:00
parent 1d899d0b8a
commit 3c620201c6
10 changed files with 289 additions and 71 deletions

View File

@@ -1,5 +0,0 @@
lib/**/*.rb
bin/*
-
features/**/*.feature
LICENSE.txt

1
.rspec
View File

@@ -1 +0,0 @@
--color

View File

@@ -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

View File

@@ -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)

8
Guardfile Normal file
View 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
View 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

View File

@@ -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

View File

@@ -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

137
test/hsmr_test.rb Normal file
View 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
View File

@@ -0,0 +1,4 @@
require "test/unit"
require "./lib/hsmr"
require "./lib/key"
require "./lib/component"