diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..dc6aad7 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,7 @@ +History +========= + +1.0.0 +------- + +Initial release. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bef7da0 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +HSMR +=========== + +HSMR is a collection of cryptographic commands usually implemented on a HSM (Hardware Security Module). These +are implemented for your education or for testing purposes and should not be used to replace an actual HSM. + +Installation +------------- + +You install it just like any other Ruby gem: + + gem install hsmr + +Usage +--------- + + require 'hsmr' + + +Author +========== + +Dan Milne, http://da.nmilne.com + +Copyright +---------- + +Copyright (c) 2010 Dan Milne. See LICENSE for details. diff --git a/lib/hsmr.rb b/lib/hsmr.rb index e69de29..ee949d1 100644 --- a/lib/hsmr.rb +++ b/lib/hsmr.rb @@ -0,0 +1,349 @@ +require 'openssl' + +module HSMR + VERSION = '1.0.0' + #Decimalisation methods + IBM=0 + VISA=1 + + # Key Lengths + SINGLE=64 + DOUBLE=128 + TRIPLE=192 + + 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") + des.encrypt + des.key=key.key + return des.update(@pin).unpack('H*').first.upcase + end + + def self.decrypt_pin(key, pinblock) + @pinblock = pinblock.unpack('a2'*(pinblock.length/2)).map{|x| x.hex}.pack('c'*(pinblock.length/2)) + des = OpenSSL::Cipher::Cipher.new("des-ede") + des.decrypt + des.padding=0 + des.key=key.key + result = des.update(@pinblock) + result << des.final + result.unpack('H*').first.upcase + end + + def self.ibm3624(key, account, plength=4, dtable="0123456789012345" ) + + validation_data = account.unpack('a2'*(account.length/2)).map{|x| x.hex}.pack('c'*(account.length/2)) + + #des = OpenSSL::Cipher::Cipher.new("des-ede-cbc") + des = OpenSSL::Cipher::Cipher.new("des-cbc") + des.encrypt + des.key=key.key + return HSMR::decimalise(des.update(validation_data).unpack('H*').first, IBM, dtable)[0...plength] + + end + + def self.decimalise(value, method=VISA, dtable="0123456789012345" ) + + result = [] + if method == IBM + ## + # The IBM method + ## + value.each_char do |c| + result << dtable[c.to_i(16),1].to_i + end + + elsif method == VISA + + value.each_char do |c| + result << c.to_i if numeric?(c) + end + + value.upcase.each_char do |c| + result << dtable[c.to_i(16),1].to_i unless numeric?(c) + end + + end + + return result + end + + def self.pvv(key, account, pvki, pin) + tsp = account.to_s[4,11] + pvki.to_s + pin.to_s + @tsp = tsp.unpack('a2'*(tsp.length/2)).map{|x| x.hex}.pack('c'*(tsp.length/2)) + des = OpenSSL::Cipher::Cipher.new("des-ede") + des.encrypt + des.key=key.key + result = des.update(@tsp).unpack('H*').first.upcase + decimalise(result, VISA)[0..3].join + end + + def self.xor(component1, *rest) + return if rest.length == 0 + + component1 = Component.new(component1) unless component1.is_a? Component + raise TypeError, "Component argument expected" unless component1.is_a? Component + + #@components=[] + #rest.each {|c| components << ((c.is_a? HSMR::Component) ? c : HSMR::Component.new(c) ) } + #components.each {|c| raise TypeError, "Component argument expected" unless c.is_a? Component } + #resultant = component1.xor(components.pop) + #components.each {|c| resultant.xor!(c) } + + rest.collect! {|c| ( (c.is_a? HSMR::Component) ? c : HSMR::Component.new(c) ) } + rest.each {|c| raise TypeError, "Component argument expected" unless c.is_a? HSMR::Component } + resultant = component1.xor(rest.pop) + rest.each {|c| resultant.xor!(c) } + + return(resultant) + end + + def self.numeric?(object) + ## Method to determine if an object is a numeric type. + true if Float(object) rescue false + end + + class Component + attr_reader :component + attr_reader :length + attr_reader :parity + + def initialize(component=nil, length=DOUBLE) + ## Should check for odd parity + if component.nil? + component = (0...(length/4)).collect { rand(16).to_s(16).upcase }.join + else + component=component.gsub(/ /,'') + #raise TypeError, "Component argument expected" unless other.is_a? Component + end + @component = component.unpack('a2'*(component.length/2)).map{|x| x.hex}.pack('c'*(component.length/2)) + @length = @component.length + end + + def kcv() + des = OpenSSL::Cipher::Cipher.new("des-cbc") if @component.length == 8 + des = OpenSSL::Cipher::Cipher.new("des-ede-cbc") if @component.length == 16 + des.encrypt + des.key=@component + des.update("\x00"*8).unpack('H*').first[0...6].upcase + end + + def xor(other) + other = Component.new(other) unless other.is_a? Component + raise TypeError, "Component argument expected" unless other.is_a? Component + + @a = @component.unpack('C2'*(@component.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 + + 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=@component.unpack('H2'*(@component.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=@component.unpack('H2'*(@component.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 + @component = working.join.unpack('a2'*(working.length)).map{|x| x.hex}.pack('c'*(working.length)) + end + + def to_s + @component.unpack('H4'*(@component.length/2)).join(" ").upcase + + end + end + + class Key + attr_reader :key + attr_reader :length + attr_reader :parity + + def initialize(init=nil, length=DOUBLE) + return nil if (init.is_a? Array ) && (init.length == 0) + + init = init.first if (init.is_a? Array) && (init.length == 1) + + if init.is_a? Array + init.collect! {|c| ( (c.is_a? HSMR::Component) ? c : HSMR::Component.new(c) ) } + + raise TypeError, "Component argument expected" unless init.first.is_a? Component + + @key=HSMR::xor(init.pop, init).key + + elsif init.is_a? Component + @key = init.component + elsif init.is_a? String + 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 = 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 encpin(pin) + @pin = pin.unpack('a2'*(pin.length/2)).map{|x| x.hex}.pack('c'*(pin.length/2)) + des = OpenSSL::Cipher::Cipher.new("des-ede") + des.encrypt + des.key=@key + return des.update(@pin).unpack('H*').first.upcase + end + + def decryptpin(pinblock) + @pinblock = pinblock.unpack('a2'*(pinblock.length/2)).map{|x| x.hex}.pack('c'*(pinblock.length/2)) + des = OpenSSL::Cipher::Cipher.new("des-ede") + des.decrypt + des.padding=0 + des.key=@key + result = des.update(@pinblock) + result << des.final + result.unpack('H*').first.upcase + end + + def xor(other) + + other=Component.new(other) if other.is_a? String + other=Component.new(other.key) if other.is_a? Key + + raise TypeError, "Component argument expected" unless other.is_a? Component + + @a = @key.unpack('C2'*(@key.length/2)) + @b = other.component.unpack('C2'*(@key.length/2)) + + resultant = Key.new( @a.zip(@b).map {|x,y| x^y}.map {|z| z.to_s(16) }.join.upcase ) + end + + def xor!(_key) + @key = xor(_key).key + end + + def pvv(account, pvki, pin) + HSMR::pvv(self, account, pvi, pin) + end + + def to_s + @key.unpack('H4 '* (@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 + + + end +end + +class String + def xor(other) + if other.empty? + self + else + a1 = self.unpack("c*") + a2 = other.unpack("c*") + a2 *= 2 while a2.length < a1.length + a1.zip(a2).collect{|c1,c2| c1^c2}.pack("c*") + end + end +end \ No newline at end of file diff --git a/spec/hsmr_spec.rb b/spec/hsmr_spec.rb index 0c407ef..2123f15 100644 --- a/spec/hsmr_spec.rb +++ b/spec/hsmr_spec.rb @@ -1,7 +1,181 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper') -describe "Hsmr" do - it "fails" do - fail "hey buddy, you should probably rename this file and start specing for real" +describe "Generate Component" do + it "should 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) + + component_1.length.should == 8 + component_2.length.should == 16 + component_3.length.should == 24 end end + +describe "Calculate component KCV" do + + it "should calculate single length component KCV values" do + component_1 = HSMR::Component.new("6DBF C180 4A01 5BAD") + component_1.kcv.should == "029E60" + + component_2 = HSMR::Component.new("5D80 0497 B319 8591") + component_2.kcv.should == "3B86C3" + + component_3 = HSMR::Component.new("B0C7 7CDC 7354 97C7") + component_3.kcv.should == "7A77BC" + + component_4 = HSMR::Component.new("DAE0 86FE D6EA 0BEA") + component_4.kcv.should == "2E6191" + + component_5 = HSMR::Component.new("682C 8315 F4BF FBC1") + component_5.kcv.should == "62B336" + + component_6 = HSMR::Component.new("5715 F289 04BC B62F") + component_6.kcv.should == "3CBA88" + + component_7 = HSMR::Component.new("D0C4 29AE C4A8 02B5") + component_7.kcv.should == "33AF02" + + component_8 = HSMR::Component.new("7049 D0F7 4A97 15B6") + component_8.kcv.should == "AC1399" + + component_9 = HSMR::Component.new("BC91 D698 157A A4E5") + component_9.kcv.should == "295491" + + component_10 = HSMR::Component.new("64AB 8568 7A0E 322F") + component_10.kcv.should == "D9F7B3" + end + + it "should calculate double length component KCV values" do + component_1 = HSMR::Component.new("ADE3 9B38 0DBC DF38 AE02 AECE 64B3 4373") + component_1.kcv.should == "3002D5" + + component_2 = HSMR::Component.new("B64A EF86 460D DF5B 57B3 D53D AD37 52A1") + component_2.kcv.should == "1F7C07" + + component_3 = HSMR::Component.new("5B89 6E29 76EC 9745 15B5 238C 8CFE 3D23") + component_3.kcv.should == "DE78F2" + + component_4 = HSMR::Component.new("5E61 CB20 D540 1FC7 58EC CDC8 B558 E9B9") + component_4.kcv.should == "FED957" + + component_5 = HSMR::Component.new("23DF CEB9 BF94 ADA8 91E9 580B 8C8F 5BEF") + component_5.kcv.should == "902085" + + component_6 = HSMR::Component.new("DFEF 61C8 2037 3DA4 CE9B 92CD 89E9 B334") + component_6.kcv.should == "E45EB7" + + component_7 = HSMR::Component.new("6746 9E4C FE83 F831 F23E 9D9E 9D9E 9DB3") + component_7.kcv.should == "813B7B" + + component_8 = HSMR::Component.new("23E5 496E DF94 0BD5 9734 B07A BF26 B9E6") + component_8.kcv.should == "E7C48F" + + component_9 = HSMR::Component.new("974F 26BC CB2A ECD5 434F 1CDC 64DF A275") + component_9.kcv.should == "E27250" + + component_10 = HSMR::Component.new("E57A DF5B CEA7 F42A DFD9 E554 07A2 F891") + component_10.kcv.should == "50E3F8" + end +end + +describe "Calculate key KCV" do + + it "should calculate single length key KCV values" do + key_1 = HSMR::Key.new("6DBF C180 4A01 5BAD") + key_1.kcv.should == "029E60" + + key_2 = HSMR::Key.new("5D80 0497 B319 8591") + key_2.kcv.should == "3B86C3" + + key_3 = HSMR::Key.new("B0C7 7CDC 7354 97C7") + key_3.kcv.should == "7A77BC" + + key_4 = HSMR::Key.new("DAE0 86FE D6EA 0BEA") + key_4.kcv.should == "2E6191" + + key_5 = HSMR::Key.new("682C 8315 F4BF FBC1") + key_5.kcv.should == "62B336" + + key_6 = HSMR::Key.new("5715 F289 04BC B62F") + key_6.kcv.should == "3CBA88" + + key_7 = HSMR::Key.new("D0C4 29AE C4A8 02B5") + key_7.kcv.should == "33AF02" + + key_8 = HSMR::Key.new("7049 D0F7 4A97 15B6") + key_8.kcv.should == "AC1399" + + key_9 = HSMR::Key.new("BC91 D698 157A A4E5") + key_9.kcv.should == "295491" + + key_10 = HSMR::Key.new("64AB 8568 7A0E 322F") + key_10.kcv.should == "D9F7B3" + end + it "should calculate double length key KCV values" do + key_1 = HSMR::Key.new("ADE3 9B38 0DBC DF38 AE02 AECE 64B3 4373") + key_1.kcv.should == "3002D5" + + key_2 = HSMR::Key.new("B64A EF86 460D DF5B 57B3 D53D AD37 52A1") + key_2.kcv.should == "1F7C07" + + key_3 = HSMR::Key.new("5B89 6E29 76EC 9745 15B5 238C 8CFE 3D23") + key_3.kcv.should == "DE78F2" + + key_4 = HSMR::Key.new("5E61 CB20 D540 1FC7 58EC CDC8 B558 E9B9") + key_4.kcv.should == "FED957" + + key_5 = HSMR::Key.new("23DF CEB9 BF94 ADA8 91E9 580B 8C8F 5BEF") + key_5.kcv.should == "902085" + + key_6 = HSMR::Key.new("DFEF 61C8 2037 3DA4 CE9B 92CD 89E9 B334") + key_6.kcv.should == "E45EB7" + + key_7 = HSMR::Key.new("6746 9E4C FE83 F831 F23E 9D9E 9D9E 9DB3") + key_7.kcv.should == "813B7B" + + key_8 = HSMR::Key.new("23E5 496E DF94 0BD5 9734 B07A BF26 B9E6") + key_8.kcv.should == "E7C48F" + + key_9 = HSMR::Key.new("974F 26BC CB2A ECD5 434F 1CDC 64DF A275") + key_9.kcv.should == "E27250" + + key_10 = HSMR::Key.new("E57A DF5B CEA7 F42A DFD9 E554 07A2 F891") + key_10.kcv.should == "50E3F8" + end +end + +describe "Calculate parity" do + it "should detect odd_parity in a key" do + odd_key = HSMR::Key.new("41A2AC14A90C583741A2AC14A90C5837") + odd_key.odd_parity?.should == false + end + + it "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") + + odd_key.key.should == even_key.key + + end + + it "should detect odd_parity in a component" do + odd_component = HSMR::Component.new("41A2AC14A90C583741A2AC14A90C5837") + odd_component.odd_parity?.should == false + end + + it "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") + + odd_component.component.should == even_component.component + + end + +end