mirror of
https://github.com/dkam/hsmr.git
synced 2025-12-28 16:54:52 +00:00
266 lines
7.2 KiB
Ruby
266 lines
7.2 KiB
Ruby
require 'openssl'
|
|
require 'component'
|
|
require 'key'
|
|
|
|
module HSMR
|
|
# Key Lengths
|
|
SINGLE=64
|
|
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 parity
|
|
'even' unless odd_parity?
|
|
'odd'
|
|
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 self.encrypt(data, key)
|
|
unless key.length == 8 || key.length == 16 || key.length ==24
|
|
raise TypeError, "key length should be 8, 16 or 24 bytes"
|
|
end
|
|
des = OpenSSL::Cipher::Cipher.new("des-cbc") if key.length == 8
|
|
des = OpenSSL::Cipher::Cipher.new("des-ede-cbc") if key.length == 16
|
|
des = OpenSSL::Cipher::Cipher.new("des-ede3-cbc") if key.length == 24
|
|
|
|
des.encrypt
|
|
des.key=key.key
|
|
to_hex( des.update(to_binary(data)) )
|
|
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 xor(other)
|
|
other=Component.new(other) if other.is_a? String
|
|
#other=Component.new(other.to_s) if other.is_a? Key
|
|
|
|
unless (other.is_a? Component ) or ( other.is_a? Key )
|
|
raise TypeError, "Component argument expected"
|
|
end
|
|
|
|
@a = @key.unpack('C2'*(@key.length/2))
|
|
@b = other.key.unpack('C2'*(other.length/2))
|
|
|
|
resultant = Key.new( @a.zip(@b).
|
|
map {|x,y| x^y}.
|
|
map {|z| z.to_s(16) }.
|
|
map {|c| c.length == 1 ? '0'+c : c }.
|
|
join.upcase )
|
|
resultant
|
|
end
|
|
|
|
def xor!(_key)
|
|
@key = xor(_key).key
|
|
end
|
|
|
|
## Module Methods
|
|
|
|
def self.to_binary(data)
|
|
data.unpack('a2'*(data.length/2)).map{|x| x.hex}.pack('c'*(data.length/2))
|
|
end
|
|
|
|
def self.to_hex(data)
|
|
data.unpack('H*').first.upcase
|
|
end
|
|
|
|
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.cvv(key_a, key_b, pan, exp, svc)
|
|
# http://www.m-sinergi.com/hairi/doc.html
|
|
# For CVV2 use SVC 000
|
|
# For CVV3 SVC is 502
|
|
# For iCVV use SVC of 999
|
|
#
|
|
#raise ArgumentError "PAN"
|
|
|
|
data1 = pan
|
|
data2 = "#{exp}#{svc}".ljust(16, '0')
|
|
|
|
result = encrypt(data1, key_a)
|
|
result = result.xor(data2)
|
|
|
|
result1 = encrypt(result, HSMR::Key.new(key_a.to_s + key_b.to_s) )
|
|
|
|
return HSMR.decimalise( result1 )[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
|
|
end
|
|
|
|
|
|
class String
|
|
def xor(other)
|
|
if other.empty?
|
|
self
|
|
else
|
|
a1 = self.unpack("a2"*(self.length/2)).map {|x| x.hex }
|
|
a2 = other.unpack("a2"*(other.length/2)).map {|x| x.hex }
|
|
#a2 *= 2 while a2.length < a1.length
|
|
|
|
#a1.zip(a2).collect{|c1,c2| c1^c2}.pack("C*")
|
|
a1.zip(a2).
|
|
map {|x,y| x^y}.
|
|
map {|z| z.to_s(16) }.
|
|
map {|c| c.length == 1 ? '0'+c : c }.
|
|
join.upcase
|
|
end
|
|
end
|
|
end
|