hsmr 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,63 @@
1
+ #Encrypt (ECB) 1234000000000000
2
+ #Key Used Was 0123456789ABCDEFFEDCBA9876543210 (08D7B4) Odd
3
+ #The Result Is D4718F4CA902C2A3
4
+
5
+
6
+ key="0123456789ABCDEFFEDCBA9876543210"
7
+ pin="1234000000000000"
8
+
9
+ def encpin(key, pin)
10
+ @key = key.unpack('a2'*(key.length/2)).map{|x| x.hex}.pack('c'*(key.length/2))
11
+ @pin = pin.unpack('a2'*(pin.length/2)).map{|x| x.hex}.pack('c'*(pin.length/2))
12
+ des = OpenSSL::Cipher::Cipher.new("des-ede")
13
+ des.encrypt
14
+ des.key=@key
15
+ return des.update(@pin).unpack('H*').first.upcase
16
+ end
17
+
18
+ def pvv(key, tsp)
19
+ @key = key.unpack('a2'*(key.length/2)).map{|x| x.hex}.pack('c'*(key.length/2))
20
+ @tsp = tsp.unpack('a2'*(tsp.length/2)).map{|x| x.hex}.pack('c'*(tsp.length/2))
21
+ des = OpenSSL::Cipher::Cipher.new("des-ede")
22
+ des.encrypt
23
+ des.key=@key
24
+ result = des.update(@pin).unpack('H*').first.upcase
25
+ end
26
+
27
+
28
+ pin=[]
29
+ pin[0]="1234000000000000"
30
+ pin[1]="8881000000000000"
31
+ pin[2]="9254000000000000"
32
+ pin[3]="1927000000000000"
33
+ pin[4]="8363000000000000"
34
+
35
+ key=[]
36
+ key[0]="0123456789ABCDEFFEDCBA9876543210"
37
+ key[1]="BA942A01EC6EABCD107346CB61F4F4FE"
38
+ key[2]="4A3DB34F255EA743ECDA19D0945ECB31"
39
+
40
+ puts " result | key | pin"
41
+ key.each do |k|
42
+ pin.each do |p|
43
+ result = encpin(k,p)
44
+ puts "#{result} | #{k} | #{p}"
45
+ end
46
+ end
47
+
48
+ RESULT | KEY | PIN
49
+ D4718F4CA902C2A3 | 0123456789ABCDEFFEDCBA9876543210 | 1234000000000000
50
+ 9EE1F2989B005F6A | 0123456789ABCDEFFEDCBA9876543210 | 8881000000000000
51
+ 30D7E71ADB09B2F6 | 0123456789ABCDEFFEDCBA9876543210 | 9254000000000000
52
+ F7DE2452CC64FC6D | 0123456789ABCDEFFEDCBA9876543210 | 1927000000000000
53
+ A947B12F1E7F345A | 0123456789ABCDEFFEDCBA9876543210 | 8363000000000000
54
+ 5DFA5CE9EDCF20D9 | BA942A01EC6EABCD107346CB61F4F4FE | 1234000000000000
55
+ 9E22FC9F1539BF54 | BA942A01EC6EABCD107346CB61F4F4FE | 8881000000000000
56
+ B30A6DE41326C0FE | BA942A01EC6EABCD107346CB61F4F4FE | 9254000000000000
57
+ E905C2F4E94638B7 | BA942A01EC6EABCD107346CB61F4F4FE | 1927000000000000
58
+ FE18700A5357D343 | BA942A01EC6EABCD107346CB61F4F4FE | 8363000000000000
59
+ 77B3EAD1F9CEAB4E | 4A3DB34F255EA743ECDA19D0945ECB31 | 1234000000000000
60
+ BF0E37A91DA9D878 | 4A3DB34F255EA743ECDA19D0945ECB31 | 8881000000000000
61
+ 43A8D24E4DAC0D5C | 4A3DB34F255EA743ECDA19D0945ECB31 | 9254000000000000
62
+ 4D367C663AB681F1 | 4A3DB34F255EA743ECDA19D0945ECB31 | 1927000000000000
63
+ 33D63EC052648F5B | 4A3DB34F255EA743ECDA19D0945ECB31 | 8363000000000000
data/doc/parity.txt ADDED
@@ -0,0 +1,14 @@
1
+ 41A2AC14A90C583741A2AC14A90C5837
2
+ 40A2AD15A80D583740A2AD15A80D5837
3
+
4
+ F2AEDAE3FEE90DC2921F01341F54D37F
5
+ F2AEDAE3FEE90DC2921F01341F54D37F
6
+
7
+ 80EFEC4895855B06B4015041C3F9F61F
8
+ 80EFEC4994855B07B5015140C2F8F71F
9
+
10
+ 4D3DE6AA837AA60A413DDD6CBCB12C82
11
+ 4C3DE6AB837AA70B403DDC6DBCB02C83
12
+
13
+ 1E74C6914F41E5EFD9969A0B134EF819
14
+ 1F75C7914F40E5EFD9979B0B134FF819
@@ -0,0 +1,24 @@
1
+ K = 2323 2323 2323 2323 4545 4545 4545 4545
2
+ KVC = 3A42D7
3
+
4
+ K = 4545 4545 4545 4545 2323 2323 2323 2323
5
+ KVC = AC5700
6
+
7
+ K = 6767 6767 6767 6767 6767 6767 6767 6767
8
+ KVC = B0B563
9
+
10
+ Card: 1234123412341234
11
+ KL: 0123456789ABCDEF
12
+ KR: 0123456789ABCDEF
13
+
14
+ PVKI: 1
15
+ PIN : 1234
16
+ PVV : 8056
17
+
18
+ PVKI: 2
19
+ PIN : 1234
20
+ PVV : 2485
21
+
22
+ PVKI: 1
23
+ PIN : 4592
24
+ PVV : 6495
data/hsmr.gemspec CHANGED
@@ -7,22 +7,17 @@ Gem::Specification.new do |s|
7
7
  s.version = HSMR::VERSION
