diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..f8dbcc9 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,28 @@ +GEM + remote: http://rubygems.org/ + specs: + diff-lcs (1.1.2) + git (1.2.5) + jeweler (1.5.2) + bundler (~> 1.0.0) + git (>= 1.2.5) + rake + rake (0.8.7) + rcov (0.9.9) + rspec (2.3.0) + rspec-core (~> 2.3.0) + rspec-expectations (~> 2.3.0) + rspec-mocks (~> 2.3.0) + rspec-core (2.3.1) + rspec-expectations (2.3.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + bundler (~> 1.0.0) + jeweler (~> 1.5.2) + rcov + rspec (~> 2.3.0) diff --git a/Rakefile b/Rakefile index 73b5916..3f62a38 100644 --- a/Rakefile +++ b/Rakefile @@ -16,9 +16,11 @@ Jeweler::Tasks.new do |gem| gem.homepage = "http://github.com/dkam/hsmr" gem.license = "MIT" gem.summary = %Q{HSM Functions in Ruby} - gem.description = %Q{TODO: longer description of your gem} + gem.description = gem.summary gem.email = "d@nmilne.com" gem.authors = ["Dan Milne"] + gem.version = '0.0.1' + # Include your dependencies below. Runtime dependencies are required when using your gem, # and development dependencies are only needed for development (ie running rake tasks, tests, etc) # gem.add_runtime_dependency 'jabber4r', '> 0.1' diff --git a/lib/hsmr.rb b/lib/hsmr.rb index ee949d1..fee3723 100644 --- a/lib/hsmr.rb +++ b/lib/hsmr.rb @@ -1,10 +1,7 @@ require 'openssl' module HSMR - VERSION = '1.0.0' - #Decimalisation methods - IBM=0 - VISA=1 + VERSION = '0.0.1' # Key Lengths SINGLE=64 @@ -38,14 +35,14 @@ module HSMR 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] + return HSMR::decimalise(des.update(validation_data).unpack('H*').first, :ibm, dtable)[0...plength] end - def self.decimalise(value, method=VISA, dtable="0123456789012345" ) + def self.decimalise(value, method=:visa, dtable="0123456789012345" ) result = [] - if method == IBM + if method == :ibm ## # The IBM method ## @@ -53,7 +50,7 @@ module HSMR result << dtable[c.to_i(16),1].to_i end - elsif method == VISA + elsif method == :visa value.each_char do |c| result << c.to_i if numeric?(c) @@ -75,7 +72,7 @@ module HSMR des.encrypt des.key=key.key result = des.update(@tsp).unpack('H*').first.upcase - decimalise(result, VISA)[0..3].join + decimalise(result, :visa)[0..3].join end def self.xor(component1, *rest) @@ -102,239 +99,9 @@ module HSMR ## 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? @@ -346,4 +113,4 @@ class String a1.zip(a2).collect{|c1,c2| c1^c2}.pack("c*") end end -end \ No newline at end of file +end diff --git a/lib/key.rb b/lib/key.rb new file mode 100644 index 0000000..752ff02 --- /dev/null +++ b/lib/key.rb @@ -0,0 +1,112 @@ +module HSMR + 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 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 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