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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +18 -0
- data/.gitignore +4 -2
- data/CHANGELOG.md +29 -0
- data/Gemfile.lock +13 -29
- data/README.md +93 -19
- data/doc/CVC.examples.txt +55 -0
- data/doc/KVC.examples.txt +190 -0
- data/doc/KVC.more examples.txt +8 -0
- data/doc/PGK.examples.txt +44 -0
- data/doc/PVV Generation.txt +45 -0
- data/doc/encryption.txt +63 -0
- data/doc/parity.txt +14 -0
- data/doc/test values.txt +24 -0
- data/hsmr.gemspec +2 -7
- data/lib/hsmr/component.rb +6 -18
- data/lib/hsmr/key.rb +22 -40
- data/lib/hsmr/version.rb +1 -1
- data/lib/hsmr.rb +40 -44
- data/test/hsmr_test.rb +165 -20
- data/test/test_helper.rb +1 -1
- metadata +16 -26
- data/.travis.yml +0 -4
- data/Guardfile +0 -8
data/doc/encryption.txt
ADDED
|
@@ -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
|
data/doc/test values.txt
ADDED
|
@@ -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 "
|
|
27
|
-
s.add_development_dependency "factory_bot", "~> 6"
|
|
22
|
+
s.add_development_dependency "minitest", "~> 5.25"
|
|
28
23
|
end
|
data/lib/hsmr/component.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
key=init
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
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 =
|
|
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
|
-
(
|
|
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
|
-
'
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
213
|
-
|
|
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
|
|
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
|
-
|
|
90
|
-
|
|
89
|
+
assert HSMR::Key.new(pair[0]).odd_parity?
|
|
90
|
+
refute HSMR::Key.new(pair[1]).odd_parity?
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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 "
|
|
1
|
+
require "minitest/autorun"
|
|
2
2
|
require "hsmr"
|