8
8
  s.authors = ["Dan Milne"]
9
9
  s.email = ["d@nmilne.com"]
10
- s.homepage = ""
11
10
  s.homepage = 'https://github.com/dkam/hsmr'
12
11
  s.summary = %q{HSM commands in Ruby}
13
12
  s.description = %q{A collection of methods usually implemented in a HSM (Hardware Security Module)}
14
13
  s.license = 'MIT'
14
+ s.required_ruby_version = '>= 3.1'
15
15
 
16
16
  s.files = `git ls-files`.split("\n")
17
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
20
 
21
- # specify any dependencies here; for example:
22
- # s.add_development_dependency "rspec"
23
- # s.add_runtime_dependency "rest-client"
24
-
25
21
  s.add_development_dependency "rake", '~> 13'
26
- s.add_development_dependency "guard-test", "~> 2"
27
- s.add_development_dependency "factory_bot", "~> 6"
22
+ s.add_development_dependency "minitest", "~> 5.25"
28
23
  end
@@ -1,38 +1,26 @@
1
1
  module HSMR
2
2
  class Component
3
3
  include HSMR
4
+
4
5
  attr_reader :key
5
6
  attr_reader :length
6
7
 
7
8
  def component
8
9
  @key
9
10
  end
10
-
11
+
11
12
  def initialize(key=nil, length=DOUBLE)
12
13
  ## Should check for odd parity
13
14
  if key.nil?
14
15
  key = generate(length)
16
+ elsif key.is_a? String
17
+ key = key.gsub(/ /,'')
15
18
  else
16
- key=key.gsub(/ /,'')
17
- #raise TypeError, "Component argument expected" unless other.is_a? Component
19
+ raise TypeError, "expected a String or nil, got #{key.class}"
18
20
  end
21
+
19
22
  @key = key.unpack('a2'*(key.length/2)).map{|x| x.hex}.pack('c'*(key.length/2))
20
23
  @length = @key.length
21
- @key = @key
22
24
  end
23
-
24
- # def xor(other)
25
- # other = Component.new(other) unless other.is_a? Component
26
- # raise TypeError, "Component argument expected" unless other.is_a? Component
27
- #
28
- # @a = @key.unpack('C2'*(@key.length/2))
29
- # @b = other.component.unpack('C2'*(other.component.length/2))
30
- #
31
- # #result = @a.zip(@b).map {|x,y| x^y}.map {|z| z.to_s(16) }.join.upcase
32
- # result = @a.zip(@b).map {|x,y| x^y}.map {|z| z.to_s(16) }.map {|c| c.length == 1 ? '0'+c : c }.join.upcase
33
- #
34
- # Key.new(result)
35
- # end
36
-
37
25
  end
