@@ -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
286306export 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
318337function 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 */
393448export 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
448503export 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