2022-11-01 18:07:16 +01:00
/ * *
* @ author mikecat
* @ copyright Crown Copyright 2022
* @ license Apache - 2.0
* /
import Operation from "../Operation.mjs" ;
import Utils from "../Utils.mjs" ;
import forge from "node-forge" ;
import { toHexFast } from "../lib/Hex.mjs" ;
import OperationError from "../errors/OperationError.mjs" ;
/ * *
* CMAC operation
* /
class CMAC extends Operation {
/ * *
* CMAC constructor
* /
constructor ( ) {
super ( ) ;
this . name = "CMAC" ;
this . module = "Crypto" ;
this . description = "CMAC is a block-cipher based message authentication code algorithm.<br><br>RFC4493 defines AES-CMAC that uses AES encryption with a 128-bit key.<br>NIST SP 800-38B suggests usages of AES with other key lengths and Triple DES." ;
this . infoURL = "https://wikipedia.org/wiki/CMAC" ;
this . inputType = "ArrayBuffer" ;
this . outputType = "string" ;
this . args = [
{
"name" : "Key" ,
"type" : "toggleString" ,
"value" : "" ,
"toggleValues" : [ "Hex" , "UTF8" , "Latin1" , "Base64" ]
} ,
{
"name" : "Encryption algorithm" ,
"type" : "option" ,
"value" : [ "AES" , "Triple DES" ]
}
] ;
}
/ * *
* @ param { ArrayBuffer } input
* @ param { Object [ ] } args
* @ returns { string }
* /
run ( input , args ) {
const key = Utils . convertToByteString ( args [ 0 ] . string , args [ 0 ] . option ) ;
const info = ( function ( ) {
switch ( args [ 1 ] ) {
case "AES" :
if ( key . length !== 16 && key . length !== 24 && key . length !== 32 ) {
throw new OperationError ( "the key for AES must be either 16, 24, or 32 bytes (currently " + key . length + " bytes)" ) ;
}
return {
"algorithm" : "AES-ECB" ,
2022-11-02 17:20:37 +01:00
"key" : key ,
2022-11-01 18:07:16 +01:00
"blockSize" : 16 ,
"Rb" : new Uint8Array ( [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0x87 ] ) ,
} ;
case "Triple DES" :
2022-11-02 17:20:37 +01:00
if ( key . length !== 16 && key . length !== 24 ) {
throw new OperationError ( "the key for Triple DES must be 16 or 24 bytes (currently " + key . length + " bytes)" ) ;
2022-11-01 18:07:16 +01:00
}
return {
"algorithm" : "3DES-ECB" ,
2022-11-02 17:20:37 +01:00
"key" : key . length === 16 ? key + key . substring ( 0 , 8 ) : key ,
2022-11-01 18:07:16 +01:00
"blockSize" : 8 ,
"Rb" : new Uint8Array ( [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0x1b ] ) ,
} ;
default :
throw new OperationError ( "undefined encryption algorithm" ) ;
}
} ) ( ) ;
const xor = function ( a , b , out ) {
if ( ! out ) out = new Uint8Array ( a . length ) ;
for ( let i = 0 ; i < a . length ; i ++ ) {
out [ i ] = a [ i ] ^ b [ i ] ;
}
return out ;
} ;
const leftShift1 = function ( a ) {
const out = new Uint8Array ( a . length ) ;
let carry = 0 ;
for ( let i = a . length - 1 ; i >= 0 ; i -- ) {
out [ i ] = ( a [ i ] << 1 ) | carry ;
carry = a [ i ] >> 7 ;
}
return out ;
} ;
2022-11-02 17:20:37 +01:00
const cipher = forge . cipher . createCipher ( info . algorithm , info . key ) ;
2022-11-01 18:07:16 +01:00
const encrypt = function ( a , out ) {
if ( ! out ) out = new Uint8Array ( a . length ) ;
cipher . start ( ) ;
cipher . update ( forge . util . createBuffer ( a ) ) ;
cipher . finish ( ) ;
const cipherText = cipher . output . getBytes ( ) ;
for ( let i = 0 ; i < a . length ; i ++ ) {
out [ i ] = cipherText . charCodeAt ( i ) ;
}
return out ;
} ;
const L = encrypt ( new Uint8Array ( info . blockSize ) ) ;
const K1 = leftShift1 ( L ) ;
if ( L [ 0 ] & 0x80 ) xor ( K1 , info . Rb , K1 ) ;
const K2 = leftShift1 ( K1 ) ;
if ( K1 [ 0 ] & 0x80 ) xor ( K2 , info . Rb , K2 ) ;
const n = Math . ceil ( input . byteLength / info . blockSize ) ;
const lastBlock = ( function ( ) {
if ( n === 0 ) {
const data = new Uint8Array ( K2 ) ;
data [ 0 ] ^= 0x80 ;
return data ;
}
const inputLast = new Uint8Array ( input , info . blockSize * ( n - 1 ) ) ;
if ( inputLast . length === info . blockSize ) {
return xor ( inputLast , K1 , inputLast ) ;
} else {
const data = new Uint8Array ( info . blockSize ) ;
data . set ( inputLast , 0 ) ;
data [ inputLast . length ] = 0x80 ;
return xor ( data , K2 , data ) ;
}
} ) ( ) ;
const X = new Uint8Array ( info . blockSize ) ;
const Y = new Uint8Array ( info . blockSize ) ;
for ( let i = 0 ; i < n - 1 ; i ++ ) {
xor ( X , new Uint8Array ( input , info . blockSize * i , info . blockSize ) , Y ) ;
encrypt ( Y , X ) ;
}
xor ( lastBlock , X , Y ) ;
const T = encrypt ( Y ) ;
return toHexFast ( T ) ;
}
}
export default CMAC ;