Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/bench/tests/sunspider/crypto-aes.lua
2727 views
1
--[[
2
* AES Cipher function: encrypt 'input' with Rijndael algorithm
3
*
4
* takes byte-array 'input' (16 bytes)
5
* 2D byte-array key schedule 'w' (Nr+1 x Nb bytes)
6
*
7
* applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
8
*
9
* returns byte-array encrypted value (16 bytes)
10
*/]]
11
12
local function prequire(name) local success, result = pcall(require, name); return success and result end
13
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../../bench_support")
14
15
-- Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [§5.1.1]
16
local Sbox = { 0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
17
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
18
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
19
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
20
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
21
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
22
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
23
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
24
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
25
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
26
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
27
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
28
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
29
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
30
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
31
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16 };
32
33
-- Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]
34
local Rcon = { { 0x00, 0x00, 0x00, 0x00 },
35
{0x01, 0x00, 0x00, 0x00},
36
{0x02, 0x00, 0x00, 0x00},
37
{0x04, 0x00, 0x00, 0x00},
38
{0x08, 0x00, 0x00, 0x00},
39
{0x10, 0x00, 0x00, 0x00},
40
{0x20, 0x00, 0x00, 0x00},
41
{0x40, 0x00, 0x00, 0x00},
42
{0x80, 0x00, 0x00, 0x00},
43
{0x1b, 0x00, 0x00, 0x00},
44
{0x36, 0x00, 0x00, 0x00} };
45
46
local function SubBytes(s, Nb) -- apply SBox to state S [§5.1.1]
47
for r = 0,3 do
48
for c = 0,Nb-1 do s[r + 1][c + 1] = Sbox[s[r + 1][c + 1] + 1]; end
49
end
50
return s;
51
end
52
53
54
local function ShiftRows(s, Nb) -- shift row r of state S left by r bytes [§5.1.2]
55
local t = {};
56
for r = 1,3 do
57
for c = 0,3 do t[c + 1] = s[r + 1][((c + r) % Nb) + 1] end; -- shift into temp copy
58
for c = 0,3 do s[r + 1][c + 1] = t[c + 1]; end -- and copy back
59
end -- note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
60
return s; -- see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
61
end
62
63
64
local function MixColumns(s, Nb) -- combine bytes of each col of state S [§5.1.3]
65
for c = 0,3 do
66
local a = {}; -- 'a' is a copy of the current column from 's'
67
local b = {}; -- 'b' is a•{02} in GF(2^8)
68
for i = 0,3 do
69
a[i + 1] = s[i + 1][c + 1];
70
71
if bit32.band(s[i + 1][c + 1], 0x80) ~= 0 then
72
b[i + 1] = bit32.bxor(bit32.lshift(s[i + 1][c + 1], 1), 0x011b);
73
else
74
b[i + 1] = bit32.lshift(s[i + 1][c + 1], 1);
75
end
76
end
77
-- a[n] ^ b[n] is a•{03} in GF(2^8)
78
s[1][c + 1] = bit32.bxor(b[1], a[2], b[2], a[3], a[4]); -- 2*a0 + 3*a1 + a2 + a3
79
s[2][c + 1] = bit32.bxor(a[1], b[2], a[3], b[3], a[4]); -- a0 * 2*a1 + 3*a2 + a3
80
s[3][c + 1] = bit32.bxor(a[1], a[2], b[3], a[4], b[4]); -- a0 + a1 + 2*a2 + 3*a3
81
s[4][c + 1] = bit32.bxor(a[1], b[1], a[2], a[3], b[4]); -- 3*a0 + a1 + a2 + 2*a3
82
end
83
return s;
84
end
85
86
87
local function SubWord(w) -- apply SBox to 4-byte word w
88
for i = 0,3 do w[i + 1] = Sbox[w[i + 1] + 1]; end
89
return w;
90
end
91
92
local function RotWord(w) -- rotate 4-byte word w left by one byte
93
w[5] = w[1];
94
for i = 0,3 do w[i + 1] = w[i + 2]; end
95
return w;
96
end
97
98
99
100
local function AddRoundKey(state, w, rnd, Nb) -- xor Round Key into state S [§5.1.4]
101
for r = 0,3 do
102
for c = 0,Nb-1 do state[r + 1][c + 1] = bit32.bxor(state[r + 1][c + 1], w[rnd*4+c + 1][r + 1]); end
103
end
104
return state;
105
end
106
107
local function Cipher(input, w) -- main Cipher function [§5.1]
108
local Nb = 4; -- block size (in words): no of columns in state (fixed at 4 for AES)
109
local Nr = #w / Nb - 1; -- no of rounds: 10/12/14 for 128/192/256-bit keys
110
111
local state = {{},{},{},{}}; -- initialise 4xNb byte-array 'state' with input [§3.4]
112
for i = 0,4*Nb-1 do state[(i % 4) + 1][math.floor(i/4) + 1] = input[i + 1]; end
113
114
state = AddRoundKey(state, w, 0, Nb);
115
116
for round = 1,Nr-1 do
117
state = SubBytes(state, Nb);
118
state = ShiftRows(state, Nb);
119
state = MixColumns(state, Nb);
120
state = AddRoundKey(state, w, round, Nb);
121
end
122
123
state = SubBytes(state, Nb);
124
state = ShiftRows(state, Nb);
125
state = AddRoundKey(state, w, Nr, Nb);
126
127
local output = {} -- convert state to 1-d array before returning [§3.4]
128
for i = 0,4*Nb-1 do output[i + 1] = state[(i % 4) + 1][math.floor(i / 4) + 1]; end
129
130
return output;
131
end
132
133
134
local function KeyExpansion(key) -- generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2]
135
local Nb = 4; -- block size (in words): no of columns in state (fixed at 4 for AES)
136
local Nk = #key / 4 -- key length (in words): 4/6/8 for 128/192/256-bit keys
137
local Nr = Nk + 6; -- no of rounds: 10/12/14 for 128/192/256-bit keys
138
139
local w = {};
140
local temp = {};
141
142
for i = 0,Nk do
143
local r = { key[4*i + 1], key[4*i + 2], key[4*i + 3], key[4*i + 4] };
144
w[i + 1] = r;
145
end
146
147
for i = Nk,(Nb*(Nr+1)) - 1 do
148
w[i + 1] = {};
149
for t = 0,3 do temp[t + 1] = w[i-1 + 1][t + 1]; end
150
if (i % Nk == 0) then
151
temp = SubWord(RotWord(temp));
152
for t = 0,3 do temp[t + 1] = bit32.bxor(temp[t + 1], Rcon[i/Nk + 1][t + 1]); end
153
elseif (Nk > 6 and i % Nk == 4) then
154
temp = SubWord(temp);
155
end
156
for t = 0,3 do w[i + 1][t + 1] = bit32.bxor(w[i - Nk + 1][t + 1], temp[t + 1]); end
157
end
158
159
return w;
160
end
161
162
local function escCtrlChars(str) -- escape control chars which might cause problems handling ciphertext
163
return string.gsub(str, "[\0\t\n\v\f\r\'\"!-]", function(c) return '!' .. string.byte(c, 1) .. '!'; end);
164
end
165
166
local function unescCtrlChars(str) -- unescape potentially problematic control characters
167
return string.gsub(str, "!%d%d?%d?!", function(c)
168
local sc = string.sub(c, 2,-2)
169
170
return string.char(tonumber(sc));
171
end);
172
end
173
174
--[[
175
* Use AES to encrypt 'plaintext' with 'password' using 'nBits' key, in 'Counter' mode of operation
176
* - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
177
* for each block
178
* - outputblock = cipher(counter, key)
179
* - cipherblock = plaintext xor outputblock
180
]]
181
182
local function AESEncryptCtr(plaintext, password, nBits)
183
if (not (nBits==128 or nBits==192 or nBits==256)) then return ''; end -- standard allows 128/192/256 bit keys
184
185
-- for this example script, generate the key by applying Cipher to 1st 16/24/32 chars of password;
186
-- for real-world applications, a higher security approach would be to hash the password e.g. with SHA-1
187
local nBytes = nBits/8; -- no bytes in key
188
local pwBytes = {};
189
for i = 0,nBytes-1 do pwBytes[i + 1] = string.byte(password, i + 1); end
190
local key = Cipher(pwBytes, KeyExpansion(pwBytes));
191
192
-- key is now 16/24/32 bytes long
193
for i = 1,nBytes-16 do
194
table.insert(key, key[i])
195
end
196
197
-- initialise counter block (NIST SP800-38A §B.2): millisecond time-stamp for nonce in 1st 8 bytes,
198
-- block counter in 2nd 8 bytes
199
local blockSize = 16; -- block size fixed at 16 bytes / 128 bits (Nb=4) for AES
200
local counterBlock = {}; -- block size fixed at 16 bytes / 128 bits (Nb=4) for AES
201
local nonce = os.clock() * 1000 -- (new Date()).getTime(); -- milliseconds since 1-Jan-1970
202
203
-- encode nonce in two stages to cater for JavaScript 32-bit limit on bitwise ops
204
for i = 0,3 do counterBlock[i + 1] = bit32.extract(nonce, i * 8, 8); end
205
for i = 0,3 do counterBlock[i + 4 + 1] = bit32.extract(math.floor(nonce / 0x100000000), i*8, 8); end
206
207
-- generate key schedule - an expansion of the key into distinct Key Rounds for each round
208
local keySchedule = KeyExpansion(key);
209
210
local blockCount = math.ceil(#plaintext / blockSize);
211
local ciphertext = {}; -- ciphertext as array of strings
212
213
for b = 0,blockCount-1 do
214
-- set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
215
-- again done in two stages for 32-bit ops
216
for c = 0,3 do counterBlock[15-c + 1] = bit32.extract(b, c*8, 8); end
217
for c = 0,3 do counterBlock[15-c-4 + 1] = bit32.extract(math.floor(b/0x100000000), c*8, 8); end
218
219
local cipherCntr = Cipher(counterBlock, keySchedule); -- -- encrypt counter block --
220
221
-- calculate length of final block:
222
local blockLength = nil
223
224
if b<blockCount-1 then
225
blockLength = blockSize;
226
else
227
blockLength = (#plaintext - 1) % blockSize+1;
228
end
229
230
local ct = '';
231
for i = 0,blockLength-1 do -- -- xor plaintext with ciphered counter byte-by-byte --
232
local plaintextByte = string.byte(plaintext, b*blockSize+i + 1);
233
local cipherByte = bit32.bxor(plaintextByte, cipherCntr[i + 1]);
234
ct = ct .. string.char(cipherByte);
235
end
236
-- ct is now ciphertext for this block
237
238
ciphertext[b + 1] = escCtrlChars(ct); -- escape troublesome characters in ciphertext
239
end
240
241
-- convert the nonce to a string to go on the front of the ciphertext
242
local ctrTxt = '';
243
for i = 0,7 do ctrTxt = ctrTxt .. string.char(counterBlock[i + 1]); end
244
ctrTxt = escCtrlChars(ctrTxt);
245
246
-- use '-' to separate blocks, use Array.join to concatenate arrays of strings for efficiency
247
return ctrTxt .. '-' .. table.concat(ciphertext, '-');
248
end
249
250
251
--[[
252
* Use AES to decrypt 'ciphertext' with 'password' using 'nBits' key, in Counter mode of operation
253
*
254
* for each block
255
* - outputblock = cipher(counter, key)
256
* - cipherblock = plaintext xor outputblock
257
]]
258
259
local function AESDecryptCtr(ciphertext, password, nBits)
260
if (not (nBits==128 or nBits==192 or nBits==256)) then return ''; end -- standard allows 128/192/256 bit keys
261
262
local nBytes = nBits/8; -- no bytes in key
263
local pwBytes = {};
264
for i = 0,nBytes-1 do pwBytes[i + 1] = string.byte(password, i + 1); end
265
local pwKeySchedule = KeyExpansion(pwBytes);
266
local key = Cipher(pwBytes, pwKeySchedule);
267
268
-- key is now 16/24/32 bytes long
269
for i = 1,nBytes-16 do
270
table.insert(key, key[i])
271
end
272
273
local keySchedule = KeyExpansion(key);
274
275
-- split ciphertext into array of block-length strings
276
local tmp = {}
277
278
for token in string.gmatch(ciphertext, "[^-]+") do
279
table.insert(tmp, token)
280
end
281
282
ciphertext = tmp;
283
284
-- recover nonce from 1st element of ciphertext
285
local blockSize = 16; -- block size fixed at 16 bytes / 128 bits (Nb=4) for AES
286
local counterBlock = {};
287
local ctrTxt = unescCtrlChars(ciphertext[1]);
288
for i = 0,7 do counterBlock[i + 1] = string.byte(ctrTxt, i + 1); end
289
290
local plaintext = {};
291
292
for b = 1,#ciphertext-1 do
293
-- set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
294
for c = 0,3 do counterBlock[15-c + 1] = bit32.extract(b-1, c*8, 8); end
295
for c = 0,3 do counterBlock[15-c-4 + 1] = bit32.extract(math.floor((b-1)/0x100000000), c*8, 8); end
296
297
local cipherCntr = Cipher(counterBlock, keySchedule); -- encrypt counter block
298
299
ciphertext[b + 1] = unescCtrlChars(ciphertext[b + 1]);
300
301
local pt = '';
302
for i = 0,#ciphertext[b + 1]-1 do
303
-- -- xor plaintext with ciphered counter byte-by-byte --
304
local ciphertextByte = string.byte(ciphertext[b + 1], i + 1);
305
local plaintextByte = bit32.bxor(ciphertextByte, cipherCntr[i + 1]);
306
pt = pt .. string.char(plaintextByte);
307
end
308
-- pt is now plaintext for this block
309
310
plaintext[b] = pt; -- b-1 'cos no initial nonce block in plaintext
311
end
312
313
return table.concat(plaintext)
314
end
315
316
local function test()
317
318
local plainText = "ROMEO: But, soft! what light through yonder window breaks?\n\
319
It is the east, and Juliet is the sun.\n\
320
Arise, fair sun, and kill the envious moon,\n\
321
Who is already sick and pale with grief,\n\
322
That thou her maid art far more fair than she:\n\
323
Be not her maid, since she is envious;\n\
324
Her vestal livery is but sick and green\n\
325
And none but fools do wear it; cast it off.\n\
326
It is my lady, O, it is my love!\n\
327
O, that she knew she were!\n\
328
She speaks yet she says nothing: what of that?\n\
329
Her eye discourses; I will answer it.\n\
330
I am too bold, 'tis not to me she speaks:\n\
331
Two of the fairest stars in all the heaven,\n\
332
Having some business, do entreat her eyes\n\
333
To twinkle in their spheres till they return.\n\
334
What if her eyes were there, they in her head?\n\
335
The brightness of her cheek would shame those stars,\n\
336
As daylight doth a lamp; her eyes in heaven\n\
337
Would through the airy region stream so bright\n\
338
That birds would sing and think it were not night.\n\
339
See, how she leans her cheek upon her hand!\n\
340
O, that I were a glove upon that hand,\n\
341
That I might touch that cheek!\n\
342
JULIET: Ay me!\n\
343
ROMEO: She speaks:\n\
344
O, speak again, bright angel! for thou art\n\
345
As glorious to this night, being o'er my head\n\
346
As is a winged messenger of heaven\n\
347
Unto the white-upturned wondering eyes\n\
348
Of mortals that fall back to gaze on him\n\
349
When he bestrides the lazy-pacing clouds\n\
350
And sails upon the bosom of the air.";
351
352
local password = "O Romeo, Romeo! wherefore art thou Romeo?";
353
354
local cipherText = AESEncryptCtr(plainText, password, 256);
355
local decryptedText = AESDecryptCtr(cipherText, password, 256);
356
357
if (decryptedText ~= plainText) then
358
assert(false, "ERROR: bad result: expected " .. plainText .. " but got " .. decryptedText);
359
end
360
361
end
362
363
bench.runCode(test, "crypto-aes")
364
365