passvault 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/Laurent_Vansteenberghe.zip +0 -0
- data/README.md +65 -0
- data/bin/passvault +3 -0
- data/bin/passvault-gui +3 -0
- data/lib/bytes_manip.rb +47 -0
- data/lib/conn_cmds.rb +337 -0
- data/lib/conn_constants.rb +116 -0
- data/lib/conn_init.rb +92 -0
- data/lib/conn_transmit.rb +96 -0
- data/lib/connection.rb +28 -0
- data/lib/console_ui.rb +243 -0
- data/lib/crypto.rb +146 -0
- data/lib/file_access.rb +103 -0
- data/lib/gui.rb +270 -0
- data/lib/key_settings.rb +124 -0
- data/lib/rights.rb +6 -0
- data/lib/vault.rb +357 -0
- data/passvault.gemspec +30 -0
- data/pres/secu.pdf +0 -0
- data/pres/secu.pptx +0 -0
- metadata +136 -0
data/.gitignore
ADDED
Binary file
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Passworld Vault
|
2
|
+
|
3
|
+
## Manual
|
4
|
+
|
5
|
+
Before launching the application, ensure the reader is plugged in. If a card is
|
6
|
+
not inserted in the reader before launching the application, the application
|
7
|
+
will ask you to insert your card.
|
8
|
+
|
9
|
+
## System requirements
|
10
|
+
|
11
|
+
* Ruby 1.9.x (with rubygems)
|
12
|
+
* an internet connection to download the dependencies
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Simply unzip passvault.zip, open your system command-line interpreter, go to the
|
17
|
+
unzipped directory and type: `gem install passvault-0.1.0.gem` (you might need
|
18
|
+
`sudo` in front of that).
|
19
|
+
|
20
|
+
## User Guide
|
21
|
+
|
22
|
+
Our password vault comes with two different user interfaces. One is a command
|
23
|
+
line interface and the other is graphical interface. Everyone can use the
|
24
|
+
command line interface, but it is the only way to run administration commands.
|
25
|
+
|
26
|
+
The user interfaces are provided with a timeout system that requires the
|
27
|
+
user to re-type its password every five minutes. This mechanism allows to
|
28
|
+
prevent a potential attacker to access the vault content if the user is away and
|
29
|
+
forgets to close the application properly.
|
30
|
+
|
31
|
+
Before launching one of the the application, ensure the reader is plugged in. If
|
32
|
+
a card is not inserted in the reader before launching the application, the
|
33
|
+
application will ask you to insert your card.
|
34
|
+
|
35
|
+
### Command-line interface
|
36
|
+
|
37
|
+
Command: `passvault`
|
38
|
+
|
39
|
+
Available commands (copy of the inline help):
|
40
|
+
|
41
|
+
-- user commands --
|
42
|
+
list : list the names of registered credentials
|
43
|
+
display <name> : display a registered credential
|
44
|
+
clip <name> : same as "display", but copies the password to the clipboard
|
45
|
+
instead of displaying it
|
46
|
+
add <name> : register a new credential'
|
47
|
+
remove <name> : remove a registered credential'
|
48
|
+
edit <name> : change a registered credential'
|
49
|
+
quit : exit the program'
|
50
|
+
help : display this help message'
|
51
|
+
-- administration commands --
|
52
|
+
reset : reinitialize the vault'
|
53
|
+
erase : erase the vault from the tag'
|
54
|
+
|
55
|
+
### Graphical user interface
|
56
|
+
|
57
|
+
Command: `passvault-gui`
|
58
|
+
|
59
|
+
Note: the graphical user interface does not seem to work on Linux.
|
60
|
+
|
61
|
+
Using our GUI is quite straightforward: when the interface is launched, the user
|
62
|
+
is prompted to type its vault password. If its password is correct, the user
|
63
|
+
access to the main part of the application, a window that shows the list of the
|
64
|
+
entries name currently in the vault. The user simply has to double-click on an
|
65
|
+
entry name to consult the associated password and login.
|
data/bin/passvault
ADDED
data/bin/passvault-gui
ADDED
data/lib/bytes_manip.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Convenience methods to manipulate bytes.
|
2
|
+
module BytesManipulation
|
3
|
+
|
4
|
+
# Converts a number to a little-endian array of n bytes.
|
5
|
+
def to_le_array(num, n)
|
6
|
+
out = Array.new(n)
|
7
|
+
out.each_index do |i|
|
8
|
+
out[i] = num % 256
|
9
|
+
num /= 256
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Converts a byte array to an array holding strings representing the numbers
|
14
|
+
# in base 16. For debugging purposes.
|
15
|
+
def to_hex_array(array)
|
16
|
+
array.flatten.map { |e| e.to_s(16) }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Xors together two byte strings of the same length.
|
20
|
+
def bs_xor(bs1, bs2)
|
21
|
+
fail unless bs1.bytesize == bs2.bytesize
|
22
|
+
out = String.new(bs1)
|
23
|
+
(0 .. bs1.bytesize - 1).each do |i|
|
24
|
+
out.setbyte(i, bs1.getbyte(i) ^ bs2.getbyte(i))
|
25
|
+
end
|
26
|
+
out
|
27
|
+
end
|
28
|
+
|
29
|
+
# Rotate a byte string by n bytes to the right. n can be negative, and efault
|
30
|
+
# to 1.
|
31
|
+
def bs_rotate(bs, n=1)
|
32
|
+
bs.unpack('C*').rotate(n).pack('C*')
|
33
|
+
end
|
34
|
+
|
35
|
+
# DESFire-specific CRC algorithm taking a byte string as input and returning a
|
36
|
+
# byte string of length 2 as output.
|
37
|
+
def desfire_crc(data)
|
38
|
+
wCRC = 0x6363
|
39
|
+
data.each_byte do |byte|
|
40
|
+
byte = (byte ^ (wCRC & 0x00FF)) % 256
|
41
|
+
byte = (byte ^ (byte << 4)) % 256
|
42
|
+
wCRC = ((wCRC >> 8) ^ (byte << 8) ^ (byte << 3) ^ (byte >> 4)) % 65536
|
43
|
+
end
|
44
|
+
[wCRC & 0xFF, (wCRC >> 8) & 0xFF].pack("C*")
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/lib/conn_cmds.rb
ADDED
@@ -0,0 +1,337 @@
|
|
1
|
+
require_relative 'bytes_manip'
|
2
|
+
require_relative 'rights'
|
3
|
+
|
4
|
+
class Connection
|
5
|
+
# This file contains functions wrapping card commands. Usually one function
|
6
|
+
# implements one card command. However, sometimes performing one "logical"
|
7
|
+
# command requires to send multiple card commands (e.g. <tt>authenticate()</tt>).
|
8
|
+
|
9
|
+
include BytesManipulation
|
10
|
+
include Rights
|
11
|
+
|
12
|
+
# Sets the given key settings (a +KeySettings+ object) for the currently
|
13
|
+
# selected application.
|
14
|
+
def change_key_settings(new_key_settings)
|
15
|
+
fail if
|
16
|
+
@key_settings and not @key_settings.can_change_key_settings?(@key_no)
|
17
|
+
data = encipher_data([new_key_settings.to_byte].pack('C*'))
|
18
|
+
response = send_card_cmd(CHANGE_KEY_SETTINGS, data.unpack('C*'))
|
19
|
+
end
|
20
|
+
|
21
|
+
# Retrieve and returns the key settings (as a +KeySettings+ object) for currently
|
22
|
+
# selected application. Sets <tt>@max_keys</tt> and <tt>@key_settings.</tt>
|
23
|
+
def get_key_settings
|
24
|
+
# We can't check for the permission to perform the operation since
|
25
|
+
# this would require knowing the key settings already.
|
26
|
+
response = send_card_cmd(GET_KEY_SETTINGS)[0]
|
27
|
+
@max_keys = response[1]
|
28
|
+
return @key_settings = KeySettings.from_byte(response[0])
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the UID of the tag.
|
32
|
+
def get_uid
|
33
|
+
# GET_VERSION can always be performed without authentication.
|
34
|
+
# We don't care about the other information returned by GET_VERSION.
|
35
|
+
send_card_cmd(GET_VERSION)
|
36
|
+
send_card_cmd(SEND_MORE_DATA)
|
37
|
+
response = send_card_cmd(SEND_MORE_DATA)[0]
|
38
|
+
return response[1..8]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Retrieve and return the file access rights (as a +FileAccess+ object) for the
|
42
|
+
# given file.
|
43
|
+
def get_file_rights(file_no)
|
44
|
+
response = send_card_cmd(GET_FILE_SETTINGS, [file_no])[0]
|
45
|
+
return Rights::FileAccess.from_a(response)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Change key number +key_no+ to be +new_key+, the old key being +old_key+.
|
49
|
+
# After a successful change of the key used to reach the current
|
50
|
+
# authentication status, that authentication is invalidated: an authentication
|
51
|
+
# with the new key is necessary to subsequent operations.
|
52
|
+
def change_key(key_no, old_key, new_key)
|
53
|
+
fail if
|
54
|
+
@key_settings and not @key_settings.can_change_key?(@aid, key_no, @key_no)
|
55
|
+
if key_no == @key_no or @key_settings.change_key == Rights::FREE_ACCESS
|
56
|
+
data = encipher_data(new_key)
|
57
|
+
else
|
58
|
+
data = encipher_old_new_data(old_key, new_key)
|
59
|
+
end
|
60
|
+
send_card_cmd(CHANGE_KEY, [key_no, *data.unpack("C*")])[0]
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns an array containing all application IDs.
|
64
|
+
def get_app_ids
|
65
|
+
fail if @key_settings and not @key_settings.can_get0?(@aid, @key_no)
|
66
|
+
aids = []
|
67
|
+
begin
|
68
|
+
response, status = send_card_cmd(GET_APP_IDS)
|
69
|
+
response.each_slice(3) do |aid|
|
70
|
+
# note: on the DESFire, aid[1] and aid[2] should be 0
|
71
|
+
aids << aid[0] + 256 * aid[1] + 256*256 * aid[2]
|
72
|
+
end
|
73
|
+
end while (status == STATUS[:ADDITIONAL_FRAME])
|
74
|
+
return aids
|
75
|
+
end
|
76
|
+
|
77
|
+
# Create an application with given application ID, given number of keys and
|
78
|
+
# given key settings. The application should not already exist.
|
79
|
+
def create_app(aid, num_keys, key_settings)
|
80
|
+
fail if @key_settings and not @key_settings.can_create_app?(@aid, @key_no)
|
81
|
+
args = [*to_le_array(aid, 3), key_settings.to_byte, num_keys]
|
82
|
+
send_card_cmd(CREATE_APPLICATION, args)[0]
|
83
|
+
end
|
84
|
+
|
85
|
+
# Delete the given application from the tag. Beware that the memory lost will
|
86
|
+
# not be reusable before the tag is formatted. It is not advised to use this.
|
87
|
+
def delete_app(aid)
|
88
|
+
fail if @key_settings and not @key_settings.can_delete_app?(@aid, @key_no)
|
89
|
+
send_card_cmd(DELETE_APP, to_le_array(aid, 3))
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns an array containg all files IDs on the current application.
|
93
|
+
def get_file_ids
|
94
|
+
fail if @key_settings and not @key_settings.can_get?(@key_no)
|
95
|
+
send_card_cmd(GET_FILE_IDS)[0]
|
96
|
+
end
|
97
|
+
|
98
|
+
# Create a new file in the selected application with given access rights,
|
99
|
+
# size, and communication settings. The file should not already exists.
|
100
|
+
def create_file(file_no, rights, file_size)
|
101
|
+
fail if @key_settings and not @key_settings.can_edit_file?(@key_no)
|
102
|
+
args = [file_no, rights.com_set, *rights.to_a, *to_le_array(file_size, 3)]
|
103
|
+
send_card_cmd(CREATE_STD_DATA_FILE, args)[0]
|
104
|
+
end
|
105
|
+
|
106
|
+
# Delete the given file from the currently selected application from the
|
107
|
+
# tag. Beware that the memory lost will not be reusable before the tag is
|
108
|
+
# formatted. It is not advised to use this (overwrite the file instead).
|
109
|
+
def delete_file(file_no)
|
110
|
+
fail if @key_settings and not @key_settings.can_edit_file?(@key_no)
|
111
|
+
send_card_cmd(DELETE_FILE, [file_no])
|
112
|
+
end
|
113
|
+
|
114
|
+
# Read the entire file identified by file_no from the selected application.
|
115
|
+
def read_whole_file(file_no)
|
116
|
+
return read_file(file_no, 0, 0)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Read the up to +length+ bytes in the given file (in the currently selected
|
120
|
+
# application), starting at the given offet. If +length+ is 0, the file read
|
121
|
+
# up to its end. A byte string is returned.
|
122
|
+
def read_file(file_no, offset, length)
|
123
|
+
args = [file_no, *to_le_array(offset, 3), *to_le_array(length, 3)]
|
124
|
+
response, status = send_card_cmd(READ_DATA, args)
|
125
|
+
|
126
|
+
data = response
|
127
|
+
while status == STATUS[:ADDITIONAL_FRAME]
|
128
|
+
response, status= send_card_cmd(SEND_MORE_DATA)
|
129
|
+
data += response
|
130
|
+
end
|
131
|
+
fail 'protocol_error' if length != 0 and data.length != length
|
132
|
+
handler = get_access_rights_read_handler(file_no)
|
133
|
+
return handler.call(length, data)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Size of the first frame for the write operation.
|
137
|
+
WRITE_FFSIZ = 52
|
138
|
+
|
139
|
+
# Size of the subsequent frame for the write operation.
|
140
|
+
WRITE_SFSIZ = 59
|
141
|
+
|
142
|
+
# Write +length+ bytes of +data+ (a byte string) within the given file (in the
|
143
|
+
# currently selected application), starting at the given offset in the file.
|
144
|
+
#
|
145
|
+
# Pre: +data+ is a byte array 0 <= +length+ <= <tt>data.size</tt> <= 52+59
|
146
|
+
def write_file(file_no, offset, length, data)
|
147
|
+
handler = get_access_rights_write_handler(file_no)
|
148
|
+
# ! the line below may make data.length > length
|
149
|
+
data = handler.call(length, data.byteslice(0, length))
|
150
|
+
|
151
|
+
# The first frame contains at most WRITE_FFSIZ bytes.
|
152
|
+
block_size = [data.length, WRITE_FFSIZ].min
|
153
|
+
block = data.byteslice(0, block_size).unpack('C*')
|
154
|
+
args = [file_no, *to_le_array(offset, 3), *to_le_array(length, 3), *block]
|
155
|
+
send_card_cmd(WRITE_DATA, args)
|
156
|
+
|
157
|
+
# If there is more data to send, send subsequent frames.
|
158
|
+
remaining = data.length
|
159
|
+
while (remaining -= block.length) > 0
|
160
|
+
block_size = [remaining, WRITE_SFSIZ].min
|
161
|
+
block = data.byteslice(data.length - remaining, block_size).unpack('C*')
|
162
|
+
send_card_cmd(SEND_MORE_DATA, block)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Erase all of the tag memory (except the tag master key).
|
167
|
+
def format
|
168
|
+
fail unless @aid === 0 && @key_no == 0
|
169
|
+
send_card_cmd(FORMAT_PICC)[0]
|
170
|
+
end
|
171
|
+
|
172
|
+
# Selects an application, and performs authentication and key settings
|
173
|
+
# retrieval for this application.
|
174
|
+
def select_app_auth(aid, key)
|
175
|
+
select_app(aid)
|
176
|
+
authenticate(0, key)
|
177
|
+
get_key_settings()
|
178
|
+
end
|
179
|
+
|
180
|
+
# Select the application with given application ID for future operations.
|
181
|
+
def select_app(aid)
|
182
|
+
@aid = aid
|
183
|
+
send_card_cmd(SELECT_APPLICATION, to_le_array(aid, 3))[0]
|
184
|
+
end
|
185
|
+
|
186
|
+
# Authenticate ourselves with key number `key_no`, which is provided in `key`,
|
187
|
+
# authenticate the tag and sets @sesskey. In case of success, @sesskey is set
|
188
|
+
# to the derived session key and is returned; @key_no and @key are set to the
|
189
|
+
# values of `key_no` and `key`. Any previous authentication is lost when
|
190
|
+
# running this function.
|
191
|
+
def authenticate(key_no, key)
|
192
|
+
# Below, after an underscore, t is for "tag", r is for "reader" (first
|
193
|
+
# letter) or for "rotated" (second letter) and c is for "ciphered".
|
194
|
+
|
195
|
+
# get tag nonce, decipher it, then rotate it
|
196
|
+
nonce_tc = send_card_cmd(GET_CIPHERED_NONCE, [key_no])[0].pack('C*')
|
197
|
+
nonce_t = decipher_receive(nonce_tc, key)
|
198
|
+
nonce_tr = bs_rotate(nonce_t, 1)
|
199
|
+
|
200
|
+
# pick a nonce
|
201
|
+
nonce_r = Random.new(Random.new_seed).bytes(8)
|
202
|
+
|
203
|
+
# obtain our proof of identity using our key
|
204
|
+
proof = decipher_send(nonce_r + nonce_tr, key)
|
205
|
+
response = send_card_cmd(SEND_MORE_DATA, proof.unpack('C*')) [0]
|
206
|
+
response = response.pack('C*')
|
207
|
+
|
208
|
+
# verify the tag proof of identity
|
209
|
+
proved = bs_rotate(decipher_receive(response, key), -1) == nonce_r
|
210
|
+
|
211
|
+
# derive and return the session key, or an exception if authentication failed
|
212
|
+
if proved
|
213
|
+
@key = key
|
214
|
+
@key_no = key_no
|
215
|
+
if key.byteslice(0, 8) != key.byteslice(8, 8)
|
216
|
+
return @sesskey = nonce_r.byteslice(0, 4) + nonce_t.byteslice(0, 4) +
|
217
|
+
nonce_r.byteslice(4, 4) + nonce_t.byteslice(4, 4)
|
218
|
+
else
|
219
|
+
return @sesskey = nonce_r.byteslice(0, 4) + nonce_t.byteslice(0, 4) +
|
220
|
+
nonce_r.byteslice(0, 4) + nonce_t.byteslice(0, 4)
|
221
|
+
end
|
222
|
+
else
|
223
|
+
@key = nil
|
224
|
+
@key_no = nil
|
225
|
+
@sesskey = nil
|
226
|
+
throw :authentication_failed, true
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
private ######################################################################
|
231
|
+
|
232
|
+
# "Enciphers" the given data for the DESFire tag (decipher in 3DES send mode,
|
233
|
+
# with CRC and padding).
|
234
|
+
def encipher_data(data)
|
235
|
+
pad_len = 8 - ((data.length + 2) % 8)
|
236
|
+
padded = data + desfire_crc(data) + Array.new(pad_len, 0).pack('C*')
|
237
|
+
return decipher_send(padded, @sesskey)
|
238
|
+
end
|
239
|
+
|
240
|
+
# "Enciphers" the given data, given the old value of the data for the DESFire
|
241
|
+
# tag (involves 3DES send mode, xoring, two CRCs and padding).
|
242
|
+
def encipher_old_new_data(old, new)
|
243
|
+
fail unless old.length == new.length
|
244
|
+
pad_len = 8 - ((old.length + 4) % 8)
|
245
|
+
xored = bs_xor(old, new)
|
246
|
+
with_crc = xored + desfire_crc(xored) + desfire_crc(new)
|
247
|
+
padded = with_crc + Array.new(pad_len, 0).pack('C*')
|
248
|
+
return decipher_send(padded, @sesskey)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Return a function that will extract readable file data from the transmitted
|
252
|
+
# file data, according the the file access permissions of the given file of
|
253
|
+
# the currently selected application.
|
254
|
+
def get_access_rights_read_handler(file_no)
|
255
|
+
rights = get_file_rights(file_no)
|
256
|
+
read_type = rights.read_type(@key_no)
|
257
|
+
fail 'access denied' unless read_type
|
258
|
+
|
259
|
+
case read_type
|
260
|
+
when Rights::CS_PLAIN
|
261
|
+
return ->(length, data) { data }
|
262
|
+
when Rights:: CS_PLAIN_MAC
|
263
|
+
fail 'MAC handling not implemented.'
|
264
|
+
when Rights::CS_CIPHERED
|
265
|
+
return ->(length, data) { decipher_data(length, data) }
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Decipher ciphered data received from the tag, also stripping the padding and
|
270
|
+
# the CRC at the end of the deciphered data. (functional)
|
271
|
+
def decipher_data(length, data)
|
272
|
+
fail unless data.length % 8 == 0
|
273
|
+
data = decipher_receive(data.pack('C*'), @sesskey)
|
274
|
+
if length == 0
|
275
|
+
data = strip_padding_whole_file(data)
|
276
|
+
else
|
277
|
+
data = strip_padding_partial_file(length + 2, data)
|
278
|
+
end
|
279
|
+
return remove_crc(data)
|
280
|
+
end
|
281
|
+
|
282
|
+
# Strip the padding at the end of deciphered file data received from the tag,
|
283
|
+
# when reading a whole file. (functional)
|
284
|
+
def strip_padding_whole_file(data)
|
285
|
+
i = data.rindex(WHOLE_FILE_PAD_MARKER)
|
286
|
+
return data if i == nil
|
287
|
+
pad_len = data.length - (i + 1)
|
288
|
+
if data.slice(i+1, pad_len) == Array.new(pad_len, 0).pack('C*')
|
289
|
+
return data.slice(0, i)
|
290
|
+
else
|
291
|
+
return data
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Strip the padding at the end of deciphered file data received from the tag,
|
296
|
+
# when reading a fixed chunk of a file. (functional)
|
297
|
+
def strip_padding_partial_file(length_with_crc, data)
|
298
|
+
pad_len = data.length - length_with_crc
|
299
|
+
if data.slice(length_with_crc, pad_len) == Array.new(pad_len, 0).pack('C*')
|
300
|
+
return data.slice(0, length_with_crc)
|
301
|
+
else
|
302
|
+
fail 'no padding when expected'
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# Remove the CRC at the end of deciphered and "unpadded" file
|
307
|
+
# data. (functional)
|
308
|
+
def remove_crc(data)
|
309
|
+
len_with_crc = data.length
|
310
|
+
data.chomp!(desfire_crc(data[0..-3]))
|
311
|
+
throw Exception.new('Bad CRC.') if len_with_crc - 2 != data.length
|
312
|
+
return data
|
313
|
+
end
|
314
|
+
|
315
|
+
# Returns a function that will wrap readable file data in order to send it to
|
316
|
+
# the tag, according the the file access permissions of the given file of
|
317
|
+
# the currently selected application.
|
318
|
+
def get_access_rights_write_handler(file_no)
|
319
|
+
rights = get_file_rights(file_no)
|
320
|
+
write_type = rights.write_type(@key_no)
|
321
|
+
fail 'access denied' unless write_type
|
322
|
+
|
323
|
+
case write_type
|
324
|
+
when Rights::CS_PLAIN
|
325
|
+
return ->(length, data) { data }
|
326
|
+
when Rights::CS_PLAIN_MAC
|
327
|
+
fail 'MAC handling not implemented'
|
328
|
+
when Rights::CS_CIPHERED
|
329
|
+
return ->(length, data) {
|
330
|
+
data += desfire_crc(data)
|
331
|
+
data += Array.new(8 - ((length + 2) % 8), 0).pack('C*')
|
332
|
+
decipher_send(data, @sesskey)
|
333
|
+
}
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
end
|