38
26
  end
data/lib/hsmr/key.rb CHANGED
@@ -6,51 +6,33 @@ module HSMR
6
6
  attr_reader :length
7
7
 
8
8
  def initialize(init=nil, length=DOUBLE)
9
- return nil if (init.is_a? Array ) && (init.length == 0)
10
-
11
- init = init.first if (init.is_a? Array) && (init.length == 1)
12
-
13
- if init.is_a? Array
14
- init.collect! {|c| ( (c.is_a? HSMR::Component) ? c : HSMR::Component.new(c) ) }
9
+ raise ArgumentError, "at least one component is required" if init.is_a?(Array) && init.empty?
15
10
 
16
- raise TypeError, "Component argument expected" unless init.first.is_a? Component
17
-
18
- @key=HSMR::xor(init.pop, init).key
19
-
20
- elsif init.is_a? Component
11
+ # A single element array is just that element
12
+ init = init.first if init.is_a?(Array) && init.length == 1
13
+
14
+ case init
15
+ when Array
16
+ components = init.collect {|c| c.is_a?(HSMR::Component) ? c : HSMR::Component.new(c) }
17
+ @key = HSMR::xor(components.pop, *components).key
18
+ when Component
21
19
  @key = init.component
22
- elsif init.is_a? String
23
- key=init.gsub(/ /,'')
24
- @key = key.unpack('a2'*(key.length/2)).map{|x| x.hex}.pack('c'*(key.length/2))
25
- elsif key.nil?
26
- key = generate(length)
27
- @key = key.unpack('a2'*(key.length/2)).map{|x| x.hex}.pack('c'*(key.length/2))
20
+ when String
21
+ @key = from_hex(init)
22
+ when nil
23
+ @key = from_hex(generate(length))
24
+ else
25
+ raise TypeError, "expected a String, Array, Component or nil, got #{init.class}"
28
26
  end
27
+
29
28
  @length = @key.length
30
- end
29
+ end
31
30
 
32
- #def xor(other)
33
- # other=Component.new(other) if other.is_a? String
34
- # other=Component.new(other.to_s) if other.is_a? Key
35
- #
36
- # puts "other is #{other.class} - #{other.key}"
37
- #
38
- # raise TypeError, "Component argument expected" unless other.is_a? Component
39
- #
40
- # @a = @key.unpack('C2'*(@key.length/2))
41
- # @b = other.component.unpack('C2'*(other.length/2))
42
- #
43
- # resultant = Key.new( @a.zip(@b).
44
- # map {|x,y| x^y}.
45
- # map {|z| z.to_s(16) }.
46
- # map {|c| c.length == 1 ? '0'+c : c }.
47
- # join.upcase )
48
- # resultant
49
- # end
50
- #
51
- # def xor!(_key)
52
- # @key = xor(_key).key
53
- # end
31
+ private
54
32
 
33
+ def from_hex(hex)
34
+ hex = hex.gsub(/ /,'')
35
+ hex.unpack('a2'*(hex.length/2)).map{|x| x.hex}.pack('c'*(hex.length/2))
36
+ end
55
37
  end
56
38
  end
data/lib/hsmr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module HSMR
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/hsmr.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'openssl'
2
+ require 'securerandom'
2
3
  require 'hsmr/component'
3
4
  require 'hsmr/key'
4
5
 
@@ -8,18 +9,36 @@ module HSMR
8
9
  DOUBLE=128
9
10
  TRIPLE=192
10
11
 
