import { increment } from "./bufferUtils" ; import { encryptAES128ECB } from "./crypto" ; const MAX_GENERATE_LENGTH = 65536 ; export class CtrDRBG { constructor ( bits , derivation , entropy ? , nonce ? , pers ? ) { this . ctr = Buffer . alloc ( 16 , 0 ) ; this . keySize = bits >>> 3 ; this . blkSize = 16 ; this . entSize = this . keySize + this . blkSize ; this . slab = Buffer . alloc ( this . entSize ) ; this . K = this . slab . slice ( 0 , this . keySize ) ; this . V = this . slab . slice ( this . keySize ) ; this . derivation = derivation ; this . initialized = false ; if ( entropy ) this . init ( entropy , nonce , pers ) ; } /** The internal counter */ private ctr : Buffer ; private readonly keySize : number ; private readonly blkSize : number ; private readonly entSize : number ; private slab : Buffer ; private K : Buffer ; private V : Buffer ; private readonly derivation : boolean ; private initialized : boolean ; init ( entropy , nonce = Buffer . alloc ( 0 ) , pers = Buffer . alloc ( 0 ) ) { let seed ; if ( this . derivation ) { seed = this . derive ( entropy , nonce , pers ) ; } else { if ( entropy . length + nonce . length > this . entSize ) throw new Error ( "Entropy is too long." ) ; if ( pers . length > this . entSize ) throw new Error ( "Personalization string is too long." ) ; seed = Buffer . alloc ( this . entSize , 0 ) ; entropy . copy ( seed , 0 ) ; nonce . copy ( seed , entropy . length ) ; for ( let i = 0 ; i < pers . length ; i ++ ) seed [ i ] ^= pers [ i ] ; } this . slab . fill ( 0 ) ; this . V . copy ( this . ctr , 0 ) ; this . update ( seed ) ; this . initialized = true ; return this ; } reseed ( entropy , add = Buffer . alloc ( 0 ) ) { if ( ! this . initialized ) throw new Error ( "DRBG not initialized." ) ; let seed ; if ( this . derivation ) { seed = this . derive ( entropy , add ) ; } else { if ( add . length > this . entSize ) throw new Error ( "Additional data is too long." ) ; seed = Buffer . alloc ( this . entSize , 0x00 ) ; entropy . copy ( seed , 0 ) ; for ( let i = 0 ; i < add . length ; i ++ ) seed [ i ] ^= add [ i ] ; } this . update ( seed ) ; return this ; } private next ( ) { increment ( this . ctr ) ; return encryptAES128ECB ( this . ctr , this . K ) ; } generate ( len , add ? ) { if ( ! this . initialized ) throw new Error ( "DRBG not initialized." ) ; if ( len > MAX_GENERATE_LENGTH ) throw new Error ( "Requested length is too long." ) ; if ( add && add . length > 0 ) { if ( this . derivation ) add = this . derive ( add ) ; this . update ( add ) ; } const blocks = Math . ceil ( len / this . blkSize ) ; const out = Buffer . alloc ( blocks * this . blkSize ) ; for ( let i = 0 ; i < blocks ; i ++ ) { const ciphertext = this . next ( ) ; ciphertext . copy ( out , i * this . blkSize ) ; } this . update ( add ) ; this . initialized = true ; return out . slice ( 0 , len ) ; } update ( seed = Buffer . alloc ( 0 ) ) { if ( seed . length > this . entSize ) throw new Error ( "Seed is too long." ) ; const newSlab = Buffer . alloc ( this . slab . length , 0 ) ; for ( let i = 0 ; i < this . entSize ; i += this . blkSize ) { this . next ( ) . copy ( newSlab , i ) ; } for ( let i = 0 ; i < seed . length ; i ++ ) newSlab [ i ] ^= seed [ i ] ; newSlab . copy ( this . slab , 0 ) ; this . V . copy ( this . ctr , 0 ) ; return this ; } serialize ( ... input ) { const N = this . entSize ; let L = 0 ; for ( const item of input ) L += item . length ; let size = this . blkSize + 4 + 4 + L + 1 ; if ( size % this . blkSize ) size += this . blkSize - ( size % this . blkSize ) ; const S = Buffer . alloc ( size , 0x00 ) ; let pos = this . blkSize ; S . writeUInt32BE ( L , pos ) ; pos += 4 ; S . writeUInt32BE ( N , pos ) ; pos += 4 ; for ( const item of input ) pos += item . copy ( S , pos ) ; S [ pos ++ ] = 0x80 ; return S ; } derive ( ... input ) { const S = this . serialize ( ... input ) ; const N = S . length / this . blkSize ; const K = Buffer . alloc ( this . keySize ) ; const blocks = Math . ceil ( this . entSize / this . blkSize ) ; const slab = Buffer . alloc ( blocks * this . blkSize ) ; const out = Buffer . alloc ( blocks * this . blkSize ) ; const chain = Buffer . alloc ( this . blkSize ) ; for ( let i = 0 ; i < K . length ; i ++ ) K [ i ] = i ; for ( let i = 0 ; i < blocks ; i ++ ) { chain . fill ( 0 ) ; S . writeUInt32BE ( i , 0 ) ; for ( let j = 0 ; j < N ; j ++ ) { for ( let k = 0 ; k < chain . length ; k ++ ) chain [ k ] ^= S [ j * this . blkSize + k ] ; encryptAES128ECB ( chain , K ) . copy ( chain , 0 ) ; } chain . copy ( slab , i * this . blkSize ) ; } const k = slab . slice ( 0 , this . keySize ) ; const x = slab . slice ( this . keySize , this . entSize ) ; for ( let i = 0 ; i < blocks ; i ++ ) { encryptAES128ECB ( x , k ) . copy ( x , 0 ) ; x . copy ( out , i * this . blkSize ) ; } return out . slice ( 0 , this . entSize ) ; } }