diff --git a/src/chacha.js b/src/chacha.js --- a/src/chacha.js +++ b/src/chacha.js @@ -1,4 +1,5 @@ // https://tools.ietf.org/html/rfc7539 +import {MASK,int32s2bytes,bytes2int32s} from "./util.js"; function lrot(x,shift){ return (x<>>(32-shift))&MASK; @@ -10,18 +11,28 @@ function createNonce(){ return nonce; } +function zeroPad(arr,length){ + return arr.concat((new Array(length)).fill(0)).slice(0,length); +} + +/** + * A Chacha20 cipher class. + * @param {Array} key Array of bytes (integers: 0<=x<256). Short keys are padded to 32B, long keys are silently truncated. + * @param {Array} nonce optional. If present, it must be an Array of bytes (integers: 0<=x<256). Short nonces are padded to 12B, long nonces are silently truncated. + */ export function Chacha20(key,nonce){ if(nonce===undefined){ - let nonce=createNonce(); + nonce=createNonce(); } - nonce=bytes2int32s(nonce); - key=bytes2int32s(key); + this._nonce=zeroPad(nonce,12); + nonce=bytes2int32s(this._nonce); + key=bytes2int32s(zeroPad(key,32)); this._state=[ 0x61707865,0x3320646e,0x79622d32,0x6b206574, key[0],key[1],key[2],key[3], key[4],key[5],key[6],key[7], - 0,nonce[0],nonce[1],nonce[2] + 1,nonce[0],nonce[1],nonce[2] ]; this._buffer=[]; } @@ -34,10 +45,15 @@ Chacha20.prototype.setPos=function(pos){ Chacha20.prototype.getByte=function(){ if(this._buffer.length==0){ this._buffer=int32s2bytes(this._computeBlock()); + this._incrementPos(); } return this._buffer.shift(); }; +Chacha20.prototype.getNonce=function(){ + return this._nonce.concat(); +}; + Chacha20.prototype._quarterRound=function(arr,ia,ib,ic,id){ let a=arr[ia], b=arr[ib], c=arr[ic], d=arr[id]; a=(a+b)&MASK; d=lrot(d^a,16); @@ -70,12 +86,15 @@ Chacha20.prototype._incrementPos=functio this._state[12]=(this._state[12]+1)&MASK; }; -function testChacha(){ - let key=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31]; - let nonce=[0,0,0,9,0,0,0,74,0,0,0,0]; +export function encrypt(data,key,nonce){ let cipher=new Chacha20(key,nonce); - cipher.setPos(1); - let output=cipher._computeBlock().map(x=>(x>>>0).toString(16).padStart(8,"0")); - let example=["e4e7f110","15593bd1","1fdd0f50","c47120a3","c7f4d1c7","0368c033","9aaa2204","4e6cd4c3","466482d2","09aa9f07","05d7c214","a2028bd9","d19c12b5","b94e16de","e883d0cb","4e3c50a2"]; - console.log(output.every((x,i)=>x==example[i])); + nonce=cipher.getNonce(); + return nonce.concat(data.map(b=>b^cipher.getByte())); } + +export function decrypt(data,key){ + let nonce=data.slice(0,12); + let ciphertext=data.slice(12); + let cipher=new Chacha20(key,nonce); + return ciphertext.map(b=>b^cipher.getByte()); +}