12
+ # Returns a DES cipher, keyed and set to :encrypt or :decrypt, in :cbc (the
13
+ # default) or :ecb mode.
14
+ #
15
+ # Single DES ("des-cbc" / "des-ecb") moved into OpenSSL 3's legacy provider
16
+ # and is not available by default. Triple DES with K1=K2=K3 is identical to
17
+ # single DES, so single length keys are widened to 24 bytes and run through
18
+ # des-ede3 instead.
19
+ def self.des(key, direction, mode = :cbc)
20
+ algorithm, material = case key.length
21
+ when 8 then ["des-ede3", key * 3]
22
+ when 16 then ["des-ede", key]
23
+ when 24 then ["des-ede3", key]
24
+ else raise ArgumentError, "key length should be 8, 16 or 24 bytes, got #{key.length}"
25
+ end
26
+
27
+ des = OpenSSL::Cipher.new(mode == :cbc ? "#{algorithm}-cbc" : algorithm)
28
+ direction == :decrypt ? des.decrypt : des.encrypt
29
+ des.key = material
30
+ des
31
+ end
32
+
11
33
  ## Mixin functionality
12
34
 
13
35
  def kcv()
14
- des = OpenSSL::Cipher.new("des-cbc") if @key.length == 8
15
- des = OpenSSL::Cipher.new("des-ede-cbc") if @key.length == 16
16
- des.encrypt
17
- des.key=@key
36
+ des = HSMR.des(@key, :encrypt)
18
37
  des.update("\x00"*8).unpack('H*').first[0...6].upcase
19
38
  end
20
39
 
21
40
  def generate(length)
22
- (0...(length/4)).collect { rand(16).to_s(16).upcase }.join
41
+ SecureRandom.hex(length / 8).upcase
23
42
  end
24
43
 
25
44
 
@@ -28,43 +47,25 @@ module HSMR
28
47
  end
29
48
 
30
49
  def parity
31
- 'even' unless odd_parity?
32
- 'odd'
50
+ odd_parity? ? 'odd' : 'even'
33
51
  end
34
52
 
35
53
  def odd_parity?
36
54
  # http://www.cryptosys.net/3des.html
37
55
  # http://csrc.nist.gov/publications/nistpubs/800-67/SP800-67.pdf
38
56
  #
39
- # The eight error detecting bits are set to make the parity of each 8-bit
40
- # byte of the key odd. That is, there is an odd number of "1"s in each 8-bit byte.
57
+ # The eight error detecting bits are set to make the parity of each 8-bit
58
+ # byte of the key odd. That is, there is an odd number of "1"s in each 8-bit
59
+ # byte. Every byte has to be odd, not just the first one.
41
60
 
42
- #3.to_s(2).count('1')
43
- #@key.unpack("H2").first.to_i(16).to_s(2)
44
-
45
- working=@key.unpack('H2'*(@key.length))
46
- working.each do |o|
47
- freq = o.to_i(16).to_s(2).count('1').to_i
48
- if( freq%2 == 0)
49
- #puts "#{o} is #{o.to_i(16).to_s(2).count('1').to_i } - even"
50
- return false
51
- else
52
- return true
53
- #puts "#{o} is #{o.to_i(16).to_s(2).count('1').to_i } - odd"
54
- end
55
- end
61
+ @key.each_byte.all? { |b| b.to_s(2).count("1").odd? }
56
62
  end
57
63
 
58
64
  def self.encrypt(data, key)
59
65
  unless key.length == 8 || key.length == 16 || key.length ==24
60
66
  raise TypeError, "key length should be 8, 16 or 24 bytes"
61
67
  end
62
- des = OpenSSL::Cipher.new("des-cbc") if key.length == 8
63
- des = OpenSSL::Cipher.new("des-ede-cbc") if key.length == 16
64
- des = OpenSSL::Cipher.new("des-ede3-cbc") if key.length == 24
65
-
66
- des.encrypt
67
- des.key=key.key
68
+ des = HSMR.des(key.key, :encrypt)
68
69
  to_hex( des.update(to_binary(data)) )
69
70
  end
70
71
 
@@ -136,18 +137,14 @@ module HSMR
136
137
 
137
138
  def self.encrypt_pin(key, pin)
138
139
  @pin = pin.unpack('a2'*(pin.length/2)).map{|x| x.hex}.pack('c'*(pin.length/2))
139
- des = OpenSSL::Cipher.new("des-ede")
140
- des.encrypt
141
- des.key=key.key
140
+ des = HSMR.des(key.key, :encrypt, :ecb)
142
141
  return des.update(@pin).unpack('H*').first.upcase
143
142
  end
144
143
 
145
144
  def self.decrypt_pin(key, pinblock)
