// https://tools.ietf.org/html/rfc7539 import {MASK,int32s2bytes,bytes2int32s,zeroPad} from "./util.js"; function lrot(x,shift){ return (x<>>(32-shift))&MASK; } function createNonce(){ let nonce=new Uint8Array(12); window.crypto.getRandomValues(nonce); return nonce; } /** * 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){ nonce=createNonce(); } 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], 1,nonce[0],nonce[1],nonce[2] ]; this._buffer=[]; } Chacha20.prototype.setPos=function(pos){ this._state[12]=pos; this._buffer=[]; } 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); c=(c+d)&MASK; b=lrot(b^c,12); a=(a+b)&MASK; d=lrot(d^a,8); c=(c+d)&MASK; b=lrot(b^c,7); arr[ia]=a; arr[ib]=b; arr[ic]=c; arr[id]=d; }; Chacha20.prototype._computeBlock=function(){ let state=this._state.slice(); for(let i=0;i<10;i++){ // 10 double rounds // column round this._quarterRound(state,0,4,8,12); this._quarterRound(state,1,5,9,13); this._quarterRound(state,2,6,10,14); this._quarterRound(state,3,7,11,15); // diagonal round this._quarterRound(state,0,5,10,15); this._quarterRound(state,1,6,11,12); this._quarterRound(state,2,7,8,13); this._quarterRound(state,3,4,9,14); } return state.map((xi,i)=>(xi+this._state[i])&MASK); }; Chacha20.prototype._incrementPos=function(){ this._state[12]=(this._state[12]+1)&MASK; }; export function encrypt(data,key,nonce){ let cipher=new Chacha20(key,nonce); 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()); }