class MIFARE::Plus
Constants
- CMD_COMMIT_PERSO
- CMD_FIRST_AUTH
- CMD_FOLLOWING_AUTH
- CMD_MULTI_BLOCK_READ
- CMD_MULTI_BLOCK_WRITE
- CMD_RESET_AUTH
- CMD_SECOND_AUTH
- CMD_VC_DESELECT
- CMD_WRITE_PERSO
- MF_ACK
Public Class Methods
new(pcd, uid, sak)
click to toggle source
Calls superclass method
PICC::new
# File lib/mifare/plus.rb, line 14 def initialize(pcd, uid, sak) super invalid_auth reset_counter end
Public Instance Methods
auth(key_number, auth_key)
click to toggle source
# File lib/mifare/plus.rb, line 29 def auth(key_number, auth_key) cmd = authed? ? CMD_FOLLOWING_AUTH : CMD_FIRST_AUTH auth_key.padding_mode(2) buffer = [cmd].append_uint(key_number, 2) buffer << 0x00 unless authed? # No PCDCaps2 to send # Ask for authentication received_data = iso_transceive(buffer) # Receive challenge auth_key.clear_iv auth_key.set_iv(generate_iv(:decrypt)) if authed? challenge = auth_key.decrypt(received_data) challenge_rot = challenge.rotate # Generate random number and encrypt it with rotated challenge random_number = SecureRandom.random_bytes(received_data.size).bytes auth_key.clear_iv auth_key.set_iv(generate_iv(:encrypt)) if authed? response = auth_key.encrypt(random_number + challenge_rot) # Send challenge response received_data = iso_transceive([CMD_SECOND_AUTH, *response]) # Check if verification matches rotated random_number auth_key.clear_iv auth_key.set_iv(generate_iv(:decrypt)) if authed? verification = auth_key.decrypt(received_data) @transaction_identifier = verification.shift(4) response_rot = verification.shift(16) if random_number.rotate != response_rot raise ReceiptIntegrityError, 'Authentication Failed' end byte_a = random_number[11..15] byte_b = challenge[11..15] byte_c = random_number[4..8] byte_d = challenge[4..8] byte_e = random_number[7..11] byte_f = challenge[7..11] byte_g = random_number[0..4] byte_h = challenge[0..4] byte_i = byte_c.xor(byte_d) byte_j = byte_g.xor(byte_h) enc_key_base = byte_a + byte_b + byte_i + [0x11] mac_key_base = byte_e + byte_f + byte_j + [0x22] auth_key.clear_iv auth_key.set_iv(generate_iv(:encrypt)) if authed? enc_key = auth_key.encrypt(enc_key_base) @enc_key = Key.new(:aes, enc_key) auth_key.clear_iv auth_key.set_iv(generate_iv(:encrypt)) if authed? mac_key = auth_key.encrypt(mac_key_base) @mac_key = Key.new(:aes, mac_key) reset_counter end
authed?()
click to toggle source
# File lib/mifare/plus.rb, line 20 def authed? !@transaction_identifier.empty? end
transceive(cmd: , plain_data: [], data: [], tx: nil, rx: nil)
click to toggle source
# File lib/mifare/plus.rb, line 24 def transceive(cmd: , plain_data: [], data: [], tx: nil, rx: nil) raise UsageError, 'Call `iso_select` before using commands' unless @iso_selected iso_transceive(send_data) end
Protected Instance Methods
generate_iv(operation)
click to toggle source
# File lib/mifare/plus.rb, line 94 def generate_iv(operation) buffer = [].append_uint(@read_counter, 2).append_uint(@write_counter, 2) buffer.concat(buffer, buffer) if operation == :encrypt buffer.unshift(@transaction_identifier) elsif operation == :decrypt buffer.concat(@transaction_identifier) else raise UsageError, 'Unknown operation mode' end end
generate_mac_payload()
click to toggle source
# File lib/mifare/plus.rb, line 107 def generate_mac_payload end
invalid_auth()
click to toggle source
# File lib/mifare/plus.rb, line 117 def invalid_auth reset_counter @transaction_identifier = [] @enc_key = nil @mac_key = nil end
reset_counter()
click to toggle source
# File lib/mifare/plus.rb, line 111 def reset_counter @session_read_counter = 0 @read_counter = 0 @write_counter = 0 end