146
145
  @pinblock = pinblock.unpack('a2'*(pinblock.length/2)).map{|x| x.hex}.pack('c'*(pinblock.length/2))
147
- des = OpenSSL::Cipher.new("des-ede")
148
- des.decrypt
146
+ des = HSMR.des(key.key, :decrypt, :ecb)
149
147
  des.padding=0
150
- des.key=key.key
151
148
  result = des.update(@pinblock)
152
149
  result << des.final
153
150
  result.unpack('H*').first.upcase
@@ -157,10 +154,7 @@ module HSMR
157
154
 
158
155
  validation_data = account.unpack('a2'*(account.length/2)).map{|x| x.hex}.pack('c'*(account.length/2))
159
156
 
160
- #des = OpenSSL::Cipher::Cipher.new("des-ede-cbc")
161
- des = OpenSSL::Cipher.new("des-cbc")
162
- des.encrypt
163
- des.key=key.key
157
+ des = HSMR.des(key.key, :encrypt)
164
158
  return HSMR::decimalise(des.update(validation_data).unpack('H*').first, :ibm, dtable)[0...plength]
165
159
 
166
160
  end
@@ -194,9 +188,7 @@ module HSMR
194
188
  def self.pvv(key, account, pvki, pin)
195
189
  tsp = account.to_s[4,11] + pvki.to_s + pin.to_s
196
190
  @tsp = tsp.unpack('a2'*(tsp.length/2)).map{|x| x.hex}.pack('c'*(tsp.length/2))
197
- des = OpenSSL::Cipher.new("des-ede")
198
- des.encrypt
199
- des.key=key.key
191
+ des = HSMR.des(key.key, :encrypt, :ecb)
200
192
  result = des.update(@tsp).unpack('H*').first.upcase
201
193
  decimalise(result, :visa)[0..3].join
202
194
  end
@@ -209,8 +201,12 @@ module HSMR
209
201
  #
210
202
  #raise ArgumentError "PAN"
211
203
 
212
- data1 = pan
213
- data2 = "#{exp}#{svc}".ljust(16, '0')
204
+ # The PAN, expiry and service code are concatenated and zero padded to 32
205
+ # digits, then split in half. Assuming the PAN alone fills the first half
206
+ # only holds for 16 digit PANs.
207
+ block = "#{pan}#{exp}#{svc}".ljust(32, '0')
208
+ data1 = block[0, 16]
209
+ data2 = block[16, 16]
214
210
 
215
211
  result = encrypt(data1, key_a)
216
212
  result = result.xor(data2)
data/test/hsmr_test.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- class TestHSMR < Test::Unit::TestCase
3
+ class TestHSMR < Minitest::Test
4
4
 
5
5
  def test_generate_a_component
6
6
  component_1 = HSMR::Component.new(nil, HSMR::SINGLE)
@@ -86,11 +86,11 @@ class TestHSMR < Test::Unit::TestCase
86
86
 
87
87
  # Test determining the parity
88
88
  parity.each do |pair|
89
- assert_equal true, HSMR::Key.new(pair[0]).odd_parity?
90
- assert_equal false, HSMR::Key.new(pair[1]).odd_parity?
89
+ assert HSMR::Key.new(pair[0]).odd_parity?
90
+ refute HSMR::Key.new(pair[1]).odd_parity?
91
91
 
92
- assert_equal true, HSMR::Component.new(pair[0]).odd_parity?
93
- assert_equal false, HSMR::Component.new(pair[1]).odd_parity?
92
+ assert HSMR::Component.new(pair[0]).odd_parity?
93
+ refute HSMR::Component.new(pair[1]).odd_parity?
94
94
  end
95
95
 
96
96
  # Test converting even to odd parity
@@ -116,22 +116,27 @@ class TestHSMR < Test::Unit::TestCase
116
116
  end
117
117
 
118
118
  def test_CVC_CVC2_calculations
