mfrc522 1.0.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/lib/core_ext.rb +38 -0
- data/lib/exceptions.rb +1 -0
- data/lib/mfrc522.rb +112 -160
- 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 +13 -15
- data/lib/iso144434.rb +0 -245
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
|