class MIFARE::Key
Attributes
cipher_suite[R]
key_size[R]
type[R]
version[R]
Public Class Methods
new(type, key, version = 0x00)
click to toggle source
# File lib/mifare/key.rb, line 8 def initialize(type, key, version = 0x00) @type = type set_key_data(type, key, version) clear_iv init_cipher end
Public Instance Methods
calculate_cmac(data)
click to toggle source
# File lib/mifare/key.rb, line 81 def calculate_cmac(data) if @cmac_subkey1.nil? || @cmac_subkey2.nil? raise UsageError, 'Generate subkeys before calculating CMAC' end # Separate from input object data = data.dup if data.size == 0 || data.size % @block_size != 0 # padding with byte: 0x80, 0x00, 0x00..... data << 0x80 until data.size % @block_size == 0 data << 0x00 end key = @cmac_subkey2 else key = @cmac_subkey1 end # XOR last data block with selected CMAC subkey data = data[0...-@block_size] + data[-@block_size..-1].zip(key).map{|x, y| x ^ y } encrypt(data) @cipher_iv.bytes end
clear_iv()
click to toggle source
# File lib/mifare/key.rb, line 63 def clear_iv @cipher_iv = "\x00" * @block_size end
decrypt(data, cbc_mode: :receive, data_length: nil)
click to toggle source
# File lib/mifare/key.rb, line 42 def decrypt(data, cbc_mode: :receive, data_length: nil) @cipher.decrypt data = cbc_crypt(data, cbc_mode) if @padding_mode == 1 data[0...data_length] elsif @padding_mode == 2 str = data.pack('C*') str.sub! /#{0x80.chr}#{0x00.chr}*\z/, '' str.bytes else raise UsageError, 'Padding mode not set' end end
encrypt(data, cbc_mode: :send, data_length: nil)
click to toggle source
# File lib/mifare/key.rb, line 27 def encrypt(data, cbc_mode: :send, data_length: nil) @cipher.encrypt # Add padding if not a complete block if data.size % @block_size != 0 raise UsageError, 'Padding mode not set' unless @padding_mode data << 0x80 if @padding_mode == 2 until data.size % @block_size == 0 data << 0x00 end end cbc_crypt(data, cbc_mode) end
generate_cmac_subkeys()
click to toggle source
# File lib/mifare/key.rb, line 67 def generate_cmac_subkeys r = (@block_size == 8) ? 0x1B : 0x87 data = Array.new(@block_size, 0) clear_iv data = encrypt(data, cbc_mode: :receive) @cmac_subkey1 = bit_shift_left(data) @cmac_subkey1[-1] ^= r if data[0] & 0x80 != 0 @cmac_subkey2 = bit_shift_left(@cmac_subkey1) @cmac_subkey2[-1] ^= r if @cmac_subkey1[0] & 0x80 != 0 end
key()
click to toggle source
# File lib/mifare/key.rb, line 23 def key @key.bytes end
padding_mode(mode)
click to toggle source
Padding Method according to ISO-9797
# File lib/mifare/key.rb, line 16 def padding_mode(mode) if mode != 1 && mode != 2 raise UsageError, 'Unknown padding mode' end @padding_mode = mode end
set_iv(iv)
click to toggle source
# File lib/mifare/key.rb, line 57 def set_iv(iv) iv = iv.pack('C*') raise UsageError, 'Incorrect IV length' if iv.size != @block_size @cipher_iv = iv end
Private Instance Methods
bit_shift_left(data)
click to toggle source
Shift single bit left across an array
# File lib/mifare/key.rb, line 197 def bit_shift_left(data) data.map.with_index do |value, index| value = (value << 1) & 0xFF if subsequent = data[index + 1] value |= (subsequent >> 7) & 0x01 end value end end
cbc_crypt(data, mode)
click to toggle source
# File lib/mifare/key.rb, line 172 def cbc_crypt(data, mode) # Important!!! # Set key and iv AFTER selecting encrypt or decrypt mode @cipher.key = @key @cipher.iv = @cipher_iv # Convert byte array to binary data = data.pack('C*') if mode == :send # Save output data as IV output_data = @cipher.update(data) + @cipher.final @cipher_iv = output_data[-@block_size..-1] output_data.bytes elsif mode == :receive # Save input data as IV @cipher_iv = data[-@block_size..-1] output_data = @cipher.update(data) + @cipher.final output_data.bytes else raise UsageError, 'Unknown CBC mode' end end
init_cipher()
click to toggle source
# File lib/mifare/key.rb, line 110 def init_cipher @cipher = OpenSSL::Cipher.new(@cipher_suite) @cipher.padding = 0 end
set_key_data(key_type, key, version)
click to toggle source
# File lib/mifare/key.rb, line 115 def set_key_data(key_type, key, version) # Convert hex string to byte array key = [key].pack('H*').bytes if key.is_a?(String) @key_size = key.size if key_type == :des if @key_size != 8 && @key_size != 16 && @key_size != 24 raise UsageError, 'Incorrect key length' end # Data block size for DES is 8 bytes @block_size = 8 @key = store_key_version(key, version) if @key_size == 8 # Store single DES key in 16 bytes @key += @key @cipher_suite = 'des-ede-cbc' elsif @key_size == 16 # Downgrade to single DES if two keys are equal @key_size = 8 if @key[0..7] == @key[8..15] @cipher_suite = 'des-ede-cbc' elsif @key_size == 24 @cipher_suite = 'des-ede3-cbc' end elsif key_type == :aes if @key_size != 16 raise UsageError, 'Incorrect key length' end # data block size for AES is 16 bytes @block_size = 16 @key = key @cipher_suite = 'aes-128-cbc' else raise UsageError, 'Unknown key type' end @key = @key.pack('C*') @version = version end
store_key_version(key, version)
click to toggle source
Store key version in parity bits(LSB of first 8 bytes) of DES key
# File lib/mifare/key.rb, line 160 def store_key_version(key, version) mask = 0x80 key.map.with_index do |key_byte, index| if (index < 8) && (version & (mask >> index) != 0) parity = 1 else parity = 0 end (key_byte & 0xFE) | parity end end