119
- cases = []
120
- # Component 1 Component 2 PAN EXP SCode CVC
121
- cases << %W{ 1234567890ABCDEF FEDCBA1234567890 5656565656565656 1010 ___ 922 }
122
- cases << %W{ 1234567890ABCDEF FEDCBA1234567890 5656565656565656 1010 000 922 }
123
- cases << %W{ 1234567890ABCDEF FEDCBA1234567890 5683739237489383838 1010 000 367 }
124
- cases << %W{ 1234567890ABCDEF FEDCBA1234567890 568367393472639 1010 000 067 }
125
- cases << %W{ 1234567890ABCDEF FEDCBA1234567890 5683673934726394 1010 000 409 }
126
- cases << %W{ 1234567890ABCDEF FEDCBA1234567890 5683673934726394 1010 050 CVV248 or CVC409 }
127
- cases << %W{ 1234567890ABCDEF FEDCBA1234567890 5683673934726394 1010 101 CVV501 or CVC409 }
128
- cases << %W{ 1234567890ABCDEF FEDCBA1234567890 5683673934726394 1010 102 CVV206 or CVC409 }
129
-
130
- kl = "0123456789ABCDEF"
131
- kr = "FEDCBA1234567890"
119
+ # doc/CVC.examples.txt. This test previously built the table below and then
120
+ # asserted nothing at all.
121
+ ka = HSMR::Key.new("1234567890ABCDEF")
122
+ kb = HSMR::Key.new("FEDCBA1234567890")
132
123
 
133
-
134
- #HSMR.cvv(kl, kr, "4509494222049051", "0907", "1010")
124
+ # PAN EXP SCode CVC
125
+ cases = []
126
+ cases << %W{ 5656565656565656 1010 "" 922 }
127
+ cases << %W{ 5656565656565656 1010 000 922 }
128
+ cases << %W{ 5683739237489383838 1010 000 367 }
129
+ cases << %W{ 568367393472639 1010 000 067 }
130
+ cases << %W{ 5683673934726394 1010 000 409 }
131
+ cases << %W{ 5683673934726394 1010 050 248 }
132
+ cases << %W{ 5683673934726394 1010 101 501 }
133
+ cases << %W{ 5683673934726394 1010 102 206 }
134
+
135
+ cases.each do |pan, exp, svc, expected|
136
+ svc = "" if svc == %q{""}
137
+ assert_equal expected, HSMR::cvv(ka, kb, pan, exp, svc),
138
+ "PAN #{pan} (#{pan.length} digits), service code #{svc.inspect}"
139
+ end
135
140
  end
136
141
 
137
142
  def test_PIN_PVV_CVV_and_CVV2_generation
@@ -179,4 +184,144 @@ class TestHSMR < Test::Unit::TestCase
179
184
  #puts "#{pin} == #{c[2]} ? #{pin.to_i == c[2].to_i} | #{pvv} == #{c[3]} ? #{pvv.to_i == c[3].to_i}"
180
185
  end
181
186
  end
