118 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			118 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| var aes = require('./aes')
 | |
| var Buffer = require('safe-buffer').Buffer
 | |
| var Transform = require('cipher-base')
 | |
| var inherits = require('inherits')
 | |
| var GHASH = require('./ghash')
 | |
| var xor = require('buffer-xor')
 | |
| var incr32 = require('./incr32')
 | |
| 
 | |
| function xorTest (a, b) {
 | |
|   var out = 0
 | |
|   if (a.length !== b.length) out++
 | |
| 
 | |
|   var len = Math.min(a.length, b.length)
 | |
|   for (var i = 0; i < len; ++i) {
 | |
|     out += (a[i] ^ b[i])
 | |
|   }
 | |
| 
 | |
|   return out
 | |
| }
 | |
| 
 | |
| function calcIv (self, iv, ck) {
 | |
|   if (iv.length === 12) {
 | |
|     self._finID = Buffer.concat([iv, Buffer.from([0, 0, 0, 1])])
 | |
|     return Buffer.concat([iv, Buffer.from([0, 0, 0, 2])])
 | |
|   }
 | |
|   var ghash = new GHASH(ck)
 | |
|   var len = iv.length
 | |
|   var toPad = len % 16
 | |
|   ghash.update(iv)
 | |
|   if (toPad) {
 | |
|     toPad = 16 - toPad
 | |
|     ghash.update(Buffer.alloc(toPad, 0))
 | |
|   }
 | |
|   ghash.update(Buffer.alloc(8, 0))
 | |
|   var ivBits = len * 8
 | |
|   var tail = Buffer.alloc(8)
 | |
|   tail.writeUIntBE(ivBits, 0, 8)
 | |
|   ghash.update(tail)
 | |
|   self._finID = ghash.state
 | |
|   var out = Buffer.from(self._finID)
 | |
|   incr32(out)
 | |
|   return out
 | |
| }
 | |
| function StreamCipher (mode, key, iv, decrypt) {
 | |
|   Transform.call(this)
 | |
| 
 | |
|   var h = Buffer.alloc(4, 0)
 | |
| 
 | |
|   this._cipher = new aes.AES(key)
 | |
|   var ck = this._cipher.encryptBlock(h)
 | |
|   this._ghash = new GHASH(ck)
 | |
|   iv = calcIv(this, iv, ck)
 | |
| 
 | |
|   this._prev = Buffer.from(iv)
 | |
|   this._cache = Buffer.allocUnsafe(0)
 | |
|   this._secCache = Buffer.allocUnsafe(0)
 | |
|   this._decrypt = decrypt
 | |
|   this._alen = 0
 | |
|   this._len = 0
 | |
|   this._mode = mode
 | |
| 
 | |
|   this._authTag = null
 | |
|   this._called = false
 | |
| }
 | |
| 
 | |
| inherits(StreamCipher, Transform)
 | |
| 
 | |
| StreamCipher.prototype._update = function (chunk) {
 | |
|   if (!this._called && this._alen) {
 | |
|     var rump = 16 - (this._alen % 16)
 | |
|     if (rump < 16) {
 | |
|       rump = Buffer.alloc(rump, 0)
 | |
|       this._ghash.update(rump)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   this._called = true
 | |
|   var out = this._mode.encrypt(this, chunk)
 | |
|   if (this._decrypt) {
 | |
|     this._ghash.update(chunk)
 | |
|   } else {
 | |
|     this._ghash.update(out)
 | |
|   }
 | |
|   this._len += chunk.length
 | |
|   return out
 | |
| }
 | |
| 
 | |
| StreamCipher.prototype._final = function () {
 | |
|   if (this._decrypt && !this._authTag) throw new Error('Unsupported state or unable to authenticate data')
 | |
| 
 | |
|   var tag = xor(this._ghash.final(this._alen * 8, this._len * 8), this._cipher.encryptBlock(this._finID))
 | |
|   if (this._decrypt && xorTest(tag, this._authTag)) throw new Error('Unsupported state or unable to authenticate data')
 | |
| 
 | |
|   this._authTag = tag
 | |
|   this._cipher.scrub()
 | |
| }
 | |
| 
 | |
| StreamCipher.prototype.getAuthTag = function getAuthTag () {
 | |
|   if (this._decrypt || !Buffer.isBuffer(this._authTag)) throw new Error('Attempting to get auth tag in unsupported state')
 | |
| 
 | |
|   return this._authTag
 | |
| }
 | |
| 
 | |
| StreamCipher.prototype.setAuthTag = function setAuthTag (tag) {
 | |
|   if (!this._decrypt) throw new Error('Attempting to set auth tag in unsupported state')
 | |
| 
 | |
|   this._authTag = tag
 | |
| }
 | |
| 
 | |
| StreamCipher.prototype.setAAD = function setAAD (buf) {
 | |
|   if (this._called) throw new Error('Attempting to set AAD in unsupported state')
 | |
| 
 | |
|   this._ghash.update(buf)
 | |
|   this._alen += buf.length
 | |
| }
 | |
| 
 | |
| module.exports = StreamCipher
 |