Skip to content

Commit 180c597

Browse files
committed
aes num to uint8
1 parent ebfaaef commit 180c597

File tree

4 files changed

+442
-225
lines changed

4 files changed

+442
-225
lines changed

src/primitives/AESGCM.ts

Lines changed: 156 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -222,33 +222,53 @@ export const getBytes64 = function (numericValue: number): number[] {
222222
]
223223
}
224224

225-
const createZeroBlock = function (length: number): number[] {
226-
return new Array(length).fill(0)
225+
type Bytes = Uint8Array
226+
227+
const createZeroBlock = function (length: number): Bytes {
228+
// Uint8Array is already zero-filled
229+
return new Uint8Array(length)
227230
}
228231

229-
const R = [0xe1].concat(createZeroBlock(15))
232+
// R = 0xe1 || 15 zero bytes
233+
const R: Bytes = (() => {
234+
const r = new Uint8Array(16)
235+
r[0] = 0xe1
236+
return r
237+
})()
238+
239+
const concatBytes = (...arrays: Bytes[]): Bytes => {
240+
let total = 0
241+
for (const a of arrays) total += a.length
242+
243+
const out = new Uint8Array(total)
244+
let offset = 0
245+
for (const a of arrays) {
246+
out.set(a, offset)
247+
offset += a.length
248+
}
249+
return out
250+
}
230251

231-
export const exclusiveOR = function (block0: number[], block1: number[]): number[] {
252+
export const exclusiveOR = function (block0: Bytes, block1: Bytes): Bytes {
232253
const len = block0.length
233-
const result = new Array(len)
254+
const result = new Uint8Array(len)
234255
for (let i = 0; i < len; i++) {
235-
result[i] = block0[i] ^ block1[i]
256+
result[i] = block0[i] ^ (block1[i] ?? 0)
236257
}
237258
return result
238259
}
239260

240-
const xorInto = function (target: number[], block: number[]): void {
261+
const xorInto = function (target: Bytes, block: Bytes): void {
241262
for (let i = 0; i < target.length; i++) {
242-
target[i] ^= block[i]
263+
target[i] ^= block[i] ?? 0
243264
}
244265
}
245266

246-
export const rightShift = function (block: number[]): number[] {
247-
let i: number
267+
export const rightShift = function (block: Bytes): Bytes {
248268
let carry = 0
249269
let oldCarry = 0
250270

251-
for (i = 0; i < block.length; i++) {
271+
for (let i = 0; i < block.length; i++) {
252272
oldCarry = carry
253273
carry = block[i] & 0x01
254274
block[i] = block[i] >> 1
@@ -261,7 +281,7 @@ export const rightShift = function (block: number[]): number[] {
261281
return block
262282
}
263283

264-
export const multiply = function (block0: number[], block1: number[]): number[] {
284+
export const multiply = function (block0: Bytes, block1: Bytes): Bytes {
265285
const v = block1.slice()
266286
const z = createZeroBlock(16)
267287

@@ -284,28 +304,27 @@ export const multiply = function (block0: number[], block1: number[]): number[]
284304
}
285305

286306
export const incrementLeastSignificantThirtyTwoBits = function (
287-
block: number[]
288-
): number[] {
289-
let i
307+
block: Bytes
308+
): Bytes {
290309
const result = block.slice()
291-
for (i = 15; i !== 11; i--) {
292-
result[i] = result[i] + 1
293310

294-
if (result[i] === 256) {
295-
result[i] = 0
296-
} else {
311+
for (let i = 15; i !== 11; i--) {
312+
result[i] = (result[i] + 1) & 0xff // wrap explicitly
313+
314+
if (result[i] !== 0) {
297315
break
298316
}
299317
}
300318

301319
return result
302320
}
303321

304-
export function ghash (input: number[], hashSubKey: number[]): number[] {
322+
export function ghash (input: Bytes, hashSubKey: Bytes): Bytes {
305323
let result = createZeroBlock(16)
324+
const block = new Uint8Array(16)
306325

307326
for (let i = 0; i < input.length; i += 16) {
308-
const block = result.slice()
327+
block.set(result)
309328
for (let j = 0; j < 16; j++) {
310329
block[j] ^= input[i + j] ?? 0
311330
}
@@ -316,14 +335,14 @@ export function ghash (input: number[], hashSubKey: number[]): number[] {
316335
}
317336

318337
function gctr (
319-
input: number[],
320-
initialCounterBlock: number[],
321-
key: number[]
322-
): number[] {
323-
if (input.length === 0) return []
324-
325-
const output = new Array(input.length)
326-
let counterBlock = initialCounterBlock
338+
input: Bytes,
339+
initialCounterBlock: Bytes,
340+
key: Bytes
341+
): Bytes {
342+
if (input.length === 0) return new Uint8Array(0)
343+
344+
const output = new Uint8Array(input.length)
345+
let counterBlock = initialCounterBlock.slice()
327346
let pos = 0
328347
const n = Math.ceil(input.length / 16)
329348

@@ -343,6 +362,42 @@ function gctr (
343362
return output
344363
}
345364

365+
function buildAuthInput (cipherText: Bytes): Bytes {
366+
const aadLenBits = 0
367+
const ctLenBits = cipherText.length * 8
368+
369+
const padLen =
370+
cipherText.length === 0
371+
? 16
372+
: (cipherText.length % 16 === 0 ? 0 : 16 - (cipherText.length % 16))
373+
374+
const total =
375+
16 +
376+
cipherText.length +
377+
padLen +
378+
16
379+
380+
const out = new Uint8Array(total)
381+
let offset = 0
382+
383+
offset += 16
384+
385+
out.set(cipherText, offset)
386+
offset += cipherText.length
387+
388+
offset += padLen
389+
390+
const aadLen = getBytes64(aadLenBits)
391+
out.set(aadLen, offset)
392+
offset += 8
393+
394+
const ctLen = getBytes64(ctLenBits)
395+
out.set(ctLen, offset)
396+
offset += 8
397+
398+
return out
399+
}
400+
346401
/**
347402
* SECURITY NOTE – NON-STANDARD AES-GCM PADDING
348403
*
@@ -391,10 +446,10 @@ function gctr (
391446
* undecryptable by newer versions of the library.
392447
*/
393448
export function AESGCM (
394-
plainText: number[],
395-
initializationVector: number[],
396-
key: number[]
397-
): { result: number[], authenticationTag: number[] } {
449+
plainText: Bytes,
450+
initializationVector: Bytes,
451+
key: Bytes
452+
): { result: Bytes, authenticationTag: Bytes } {
398453
if (initializationVector.length === 0) {
399454
throw new Error('Initialization vector must not be empty')
400455
}
@@ -403,54 +458,54 @@ export function AESGCM (
403458
throw new Error('Key must not be empty')
404459
}
405460

406-
let preCounterBlock
407-
let plainTag: number[] = []
408-
const hashSubKey = AES(createZeroBlock(16), key)
409-
preCounterBlock = [...initializationVector]
461+
const hashSubKey = new Uint8Array(AES(createZeroBlock(16), key))
462+
463+
let preCounterBlock: Bytes
464+
410465
if (initializationVector.length === 12) {
411-
preCounterBlock = preCounterBlock.concat(createZeroBlock(3)).concat([0x01])
466+
preCounterBlock = concatBytes(initializationVector, createZeroBlock(3), new Uint8Array([0x01]))
412467
} else {
413-
if (initializationVector.length % 16 !== 0) {
414-
preCounterBlock = preCounterBlock.concat(
415-
createZeroBlock(16 - (initializationVector.length % 16))
468+
let ivPadded = initializationVector
469+
if (ivPadded.length % 16 !== 0) {
470+
ivPadded = concatBytes(
471+
ivPadded,
472+
createZeroBlock(16 - (ivPadded.length % 16))
416473
)
417474
}
418475

419-
preCounterBlock = preCounterBlock.concat(createZeroBlock(8))
420-
421-
preCounterBlock = ghash(
422-
preCounterBlock.concat(getBytes64(initializationVector.length * 8)),
423-
hashSubKey
476+
const lenBlock = getBytes64(initializationVector.length * 8)
477+
const s = concatBytes(
478+
ivPadded,
479+
createZeroBlock(8),
480+
new Uint8Array(lenBlock)
424481
)
425-
}
426482

427-
const cipherText = gctr(plainText, incrementLeastSignificantThirtyTwoBits(preCounterBlock), key)
483+
preCounterBlock = ghash(s, hashSubKey)
484+
}
428485

429-
plainTag = plainTag.concat(createZeroBlock(16))
430-
plainTag = plainTag.concat(cipherText)
486+
const cipherText = gctr(
487+
plainText,
488+
incrementLeastSignificantThirtyTwoBits(preCounterBlock),
489+
key
490+
)
431491

432-
if (cipherText.length === 0) {
433-
plainTag = plainTag.concat(createZeroBlock(16))
434-
} else if (cipherText.length % 16 !== 0) {
435-
plainTag = plainTag.concat(createZeroBlock(16 - (cipherText.length % 16)))
436-
}
492+
const authInput = buildAuthInput(cipherText)
437493

438-
plainTag = plainTag
439-
.concat(getBytes64(0))
440-
.concat(getBytes64(cipherText.length * 8))
494+
const s = ghash(authInput, hashSubKey)
495+
const authenticationTag = gctr(s, preCounterBlock, key)
441496

442497
return {
443498
result: cipherText,
444-
authenticationTag: gctr(ghash(plainTag, hashSubKey), preCounterBlock, key)
499+
authenticationTag
445500
}
446501
}
447502

448503
export function AESGCMDecrypt (
449-
cipherText: number[],
450-
initializationVector: number[],
451-
authenticationTag: number[],
452-
key: number[]
453-
): number[] | null {
504+
cipherText: Bytes,
505+
initializationVector: Bytes,
506+
authenticationTag: Bytes,
507+
key: Bytes
508+
): Bytes | null {
454509
if (cipherText.length === 0) {
455510
throw new Error('Cipher text must not be empty')
456511
}
@@ -463,49 +518,57 @@ export function AESGCMDecrypt (
463518
throw new Error('Key must not be empty')
464519
}
465520

466-
let preCounterBlock
467-
let compareTag: number[] = []
468-
469521
// Generate the hash subkey
470-
const hashSubKey = AES(createZeroBlock(16), key)
522+
const hashSubKey = new Uint8Array(AES(createZeroBlock(16), key))
523+
524+
let preCounterBlock: Bytes
471525

472-
preCounterBlock = [...initializationVector]
473526
if (initializationVector.length === 12) {
474-
preCounterBlock = preCounterBlock.concat(createZeroBlock(3)).concat([0x01])
527+
preCounterBlock = concatBytes(
528+
initializationVector,
529+
createZeroBlock(3),
530+
new Uint8Array([0x01])
531+
)
475532
} else {
476-
if (initializationVector.length % 16 !== 0) {
477-
preCounterBlock = preCounterBlock.concat(createZeroBlock(16 - (initializationVector.length % 16)))
533+
let ivPadded = initializationVector
534+
if (ivPadded.length % 16 !== 0) {
535+
ivPadded = concatBytes(
536+
ivPadded,
537+
createZeroBlock(16 - (ivPadded.length % 16))
538+
)
478539
}
479540

480-
preCounterBlock = preCounterBlock.concat(createZeroBlock(8))
481-
482-
preCounterBlock = ghash(
483-
preCounterBlock.concat(getBytes64(initializationVector.length * 8)),
484-
hashSubKey
541+
const lenBlock = getBytes64(initializationVector.length * 8)
542+
const s = concatBytes(
543+
ivPadded,
544+
createZeroBlock(8),
545+
new Uint8Array(lenBlock)
485546
)
547+
548+
preCounterBlock = ghash(s, hashSubKey)
486549
}
487550

488551
// Decrypt to obtain the plain text
489-
const plainText = gctr(cipherText, incrementLeastSignificantThirtyTwoBits(preCounterBlock), key)
552+
const plainText = gctr(
553+
cipherText,
554+
incrementLeastSignificantThirtyTwoBits(preCounterBlock),
555+
key
556+
)
490557

491-
compareTag = compareTag.concat(createZeroBlock(16))
492-
compareTag = compareTag.concat(cipherText)
558+
const authInput = buildAuthInput(cipherText)
559+
const s = ghash(authInput, hashSubKey)
560+
const calculatedTag = gctr(s, preCounterBlock, key)
493561

494-
if (cipherText.length === 0) {
495-
compareTag = compareTag.concat(createZeroBlock(16))
496-
} else if (cipherText.length % 16 !== 0) {
497-
compareTag = compareTag.concat(createZeroBlock(16 - (cipherText.length % 16)))
562+
if (calculatedTag.length !== authenticationTag.length) {
563+
return null
498564
}
499565

500-
compareTag = compareTag
501-
.concat(getBytes64(0))
502-
.concat(getBytes64(cipherText.length * 8))
503-
504-
// Generate the authentication tag
505-
const calculatedTag = gctr(ghash(compareTag, hashSubKey), preCounterBlock, key)
566+
let diff = 0
567+
for (let i = 0; i < calculatedTag.length; i++) {
568+
diff |= calculatedTag[i] ^ authenticationTag[i]
569+
}
506570

507-
// If the calculated tag does not match the provided tag, return null - the decryption failed.
508-
if (calculatedTag.join() !== authenticationTag.join()) {
571+
if (diff !== 0) {
509572
return null
510573
}
511574

0 commit comments

Comments
 (0)