187
+ def test_key_rejects_unusable_input
188
+ # Previously any unexpected type silently returned a freshly generated
189
+ # random key rather than raising.
190
+ assert_raises(TypeError) { HSMR::Key.new(123) }
191
+ assert_raises(TypeError) { HSMR::Key.new(:oops) }
192
+ assert_raises(TypeError) { HSMR::Component.new(123) }
193
+ assert_raises(ArgumentError) { HSMR::Key.new([]) }
194
+ end
195
+
196
+ def test_key_generates_a_usable_key_when_given_nothing
197
+ key = HSMR::Key.new
198
+ assert_equal 16, key.length
199
+ assert_equal 16, key.key.length
200
+ assert_match(/\A[0-9A-F ]+\z/, key.to_s)
201
+
202
+ assert_equal 8, HSMR::Key.new(nil, HSMR::SINGLE).length
203
+ assert_equal 24, HSMR::Key.new(nil, HSMR::TRIPLE).length
204
+
205
+ refute_equal HSMR::Key.new.to_s, HSMR::Key.new.to_s
206
+ end
207
+
208
+ def test_key_does_not_mutate_the_component_array_it_is_given
209
+ components = [HSMR::Component.new("0123456789ABCDEF"), HSMR::Component.new("FEDCBA9876543210")]
210
+ HSMR::Key.new(components)
211
+ assert_equal 2, components.length
212
+ end
213
+
214
+ # Vectors below are from the HSM notes in doc/ -- values produced by a real
215
+ # HSM, not by this library.
216
+
217
+ def test_PIN_block_encryption_matches_known_HSM_values
218
+ # doc/encryption.txt
219
+ vectors = [
220
+ %W{ 0123456789ABCDEFFEDCBA9876543210 1234000000000000 D4718F4CA902C2A3 },
221
+ %W{ 0123456789ABCDEFFEDCBA9876543210 8881000000000000 9EE1F2989B005F6A },
222
+ %W{ 0123456789ABCDEFFEDCBA9876543210 9254000000000000 30D7E71ADB09B2F6 },
223
+ %W{ 0123456789ABCDEFFEDCBA9876543210 1927000000000000 F7DE2452CC64FC6D },
224
+ %W{ 0123456789ABCDEFFEDCBA9876543210 8363000000000000 A947B12F1E7F345A },
225
+ %W{ BA942A01EC6EABCD107346CB61F4F4FE 1234000000000000 5DFA5CE9EDCF20D9 },
226
+ %W{ BA942A01EC6EABCD107346CB61F4F4FE 8881000000000000 9E22FC9F1539BF54 },
227
+ %W{ BA942A01EC6EABCD107346CB61F4F4FE 9254000000000000 B30A6DE41326C0FE },
228
+ %W{ BA942A01EC6EABCD107346CB61F4F4FE 1927000000000000 E905C2F4E94638B7 },
229
+ %W{ BA942A01EC6EABCD107346CB61F4F4FE 8363000000000000 FE18700A5357D343 },
230
+ %W{ 4A3DB34F255EA743ECDA19D0945ECB31 1234000000000000 77B3EAD1F9CEAB4E },
231
+ %W{ 4A3DB34F255EA743ECDA19D0945ECB31 8881000000000000 BF0E37A91DA9D878 },
232
+ %W{ 4A3DB34F255EA743ECDA19D0945ECB31 9254000000000000 43A8D24E4DAC0D5C },
233
+ %W{ 4A3DB34F255EA743ECDA19D0945ECB31 1927000000000000 4D367C663AB681F1 },
234
+ %W{ 4A3DB34F255EA743ECDA19D0945ECB31 8363000000000000 33D63EC052648F5B },
235
+ ]
236
+
237
+ vectors.each do |hex_key, pinblock, expected|
238
+ key = HSMR::Key.new(hex_key)
239
+ assert_equal expected, HSMR::encrypt_pin(key, pinblock)
240
+ assert_equal pinblock, HSMR::decrypt_pin(key, expected)
241
+ end
242
+ end
243
+
244
+ def test_setting_odd_parity_fixes_every_byte
245
+ # doc/parity.txt. The third pair starts with an odd byte but has even bytes
246
+ # later on -- odd_parity? used to check only the first byte and leave it be.
247
+ [%W{ 41A2AC14A90C583741A2AC14A90C5837 40A2AD15A80D583740A2AD15A80D5837 },
248
+ %W{ F2AEDAE3FEE90DC2921F01341F54D37F F2AEDAE3FEE90DC2921F01341F54D37F },
249
+ %W{ 80EFEC4895855B06B4015041C3F9F61F 80EFEC4994855B07B5015140C2F8F71F },
250
+ %W{ 4D3DE6AA837AA60A413DDD6CBCB12C82 4C3DE6AB837AA70B403DDC6DBCB02C83 },
251
+ %W{ 1E74C6914F41E5EFD9969A0B134EF819 1F75C7914F40E5EFD9979B0B134FF819 }].each do |input, expected|
252
+ assert_equal expected, HSMR::Key.new(input).set_odd_parity.to_s.gsub(" ", "")
253
+ assert HSMR::Key.new(expected).odd_parity?
254
+ end
255
+ end
256
+
257
+ def test_odd_parity_checks_every_byte_not_just_the_first
258
+ # First byte 0x80 is odd, but later bytes are not.
259
+ refute HSMR::Key.new("80EFEC4895855B06B4015041C3F9F61F").odd_parity?
260
+ assert_equal "even", HSMR::Key.new("80EFEC4895855B06B4015041C3F9F61F").parity
261
+ assert_equal "odd", HSMR::Key.new("0123456789ABCDEF").parity
262
+ end
263
+
264
+ def test_double_length_key_KCV_values_from_HSM_notes
265
+ # doc/test values.txt and doc/HSM Stuff/me test send key.txt
266
+ [%W{ 23232323232323234545454545454545 3A42D7 },
267
+ %W{ 45454545454545452323232323232323 AC5700 },
268
+ %W{ 67676767676767676767676767676767 B0B563 },
269
+ %W{ B5199D109D46E6914AB96D6E7F7CDAA8 F0916F },
270
+ %W{ AE9232A20276E0D03B16EC4C2C01CBC7 FB599F },
271
+ %W{ 25F491A467FDF7CEB3B0E6135BA78C0D 098FA4 }].each do |hex_key, kcv|
272
+ assert_equal kcv, HSMR::Key.new(hex_key).kcv
273
+ end
274
+ end
275
+
276
+ def test_PVV_generation_across_PVK_indexes
277
+ # doc/test values.txt -- the other PVV test only ever uses PVKI 2.
278
+ pvk = HSMR::Key.new("0123456789ABCDEF" * 2)
279
+
280
+ assert_equal "8056", HSMR::pvv(pvk, "1234123412341234", "1", "1234")
281
+ assert_equal "2485", HSMR::pvv(pvk, "1234123412341234", "2", "1234")
282
+ assert_equal "6495", HSMR::pvv(pvk, "1234123412341234", "1", "4592")
283
+ end
284
+
285
+ def test_des_rejects_keys_of_the_wrong_length
286
+ assert_raises(ArgumentError) { HSMR.des("short", :encrypt) }
287
+ end
288
+ def test_README_examples_still_hold
289
+ # Every value shown in README.md, so the docs cannot drift from the code.
290
+ key = HSMR::Key.new("4CA2161637D0133E5E151AEA45DA2A12")
291
+
292
+ assert_equal "4CA2 1616 37D0 133E 5E15 1AEA 45DA 2A12", key.to_s
293
+ assert_equal "7B0898", key.kcv
294
+ assert_equal "even", key.parity
295
+ refute key.odd_parity?
296
+ assert_equal 8, HSMR::Key.new(nil, HSMR::SINGLE).length
297
+
298
+ # Parity bits sit outside the DES key schedule, so the kcv is unchanged
299
+ kcv_before = key.kcv
300
+ assert_equal "4CA2 1616 37D0 133E 5E15 1AEA 45DA 2A13", key.set_odd_parity.to_s
301
+ assert_equal kcv_before, key.kcv
302
+
303
+ c1 = HSMR::Component.new("0123456789ABCDEF")
304
+ c2 = HSMR::Component.new("FEDCBA9876543210")
305
+ assert_equal "0123 4567 89AB CDEF", c1.to_s
306
+ assert_equal "D5D44F", c1.kcv
307
+ combined = HSMR::Key.new([c1, c2])
308
+ assert_equal "FFFF FFFF FFFF FFFF", combined.to_s
309
+ assert_equal "CAAAAF", combined.kcv
310
+
311
+ zmk = HSMR::Key.new("0123456789ABCDEFFEDCBA9876543210")
312
+ block = HSMR.encrypt_pin(zmk, "041274FFFFFFFFFF")
313
+ assert_equal "CFB709CC37D9262A", block
314
+ assert_equal "041274FFFFFFFFFF", HSMR.decrypt_pin(zmk, block)
315
+
316
+ pgk = HSMR::Key.new("3737373737373737")
317
+ assert_equal "4412", HSMR.ibm3624(pgk, "5560501200002101", 4, "0123456789012345").join
318
+
319
+ pvk = HSMR::Key.new("4CA2161637D0133E5E151AEA45DA2A12")
320
+ assert_equal "0798", HSMR.pvv(pvk, "5999997890123412", "1", "1234")
321
+
322
+ cvk_a = HSMR::Key.new("1111111111111111")
323
+ cvk_b = HSMR::Key.new("1111111111111111")
324
+ assert_equal "317", HSMR.cvv(cvk_a, cvk_b, "5560501200002101", "1010", "0")
325
+ assert_equal "134", HSMR.cvv(cvk_a, cvk_b, "5560501200002101", "1010", "101")
326
+ end
182
327
  end
data/test/test_helper.rb CHANGED
@@ -1,2 +1,2 @@
1
- require "test/unit"
1
+ require "minitest/autorun"
2
2
  require "hsmr"