2022-10-28 20:09:41 +02:00
/ * *
* @ author mikecat
* @ copyright Crown Copyright 2022
* @ license Apache - 2.0
* /
import Operation from "../Operation.mjs" ;
import Utils from "../Utils.mjs" ;
import { toHexFast } from "../lib/Hex.mjs" ;
import OperationError from "../errors/OperationError.mjs" ;
/ * *
* Rabbit Stream Cipher operation
* /
class RabbitStreamCipher extends Operation {
/ * *
* RabbitStreamCipher constructor
* /
constructor ( ) {
super ( ) ;
this . name = "Rabbit Stream Cipher" ;
this . module = "Ciphers" ;
this . description = "Rabbit Stream Cipher, a stream cipher algorithm defined in RFC4503.<br><br>The cipher uses a 128-bit key and an optional 64-bit initialization vector (IV).<br><br>big-endian: based on RFC4503 and RFC3447<br>little-endian: compatible with Crypto++" ;
this . infoURL = "https://wikipedia.org/wiki/Rabbit_(cipher)" ;
this . inputType = "string" ;
this . outputType = "string" ;
this . args = [
{
"name" : "Key" ,
"type" : "toggleString" ,
"value" : "" ,
"toggleValues" : [ "Hex" , "UTF8" , "Latin1" , "Base64" ]
} ,
{
"name" : "IV" ,
"type" : "toggleString" ,
"value" : "" ,
"toggleValues" : [ "Hex" , "UTF8" , "Latin1" , "Base64" ]
} ,
{
"name" : "Endianness" ,
"type" : "option" ,
"value" : [ "Big" , "Little" ]
} ,
{
"name" : "Input" ,
"type" : "option" ,
"value" : [ "Raw" , "Hex" ]
} ,
{
"name" : "Output" ,
"type" : "option" ,
"value" : [ "Raw" , "Hex" ]
}
] ;
}
/ * *
* @ param { string } input
* @ param { Object [ ] } args
* @ returns { string }
* /
run ( input , args ) {
const key = Utils . convertToByteArray ( args [ 0 ] . string , args [ 0 ] . option ) ,
iv = Utils . convertToByteArray ( args [ 1 ] . string , args [ 1 ] . option ) ,
endianness = args [ 2 ] ,
inputType = args [ 3 ] ,
outputType = args [ 4 ] ;
const littleEndian = endianness === "Little" ;
if ( key . length !== 16 ) {
throw new OperationError ( ` Invalid key length: ${ key . length } bytes (expected: 16) ` ) ;
}
if ( iv . length !== 0 && iv . length !== 8 ) {
throw new OperationError ( ` Invalid IV length: ${ iv . length } bytes (expected: 0 or 8) ` ) ;
}
// Inner State
2022-10-28 20:33:30 +02:00
const X = new Uint32Array ( 8 ) , C = new Uint32Array ( 8 ) ;
2022-10-28 20:09:41 +02:00
let b = 0 ;
// Counter System
const A = [
0x4d34d34d , 0xd34d34d3 , 0x34d34d34 , 0x4d34d34d ,
0xd34d34d3 , 0x34d34d34 , 0x4d34d34d , 0xd34d34d3
] ;
const counterUpdate = function ( ) {
for ( let j = 0 ; j < 8 ; j ++ ) {
const temp = C [ j ] + A [ j ] + b ;
b = ( temp / ( ( 1 << 30 ) * 4 ) ) >>> 0 ;
2022-10-28 20:33:30 +02:00
C [ j ] = temp ;
2022-10-28 20:09:41 +02:00
}
} ;
// Next-State Function
const g = function ( u , v ) {
const uv = ( u + v ) >>> 0 ;
const upper = uv >>> 16 , lower = uv & 0xffff ;
const upperUpper = upper * upper ;
const upperLower2 = 2 * upper * lower ;
const lowerLower = lower * lower ;
const mswTemp = upperUpper + ( ( upperLower2 / ( 1 << 16 ) ) >>> 0 ) ;
const lswTemp = lowerLower + ( upperLower2 & 0xffff ) * ( 1 << 16 ) ;
const msw = mswTemp + ( ( lswTemp / ( ( 1 << 30 ) * 4 ) ) >>> 0 ) ;
const lsw = lswTemp >>> 0 ;
2022-10-28 20:33:30 +02:00
return lsw ^ msw ;
2022-10-28 20:09:41 +02:00
} ;
const leftRotate = function ( value , width ) {
return ( value << width ) | ( value >>> ( 32 - width ) ) ;
} ;
const nextStateHelper1 = function ( v0 , v1 , v2 ) {
2022-10-28 20:33:30 +02:00
return v0 + leftRotate ( v1 , 16 ) + leftRotate ( v2 , 16 ) ;
2022-10-28 20:09:41 +02:00
} ;
const nextStateHelper2 = function ( v0 , v1 , v2 ) {
2022-10-28 20:33:30 +02:00
return v0 + leftRotate ( v1 , 8 ) + v2 ;
2022-10-28 20:09:41 +02:00
} ;
2022-10-28 20:33:30 +02:00
const G = new Uint32Array ( 8 ) ;
2022-10-28 20:09:41 +02:00
const nextState = function ( ) {
for ( let j = 0 ; j < 8 ; j ++ ) {
G [ j ] = g ( X [ j ] , C [ j ] ) ;
}
X [ 0 ] = nextStateHelper1 ( G [ 0 ] , G [ 7 ] , G [ 6 ] ) ;
X [ 1 ] = nextStateHelper2 ( G [ 1 ] , G [ 0 ] , G [ 7 ] ) ;
X [ 2 ] = nextStateHelper1 ( G [ 2 ] , G [ 1 ] , G [ 0 ] ) ;
X [ 3 ] = nextStateHelper2 ( G [ 3 ] , G [ 2 ] , G [ 1 ] ) ;
X [ 4 ] = nextStateHelper1 ( G [ 4 ] , G [ 3 ] , G [ 2 ] ) ;
X [ 5 ] = nextStateHelper2 ( G [ 5 ] , G [ 4 ] , G [ 3 ] ) ;
X [ 6 ] = nextStateHelper1 ( G [ 6 ] , G [ 5 ] , G [ 4 ] ) ;
X [ 7 ] = nextStateHelper2 ( G [ 7 ] , G [ 6 ] , G [ 5 ] ) ;
} ;
// Key Setup Scheme
2022-10-28 20:33:30 +02:00
const K = new Uint16Array ( 8 ) ;
2022-10-28 20:09:41 +02:00
if ( littleEndian ) {
for ( let i = 0 ; i < 8 ; i ++ ) {
2022-10-28 20:33:30 +02:00
K [ i ] = ( key [ 1 + 2 * i ] << 8 ) | key [ 2 * i ] ;
2022-10-28 20:09:41 +02:00
}
} else {
for ( let i = 0 ; i < 8 ; i ++ ) {
2022-10-28 20:33:30 +02:00
K [ i ] = ( key [ 14 - 2 * i ] << 8 ) | key [ 15 - 2 * i ] ;
2022-10-28 20:09:41 +02:00
}
}
for ( let j = 0 ; j < 8 ; j ++ ) {
if ( j % 2 === 0 ) {
2022-10-28 20:33:30 +02:00
X [ j ] = ( K [ ( j + 1 ) % 8 ] << 16 ) | K [ j ] ;
C [ j ] = ( K [ ( j + 4 ) % 8 ] << 16 ) | K [ ( j + 5 ) % 8 ] ;
2022-10-28 20:09:41 +02:00
} else {
2022-10-28 20:33:30 +02:00
X [ j ] = ( K [ ( j + 5 ) % 8 ] << 16 ) | K [ ( j + 4 ) % 8 ] ;
C [ j ] = ( K [ j ] << 16 ) | K [ ( j + 1 ) % 8 ] ;
2022-10-28 20:09:41 +02:00
}
}
for ( let i = 0 ; i < 4 ; i ++ ) {
counterUpdate ( ) ;
nextState ( ) ;
}
for ( let j = 0 ; j < 8 ; j ++ ) {
2022-10-28 20:33:30 +02:00
C [ j ] = C [ j ] ^ X [ ( j + 4 ) % 8 ] ;
2022-10-28 20:09:41 +02:00
}
// IV Setup Scheme
if ( iv . length === 8 ) {
const getIVValue = function ( a , b , c , d ) {
if ( littleEndian ) {
2022-10-28 20:33:30 +02:00
return ( iv [ a ] << 24 ) | ( iv [ b ] << 16 ) |
( iv [ c ] << 8 ) | iv [ d ] ;
2022-10-28 20:09:41 +02:00
} else {
2022-10-28 20:33:30 +02:00
return ( iv [ 7 - a ] << 24 ) | ( iv [ 7 - b ] << 16 ) |
( iv [ 7 - c ] << 8 ) | iv [ 7 - d ] ;
2022-10-28 20:09:41 +02:00
}
} ;
2022-10-28 20:33:30 +02:00
C [ 0 ] = C [ 0 ] ^ getIVValue ( 3 , 2 , 1 , 0 ) ;
C [ 1 ] = C [ 1 ] ^ getIVValue ( 7 , 6 , 3 , 2 ) ;
C [ 2 ] = C [ 2 ] ^ getIVValue ( 7 , 6 , 5 , 4 ) ;
C [ 3 ] = C [ 3 ] ^ getIVValue ( 5 , 4 , 1 , 0 ) ;
C [ 4 ] = C [ 4 ] ^ getIVValue ( 3 , 2 , 1 , 0 ) ;
C [ 5 ] = C [ 5 ] ^ getIVValue ( 7 , 6 , 3 , 2 ) ;
C [ 6 ] = C [ 6 ] ^ getIVValue ( 7 , 6 , 5 , 4 ) ;
C [ 7 ] = C [ 7 ] ^ getIVValue ( 5 , 4 , 1 , 0 ) ;
2022-10-28 20:09:41 +02:00
for ( let i = 0 ; i < 4 ; i ++ ) {
counterUpdate ( ) ;
nextState ( ) ;
}
}
// Extraction Scheme
2022-10-28 20:33:30 +02:00
const S = new Uint8Array ( 16 ) ;
2022-10-28 20:09:41 +02:00
const extract = function ( ) {
let pos = 0 ;
const addPart = function ( value ) {
S [ pos ++ ] = value >>> 8 ;
S [ pos ++ ] = value & 0xff ;
} ;
counterUpdate ( ) ;
nextState ( ) ;
addPart ( ( X [ 6 ] >>> 16 ) ^ ( X [ 1 ] & 0xffff ) ) ;
addPart ( ( X [ 6 ] & 0xffff ) ^ ( X [ 3 ] >>> 16 ) ) ;
addPart ( ( X [ 4 ] >>> 16 ) ^ ( X [ 7 ] & 0xffff ) ) ;
addPart ( ( X [ 4 ] & 0xffff ) ^ ( X [ 1 ] >>> 16 ) ) ;
addPart ( ( X [ 2 ] >>> 16 ) ^ ( X [ 5 ] & 0xffff ) ) ;
addPart ( ( X [ 2 ] & 0xffff ) ^ ( X [ 7 ] >>> 16 ) ) ;
addPart ( ( X [ 0 ] >>> 16 ) ^ ( X [ 3 ] & 0xffff ) ) ;
addPart ( ( X [ 0 ] & 0xffff ) ^ ( X [ 5 ] >>> 16 ) ) ;
if ( littleEndian ) {
for ( let i = 0 , j = S . length - 1 ; i < j ; ) {
const temp = S [ i ] ;
S [ i ] = S [ j ] ;
S [ j ] = temp ;
i ++ ;
j -- ;
}
}
} ;
const data = Utils . convertToByteString ( input , inputType ) ;
const result = new Uint8Array ( data . length ) ;
for ( let i = 0 ; i <= data . length - 16 ; i += 16 ) {
extract ( ) ;
for ( let j = 0 ; j < 16 ; j ++ ) {
result [ i + j ] = data . charCodeAt ( i + j ) ^ S [ j ] ;
}
}
if ( data . length % 16 !== 0 ) {
const offset = data . length - data . length % 16 ;
const length = data . length - offset ;
extract ( ) ;
if ( littleEndian ) {
for ( let j = 0 ; j < length ; j ++ ) {
result [ offset + j ] = data . charCodeAt ( offset + j ) ^ S [ j ] ;
}
} else {
for ( let j = 0 ; j < length ; j ++ ) {
result [ offset + j ] = data . charCodeAt ( offset + j ) ^ S [ 16 - length + j ] ;
}
}
}
if ( outputType === "Hex" ) {
return toHexFast ( result ) ;
}
return Utils . byteArrayToChars ( result ) ;
}
}
export default RabbitStreamCipher ;