Module: EMV::Crypto

Defined in:
lib/emv/crypto/crypto.rb

Class Method Summary collapse

Class Method Details

.check_and_convert(mes, data, length_in_bytes) ⇒ Object



98
99
100
101
102
103
104
105
# File 'lib/emv/crypto/crypto.rb', line 98

def self.check_and_convert mes, data, length_in_bytes
  raise "`#{mes}` may not be nil" unless data
  unless data.length == length_in_bytes || data.length == length_in_bytes*2
    raise "invalid length for '#{mes}'. Should be #{length_in_bytes}" 
  end
  data = ISO7816.s2b(data) if data.length == length_in_bytes*2
  data
end

.encrypt_cbc(key, data) ⇒ Object

encrypt cbc accroding to CPS 5.5.2



37
38
39
40
41
42
# File 'lib/emv/crypto/crypto.rb', line 37

def self.encrypt_cbc key, data
  cipher = OpenSSL::Cipher::Cipher.new("des-ede-cbc").encrypt
  cipher.key = key
  data = pad(data)
  cipher.update data
end

.encrypt_ecb(key, data) ⇒ Object

encrypt ecb according to CPS 5.5.1



30
31
32
33
34
# File 'lib/emv/crypto/crypto.rb', line 30

def self.encrypt_ecb key, data
  cipher = OpenSSL::Cipher::Cipher.new("des-ede").encrypt
  cipher.key = key
  cipher.update(data)
end

.generate_session_key(ic_card_key, seq, type) ⇒ Object

Generate Session keys according to CPS 5.3



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/emv/crypto/crypto.rb', line 7

def self.generate_session_key ic_card_key, seq, type
  seq         = check_and_convert("seq", seq, 2)
  ic_card_key = check_and_convert("ic_card_key", ic_card_key, 16)
  
  derivation_data = case type
          when :enc
            "\x01\x82"
          when :mac
            "\x01\x01"
          when :dek
            "\x01\x81"
          else
            raise "invalid type of key: #{type}"
          end

  derivation_data += seq + ISO7816.s2b("000000000000000000000000")

  cipher = OpenSSL::Cipher::Cipher.new("des-ede-cbc").encrypt
  cipher.key = ic_card_key
  cipher.update derivation_data
end

.mac_for_personalization(key, input) ⇒ Object

Mac calculation according to CPS 5.4.1.



45
46
47
48
49
50
51
52
53
54
55
# File 'lib/emv/crypto/crypto.rb', line 45

def self.mac_for_personalization key, input
  key = check_and_convert "key", key, 16
  input = pad(input)
  cipher = OpenSSL::Cipher::Cipher.new("des-ede-cbc").encrypt
  cipher.key = key
  mac = ""
  input.scan(/.{8}/m) {|block|
    mac = cipher.update block
  }
  mac
end

.pad(input) ⇒ Object



89
90
91
92
93
94
95
96
# File 'lib/emv/crypto/crypto.rb', line 89

def self.pad input

  input += "\x80"
  while (input.length % 8) != 0
    input << 0x00
  end
  input
end

.retail_mac(key, data) ⇒ Object

calculate ISO 9797-1 “Retail Mac” (MAC Algorithm 3 with output transformation 3, without truncation) : DES with final TDES.

Padding is added if data is not a multiple of 8



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/emv/crypto/crypto.rb', line 63

def self.retail_mac key, data
  cipher      = OpenSSL::Cipher::Cipher.new("des-cbc").encrypt
  cipher.key  = key[0,8]

  data = pad(data) 

  single_data = data[0,data.length-8]
  # Single DES with XOR til the last block
  if single_data && single_data.length > 0
    mac_ = cipher.update(single_data)
    mac_ = mac_[mac_.length-8, 8]
  else # length of data was <= 8
    mac_ = "\x00"*8
  end

  triple_data = data[data.length-8, 8]
  mac = ""
  0.upto(7) { |i|
    mac << (mac_[i] ^ triple_data[i])
  }
  # Final Round of TDES
  cipher      = OpenSSL::Cipher::Cipher.new("des-ede").encrypt
  cipher.key  = key
  cipher.update(mac)
end