Module: Crypto

Includes:
BytesManipulation
Included in:
Connection, Vault
Defined in:
lib/crypto.rb

Overview

This class contains the implementation of cryptographics utilities and functions.

All keys used to communicate with the tag (Triple DES keys) are always represented as 16 bytes long byte-strings. Byte-strings is what the OpenSSL library use, so it makes sense to use that. But often, it is useful to manipulate those as array of bytes. The functions String.unpack() and Array.pack() ((un)pack into(from) a byte string) are very useful for this, especially with ‘C*’ as argument .

The card is only able to perform TDES encryption (not decryption) with 2 keys (keying option 2, aka 2TDEA). When the two keys are identical, the result is the same as plain DES. We elected to use only TDES on the user end (which is presumably also the case on the tag’s end). Therefore all TDES and DES keys are 16-byte key. A 16 byte DES key is formed by concatening the 8 byte DES key to itself.

AES encryption is used to encrypt the credentials stored on the tag.

OpenSSL modes:

DES-EDE-CBC = Triple DES (Encrypt Decrypt Encrypt) in the regular CBC mode (send for encryption, receive for decryption). The IV is zeroed by default.

AES-256-CBC = AES with 256 bit keys in the regular CBC mode. The IV is zeroed by default.

Constant Summary collapse

DES_BLEN =

Block length for DES.

8
ZERO_IV =

An IV of DES_BLEN zeroes.

Array.new(DES_BLEN, 0).pack("C*")
HMAC_LEN =

Size of the HMAC (SHA-256) digest.

32

Instance Method Summary collapse

Methods included from BytesManipulation

#bs_rotate, #bs_xor, #desfire_crc, #to_hex_array, #to_le_array

Instance Method Details

#aes_decipher(bytes, key) ⇒ Object

Deciphers using AES with 256 bits key in CBC (receive) mode.



131
132
133
134
135
136
137
# File 'lib/crypto.rb', line 131

def aes_decipher(bytes, key)
  aes = OpenSSL::Cipher.new('AES-256-CBC')
  aes.decrypt
  aes.key = key
  aes.padding = 0 # no padding
  return aes.update(bytes) + aes.final
end

#aes_encipher(bytes, key) ⇒ Object

Enciphers using AES with 256 bits key in CBC (send) mode.



122
123
124
125
126
127
128
# File 'lib/crypto.rb', line 122

def aes_encipher(bytes, key)
  aes = OpenSSL::Cipher.new('AES-256-CBC')
  aes.encrypt
  aes.key = key
  aes.padding = 0 # no padding
  return aes.update(bytes) + aes.final
end

#decipher_receive(bytes, key) ⇒ Object

Deciphers a byte string using 3DES in CBC receive mode. Returns a byte string.



45
46
47
48
49
50
51
# File 'lib/crypto.rb', line 45

def decipher_receive(bytes, key)
  des = OpenSSL::Cipher.new('DES-EDE-CBC')
  des.decrypt
  des.key = key
  des.padding = 0 # no padding
  return des.update(bytes) + des.final
end

#decipher_send(bytes, key) ⇒ Object

Deciphers a byte string using 3DES in CBC send mode.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/crypto.rb', line 54

def decipher_send(bytes, key)
  mix = ZERO_IV
  partial_block_count =  (bytes.bytesize % DES_BLEN > 0) ? 1 : 0
  nblocks = bytes.length / DES_BLEN + partial_block_count
  out = Array.new(nblocks)

  out.each_index do |i|
    block = bytes.byteslice(i*DES_BLEN, DES_BLEN)
    block = bs_xor(block, mix)
    block = decipher_receive(block, key)
    out[i] = block
    mix = block
  end
  return out.reduce("", :+)
end

#derive_desfire_key(pass, salt) ⇒ Object

Derive a DESFire Triple DES key. This is similar to derive_key() with key_size = 16, excepted that we overwrite the parity bits of the key. Anyone can ask to get those bits from the tag (“key versionning” feature). As such, keeping the orignal parity bits makes the key less secure.



114
115
116
117
118
119
# File 'lib/crypto.rb', line 114

def derive_desfire_key(pass, salt)
  key = derive_key(pass, salt, 16)
  array = key.unpack('C*')
  array.map! { |e| e & 0xFE }
  return array.pack('C*')
end

#derive_key(pass, salt, key_size) ⇒ Object

Derive a key of the given size using the PBKDF2 derivation function, from given pass and salt. 50k iterations of SHA-256 are used.



103
104
105
106
107
# File 'lib/crypto.rb', line 103

def derive_key(pass, salt, key_size)
  sha256_d = OpenSSL::Digest::SHA256.new
  # (pass, salt, iter, keylength, digest)
  return OpenSSL::PKCS5::pbkdf2_hmac(pass, salt, 50000, key_size, sha256_d)
end

#encipher_receive(bytes, key) ⇒ Object

Enciphers a byte string using 3DES in CBC receive mode. Returns a byte string. Unused.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/crypto.rb', line 85

def encipher_receive(bytes, key)
  mix = ZERO_IV
  partial_block_count = (bytes.bytesize % DES_BLEN > 0) ? 1 : 0
  nblocks = bytes.length / DES_BLEN + partial_block_count
  out = Array.new(nblocks)

  out.each_index do |i|
    block1 = bytes.byteslice(i*DES_BLEN, DES_BLEN)
    block = encipher_send(block1, key)
    block = bs_xor(block, mix)
    out[i] = block
    mix = block1
  end
  return out.reduce("", :+)
end

#encipher_send(bytes, key) ⇒ Object

Enciphers a byte string using 3DES in CBC send mode. Returns a byte string. Unused.



75
76
77
78
79
80
81
# File 'lib/crypto.rb', line 75

def encipher_send(bytes, key)
  des = OpenSSL::Cipher.new('DES-EDE-CBC')
  des.encrypt
  des.key = key
  des.padding = 0 # no padding
  return des.update(bytes) + des.final
end

#hmac(bytes, key) ⇒ Object

Computes a HMAC digest on given data using given key. Uses SHA-1 as digest.



141
142
143
144
# File 'lib/crypto.rb', line 141

def hmac(bytes, key)
  sha256_d = sha256_d = OpenSSL::Digest::SHA256.new
  return OpenSSL::HMAC.digest(sha256_d, key, bytes)
end