mfrc522 1.0.6 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/core_ext.rb +38 -0
- data/lib/exceptions.rb +1 -0
- data/lib/mfrc522.rb +37 -106
- data/lib/mifare/classic.rb +38 -19
- data/lib/mifare/des_fire.rb +156 -157
- data/lib/mifare/key.rb +39 -12
- data/lib/mifare/plus.rb +124 -0
- data/lib/mifare/plus_sl0.rb +11 -0
- data/lib/mifare/plus_sl1.rb +0 -0
- data/lib/mifare/plus_sl3.rb +11 -0
- data/lib/mifare/ultralight.rb +68 -17
- data/lib/mifare/ultralight_c.rb +13 -14
- data/lib/mifare/ultralight_ev1.rb +40 -0
- data/lib/picc.rb +313 -9
- metadata +7 -3
- data/lib/iso144434.rb +0 -247
data/lib/mifare/key.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
module
|
1
|
+
module MIFARE
|
2
2
|
class Key
|
3
3
|
attr_reader :type
|
4
4
|
attr_reader :cipher_suite
|
@@ -12,25 +12,52 @@ module Mifare
|
|
12
12
|
init_cipher
|
13
13
|
end
|
14
14
|
|
15
|
+
# Padding Method according to ISO-9797
|
16
|
+
def padding_mode(mode)
|
17
|
+
if mode != 1 && mode != 2
|
18
|
+
raise UsageError, 'Unknown padding mode'
|
19
|
+
end
|
20
|
+
@padding_mode = mode
|
21
|
+
end
|
22
|
+
|
15
23
|
def key
|
16
24
|
@key.bytes
|
17
25
|
end
|
18
26
|
|
19
|
-
def encrypt(data, cbc_mode
|
27
|
+
def encrypt(data, cbc_mode: :send, data_length: nil)
|
20
28
|
@cipher.encrypt
|
21
29
|
|
22
30
|
# Add padding if not a complete block
|
23
|
-
|
24
|
-
|
31
|
+
if data.size % @block_size != 0
|
32
|
+
raise UsageError, 'Padding mode not set' unless @padding_mode
|
33
|
+
data << 0x80 if @padding_mode == 2
|
34
|
+
until data.size % @block_size == 0
|
35
|
+
data << 0x00
|
36
|
+
end
|
25
37
|
end
|
26
38
|
|
27
39
|
cbc_crypt(data, cbc_mode)
|
28
40
|
end
|
29
41
|
|
30
|
-
def decrypt(data, cbc_mode
|
42
|
+
def decrypt(data, cbc_mode: :receive, data_length: nil)
|
31
43
|
@cipher.decrypt
|
32
44
|
|
33
|
-
cbc_crypt(data, cbc_mode)
|
45
|
+
data = cbc_crypt(data, cbc_mode)
|
46
|
+
if @padding_mode == 1
|
47
|
+
data[0...data_length]
|
48
|
+
elsif @padding_mode == 2
|
49
|
+
str = data.pack('C*')
|
50
|
+
str.sub! /#{0x80.chr}#{0x00.chr}*\z/, ''
|
51
|
+
str.bytes
|
52
|
+
else
|
53
|
+
raise UsageError, 'Padding mode not set'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def set_iv(iv)
|
58
|
+
iv = iv.pack('C*')
|
59
|
+
raise UsageError, 'Incorrect IV length' if iv.size != @block_size
|
60
|
+
@cipher_iv = iv
|
34
61
|
end
|
35
62
|
|
36
63
|
def clear_iv
|
@@ -42,7 +69,7 @@ module Mifare
|
|
42
69
|
data = Array.new(@block_size, 0)
|
43
70
|
|
44
71
|
clear_iv
|
45
|
-
data = encrypt(data, :receive)
|
72
|
+
data = encrypt(data, cbc_mode: :receive)
|
46
73
|
|
47
74
|
@cmac_subkey1 = bit_shift_left(data)
|
48
75
|
@cmac_subkey1[-1] ^= r if data[0] & 0x80 != 0
|
@@ -53,7 +80,7 @@ module Mifare
|
|
53
80
|
|
54
81
|
def calculate_cmac(data)
|
55
82
|
if @cmac_subkey1.nil? || @cmac_subkey2.nil?
|
56
|
-
raise 'Generate subkeys before calculating CMAC'
|
83
|
+
raise UsageError, 'Generate subkeys before calculating CMAC'
|
57
84
|
end
|
58
85
|
|
59
86
|
# Separate from input object
|
@@ -92,7 +119,7 @@ module Mifare
|
|
92
119
|
|
93
120
|
if key_type == :des
|
94
121
|
if @key_size != 8 && @key_size != 16 && @key_size != 24
|
95
|
-
raise
|
122
|
+
raise UsageError, 'Incorrect key length'
|
96
123
|
end
|
97
124
|
|
98
125
|
# Data block size for DES is 8 bytes
|
@@ -114,7 +141,7 @@ module Mifare
|
|
114
141
|
|
115
142
|
elsif key_type == :aes
|
116
143
|
if @key_size != 16
|
117
|
-
raise
|
144
|
+
raise UsageError, 'Incorrect key length'
|
118
145
|
end
|
119
146
|
|
120
147
|
# data block size for AES is 16 bytes
|
@@ -122,7 +149,7 @@ module Mifare
|
|
122
149
|
@key = key
|
123
150
|
@cipher_suite = 'aes-128-cbc'
|
124
151
|
else
|
125
|
-
raise
|
152
|
+
raise UsageError, 'Unknown key type'
|
126
153
|
end
|
127
154
|
|
128
155
|
@key = @key.pack('C*')
|
@@ -162,7 +189,7 @@ module Mifare
|
|
162
189
|
|
163
190
|
output_data.bytes
|
164
191
|
else
|
165
|
-
raise
|
192
|
+
raise UsageError, 'Unknown CBC mode'
|
166
193
|
end
|
167
194
|
end
|
168
195
|
|
data/lib/mifare/plus.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
module MIFARE
|
2
|
+
class Plus < ::PICC
|
3
|
+
CMD_WRITE_PERSO = 0xA8
|
4
|
+
CMD_COMMIT_PERSO = 0xAA
|
5
|
+
CMD_MULTI_BLOCK_READ = 0x38
|
6
|
+
CMD_MULTI_BLOCK_WRITE = 0xA8
|
7
|
+
CMD_FIRST_AUTH = 0x70
|
8
|
+
CMD_SECOND_AUTH = 0x72
|
9
|
+
CMD_FOLLOWING_AUTH = 0x76
|
10
|
+
CMD_RESET_AUTH = 0x78
|
11
|
+
CMD_VC_DESELECT = 0x48
|
12
|
+
MF_ACK = 0x0A
|
13
|
+
|
14
|
+
def initialize(pcd, uid, sak)
|
15
|
+
super
|
16
|
+
invalid_auth
|
17
|
+
reset_counter
|
18
|
+
end
|
19
|
+
|
20
|
+
def authed?
|
21
|
+
!@transaction_identifier.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def transceive(cmd: , plain_data: [], data: [], tx: nil, rx: nil)
|
25
|
+
raise UsageError, 'Call `iso_select` before using commands' unless @iso_selected
|
26
|
+
iso_transceive(send_data)
|
27
|
+
end
|
28
|
+
|
29
|
+
def auth(key_number, auth_key)
|
30
|
+
cmd = authed? ? CMD_FOLLOWING_AUTH : CMD_FIRST_AUTH
|
31
|
+
auth_key.padding_mode(2)
|
32
|
+
|
33
|
+
buffer = [cmd].append_uint(key_number, 2)
|
34
|
+
buffer << 0x00 unless authed? # No PCDCaps2 to send
|
35
|
+
|
36
|
+
# Ask for authentication
|
37
|
+
received_data = iso_transceive(buffer)
|
38
|
+
|
39
|
+
# Receive challenge
|
40
|
+
auth_key.clear_iv
|
41
|
+
auth_key.set_iv(generate_iv(:decrypt)) if authed?
|
42
|
+
challenge = auth_key.decrypt(received_data)
|
43
|
+
challenge_rot = challenge.rotate
|
44
|
+
|
45
|
+
# Generate random number and encrypt it with rotated challenge
|
46
|
+
random_number = SecureRandom.random_bytes(received_data.size).bytes
|
47
|
+
auth_key.clear_iv
|
48
|
+
auth_key.set_iv(generate_iv(:encrypt)) if authed?
|
49
|
+
response = auth_key.encrypt(random_number + challenge_rot)
|
50
|
+
|
51
|
+
# Send challenge response
|
52
|
+
received_data = iso_transceive([CMD_SECOND_AUTH, *response])
|
53
|
+
|
54
|
+
# Check if verification matches rotated random_number
|
55
|
+
auth_key.clear_iv
|
56
|
+
auth_key.set_iv(generate_iv(:decrypt)) if authed?
|
57
|
+
verification = auth_key.decrypt(received_data)
|
58
|
+
@transaction_identifier = verification.shift(4)
|
59
|
+
response_rot = verification.shift(16)
|
60
|
+
|
61
|
+
if random_number.rotate != response_rot
|
62
|
+
raise ReceiptIntegrityError, 'Authentication Failed'
|
63
|
+
end
|
64
|
+
|
65
|
+
byte_a = random_number[11..15]
|
66
|
+
byte_b = challenge[11..15]
|
67
|
+
byte_c = random_number[4..8]
|
68
|
+
byte_d = challenge[4..8]
|
69
|
+
byte_e = random_number[7..11]
|
70
|
+
byte_f = challenge[7..11]
|
71
|
+
byte_g = random_number[0..4]
|
72
|
+
byte_h = challenge[0..4]
|
73
|
+
byte_i = byte_c.xor(byte_d)
|
74
|
+
byte_j = byte_g.xor(byte_h)
|
75
|
+
|
76
|
+
enc_key_base = byte_a + byte_b + byte_i + [0x11]
|
77
|
+
mac_key_base = byte_e + byte_f + byte_j + [0x22]
|
78
|
+
|
79
|
+
auth_key.clear_iv
|
80
|
+
auth_key.set_iv(generate_iv(:encrypt)) if authed?
|
81
|
+
enc_key = auth_key.encrypt(enc_key_base)
|
82
|
+
@enc_key = Key.new(:aes, enc_key)
|
83
|
+
|
84
|
+
auth_key.clear_iv
|
85
|
+
auth_key.set_iv(generate_iv(:encrypt)) if authed?
|
86
|
+
mac_key = auth_key.encrypt(mac_key_base)
|
87
|
+
@mac_key = Key.new(:aes, mac_key)
|
88
|
+
|
89
|
+
reset_counter
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
|
94
|
+
def generate_iv(operation)
|
95
|
+
buffer = [].append_uint(@read_counter, 2).append_uint(@write_counter, 2)
|
96
|
+
buffer.concat(buffer, buffer)
|
97
|
+
|
98
|
+
if operation == :encrypt
|
99
|
+
buffer.unshift(@transaction_identifier)
|
100
|
+
elsif operation == :decrypt
|
101
|
+
buffer.concat(@transaction_identifier)
|
102
|
+
else
|
103
|
+
raise UsageError, 'Unknown operation mode'
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def generate_mac_payload
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
def reset_counter
|
112
|
+
@session_read_counter = 0
|
113
|
+
@read_counter = 0
|
114
|
+
@write_counter = 0
|
115
|
+
end
|
116
|
+
|
117
|
+
def invalid_auth
|
118
|
+
reset_counter
|
119
|
+
@transaction_identifier = []
|
120
|
+
@enc_key = nil
|
121
|
+
@mac_key = nil
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
File without changes
|
data/lib/mifare/ultralight.rb
CHANGED
@@ -1,35 +1,77 @@
|
|
1
|
-
module
|
1
|
+
module MIFARE
|
2
2
|
class Ultralight < ::PICC
|
3
|
-
CMD_READ
|
4
|
-
|
5
|
-
|
3
|
+
CMD_READ = 0x30 # Reads 4 pages(16 bytes) from the PICC.
|
4
|
+
CMD_FAST_READ = 0x3A # Reads pages within requested range
|
5
|
+
CMD_WRITE = 0xA2 # Writes 1 page(4 bytes) to the PICC.
|
6
|
+
CMD_COMP_WRITE = 0xA0
|
7
|
+
CMD_READ_CNT = 0x39
|
8
|
+
CMD_INCR_CNT = 0xA5
|
9
|
+
CMD_PWD_AUTH = 0x1B
|
10
|
+
CMD_3DES_AUTH = 0x1A # Ultralight C 3DES Authentication.
|
11
|
+
CMD_GET_VERSION = 0x60
|
12
|
+
CMD_READ_SIG = 0x3C
|
13
|
+
CMD_VCSL = 0x4B
|
14
|
+
CMD_CHECK_TEARING_EVENT = 0x3E
|
15
|
+
MF_ACK = 0x0A # Mifare Acknowledge
|
16
|
+
|
17
|
+
CARD_VERSION = Struct.new(
|
18
|
+
:vendor_id, :type, :subtype, :major_ver, :minor_ver, :storage_size, :protocol_type
|
19
|
+
)
|
6
20
|
|
7
21
|
def initialize(pcd, uid, sak)
|
8
22
|
super
|
9
|
-
|
23
|
+
# Set transceive timeout to 15ms
|
24
|
+
@pcd.internal_timer(50)
|
25
|
+
|
26
|
+
# Maximum fast read range
|
27
|
+
@max_range = ((@pcd.buffer_size - 2) / 4).to_i
|
10
28
|
|
11
29
|
# Check if Ultralight C
|
12
|
-
if support_3des_auth?
|
30
|
+
if @model_c = support_3des_auth?
|
13
31
|
extend UltralightC
|
14
|
-
|
32
|
+
end
|
33
|
+
|
34
|
+
unless @version = check_version
|
35
|
+
if version[:major_ver] == 0x01
|
36
|
+
extend UltralightEV1
|
37
|
+
end
|
15
38
|
end
|
16
39
|
end
|
17
40
|
|
18
|
-
def
|
19
|
-
|
41
|
+
def transceive(send_data)
|
42
|
+
received_data, valid_bits = picc_transceive(send_data, false, true)
|
43
|
+
unless valid_bits == 0
|
44
|
+
raise UnexpectedDataError, 'Incorrect Mifare ACK format' if received_data.size != 1 || valid_bits != 4 # ACK is 4 bits long
|
45
|
+
raise MifareNakError, "Mifare NAK detected: 0x#{received_data[0].to_bytehex}" if received_data[0] != MF_ACK
|
46
|
+
end
|
47
|
+
received_data
|
48
|
+
end
|
20
49
|
|
21
|
-
|
50
|
+
def read(block_addr)
|
51
|
+
transceive([CMD_READ, block_addr])
|
22
52
|
end
|
23
53
|
|
24
54
|
def write(page, send_data)
|
25
55
|
if send_data.size != 4
|
26
|
-
raise
|
56
|
+
raise UsageError, "Expect 4 bytes data, got: #{send_data.size} byte"
|
27
57
|
end
|
28
58
|
|
29
|
-
|
30
|
-
|
59
|
+
transceive([CMD_WRITE, page, *send_data])
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_version
|
63
|
+
version = transceive([CMD_GET_VERSION])
|
64
|
+
|
65
|
+
expo = (version[6] >> 1) & 0x0F
|
66
|
+
if version[6] & 0x01 == 0
|
67
|
+
size = 1 << expo
|
68
|
+
else
|
69
|
+
size = (1 << expo) | (1 << (expo - 1))
|
70
|
+
end
|
31
71
|
|
32
|
-
|
72
|
+
CARD_VERSION.new(
|
73
|
+
version[1], version[2], version[3], version[4], version[5], size, version[7]
|
74
|
+
)
|
33
75
|
end
|
34
76
|
|
35
77
|
def model_c?
|
@@ -38,20 +80,29 @@ module Mifare
|
|
38
80
|
|
39
81
|
private
|
40
82
|
|
83
|
+
def check_version
|
84
|
+
begin
|
85
|
+
version = get_version
|
86
|
+
rescue CommunicationError
|
87
|
+
restart_communication
|
88
|
+
return nil
|
89
|
+
end
|
90
|
+
version
|
91
|
+
end
|
92
|
+
|
41
93
|
# Check if PICC support Ultralight 3DES command
|
42
94
|
def support_3des_auth?
|
43
95
|
# Ask for authentication
|
44
96
|
buffer = [CMD_3DES_AUTH, 0x00]
|
45
97
|
|
46
98
|
begin
|
47
|
-
|
99
|
+
transceive(buffer)
|
48
100
|
result = true
|
49
101
|
rescue CommunicationError
|
50
102
|
result = false
|
51
103
|
end
|
52
104
|
|
53
|
-
|
54
|
-
|
105
|
+
restart_communication
|
55
106
|
result
|
56
107
|
end
|
57
108
|
end
|
data/lib/mifare/ultralight_c.rb
CHANGED
@@ -1,19 +1,18 @@
|
|
1
|
-
module
|
1
|
+
module MIFARE
|
2
2
|
module UltralightC
|
3
|
-
CMD_3DES_AUTH = 0x1A # Ultralight C 3DES Authentication.
|
4
|
-
|
5
3
|
def auth(auth_key)
|
6
4
|
if auth_key.cipher_suite != 'des-ede-cbc'
|
7
|
-
raise
|
5
|
+
raise UsageError, 'Incorrect Auth Key Type'
|
8
6
|
end
|
9
7
|
|
10
8
|
auth_key.clear_iv
|
9
|
+
auth_key.padding_mode(1)
|
11
10
|
|
12
11
|
# Ask for authentication
|
13
12
|
buffer = [CMD_3DES_AUTH, 0x00]
|
14
|
-
received_data =
|
13
|
+
received_data = transceive(buffer)
|
15
14
|
card_status = received_data.shift
|
16
|
-
raise UnexpectedDataError, '
|
15
|
+
raise UnexpectedDataError, 'Auth failed: stage 1' if card_status != 0xAF
|
17
16
|
|
18
17
|
challenge = auth_key.decrypt(received_data)
|
19
18
|
challenge_rot = challenge.rotate
|
@@ -24,15 +23,15 @@ module Mifare
|
|
24
23
|
|
25
24
|
# Send challenge response
|
26
25
|
buffer = [0xAF] + response
|
27
|
-
received_data =
|
26
|
+
received_data = transceive(buffer)
|
28
27
|
card_status = received_data.shift
|
29
|
-
raise UnexpectedDataError, '
|
28
|
+
raise UnexpectedDataError, 'Auth failed: stage 2' if card_status != 0x00
|
30
29
|
|
31
30
|
# Check if verification matches rotated random_number
|
32
31
|
verification = auth_key.decrypt(received_data)
|
33
32
|
|
34
33
|
if random_number.rotate != verification
|
35
|
-
|
34
|
+
restart_communication
|
36
35
|
return @authed = false
|
37
36
|
end
|
38
37
|
|
@@ -40,14 +39,14 @@ module Mifare
|
|
40
39
|
end
|
41
40
|
|
42
41
|
def authed?
|
43
|
-
@authed
|
42
|
+
@authed || false
|
44
43
|
end
|
45
44
|
|
46
45
|
def write_des_key(key)
|
47
46
|
# key should be 16 bytes long
|
48
47
|
bytes = [key].pack('H*').bytes
|
49
48
|
if bytes.size != 16
|
50
|
-
raise
|
49
|
+
raise UsageError, "Expect 16 bytes 3DES key, got: #{bytes.size} byte"
|
51
50
|
end
|
52
51
|
|
53
52
|
# Key1
|
@@ -60,7 +59,7 @@ module Mifare
|
|
60
59
|
|
61
60
|
def counter_increment(value)
|
62
61
|
if value < 0
|
63
|
-
raise
|
62
|
+
raise UsageError, 'Expect positive integer for counter'
|
64
63
|
end
|
65
64
|
# you can set any value between 0x0000 to 0xFFFF on the first write (initialize)
|
66
65
|
# after initialized, counter can only be incremented by 0x01 ~ 0x0F
|
@@ -68,8 +67,8 @@ module Mifare
|
|
68
67
|
end
|
69
68
|
|
70
69
|
def enable_protection_from(block_addr)
|
71
|
-
if block_addr
|
72
|
-
raise
|
70
|
+
if block_addr < 0x03 || block_addr > 0x30
|
71
|
+
raise UsageError, 'Requested block beyond memory limit'
|
73
72
|
end
|
74
73
|
# authentication will be required from `block_addr` to 0x2F
|
75
74
|
# valid value are from 0x03 to 0x30
|