Changeset - 51344a25b55d
[Not reviewed]
default
0 3 0
Laman - 5 years ago 2019-06-30 16:33:14

array padding refactored into util
3 files changed with 8 insertions and 12 deletions:
0 comments (0 inline, 0 general)
src/blake.js
Show inline comments
 
// https://tools.ietf.org/html/rfc7693
 
import {MASK,int32s2bytes,bytes2int32s} from "./util.js";
 
import {MASK,int32s2bytes,bytes2int32s,zeroPad} from "./util.js";
 

	
 
const BLOCK_LEN=64;
 

	
 
const IV=[0x6A09E667,0xBB67AE85,0x3C6EF372,0xA54FF53A,0x510E527F,0x9B05688C,0x1F83D9AB,0x5BE0CD19];
 

	
 
function padEnd(arr,length,val=0){
 
	return arr.concat((new Array(length-arr.length)).fill(0));
 
}
 

	
 
function rrot(x,shift){
 
	return ((x>>>shift)|(x<<(32-shift)))&MASK;
 
}
 

	
 
export function BLAKE2S(outputLen=32,key=[]){
 
	this._buffer=[];
 
	this._dataLen=[0,0]; // low, high
 
	this._outputLen=outputLen;
 
	
 
	this._state=IV.slice();
 
	this._state[0]^=0x01010000^(key.length<<8)^this._outputLen;
 
	
 
	if(key.length>0){this.update(padEnd(key,BLOCK_LEN,0));}
 
	if(key.length>0){this.update(zeroPad(key,BLOCK_LEN));}
 
}
 

	
 
BLAKE2S.prototype.update=function(data){
 
	for(let i=0;i<data.length;i++){
 
		if(this._buffer.length==BLOCK_LEN){
 
			this._compress(false);
 
			this._buffer=[];
 
		}
 
		this._buffer.push(data[i]);
 
		this._dataLen[0]=(this._dataLen[0]+1)&MASK;
 
		if(this._dataLen[0]<this._buffer.length){this._dataLen[1]++;}
 
	}
 
};
 

	
 
BLAKE2S.prototype.digest=function(){
 
	this._buffer=padEnd(this._buffer,BLOCK_LEN,0);
 
	this._buffer=zeroPad(this._buffer,BLOCK_LEN);
 
	this._compress(true);
 
	return int32s2bytes(this._state).slice(0,this._outputLen);
 
};
 

	
 
BLAKE2S.prototype._compress=function(last){
 
	const SIGMA=[
 
		[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],
 
		[14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3],
 
		[11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4],
 
		[7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8],
 
		[9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13],
 
		[2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9],
 
		[12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11],
 
		[13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10],
 
		[6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5],
 
		[10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0]
 
	];
 
	let v=this._state.concat(IV);
 
	v[12]^=this._dataLen[0];
 
	v[13]^=this._dataLen[1];
 
	if(last){v[14]^=MASK;}
 
	let data=bytes2int32s(this._buffer);
 

	
 
	for(let i=0;i<10;i++){
 
		let perm=SIGMA[i%10];
 
		this._mix(v,0,4,8,12,data[perm[0]],data[perm[1]]);
 
		this._mix(v,1,5,9,13,data[perm[2]],data[perm[3]]);
 
		this._mix(v,2,6,10,14,data[perm[4]],data[perm[5]]);
 
		this._mix(v,3,7,11,15,data[perm[6]],data[perm[7]]);
 

	
 
		this._mix(v,0,5,10,15,data[perm[8]],data[perm[9]]);
 
		this._mix(v,1,6,11,12,data[perm[10]],data[perm[11]]);
 
		this._mix(v,2,7,8,13,data[perm[12]],data[perm[13]]);
 
		this._mix(v,3,4,9,14,data[perm[14]],data[perm[15]]);
 
	}
 

	
 
	this._state=this._state.map((x,i)=>x^v[i]^v[i+8]);
 
};
 

	
 
BLAKE2S.prototype._mix=function(arr,ia,ib,ic,id,x,y){
 
	let a=arr[ia], b=arr[ib], c=arr[ic], d=arr[id];
 
	a=(a+b+x)&MASK; d=rrot(d^a,16);
 
	c=(c+d)&MASK; b=rrot(b^c,12);
 
	a=(a+b+y)&MASK; d=rrot(d^a,8);
 
	c=(c+d)&MASK; b=rrot(b^c,7);
 
	arr[ia]=a; arr[ib]=b; arr[ic]=c; arr[id]=d;
 
};
 

	
 
export function blake2s(data,outputLen=32,key=[]){
 
	let h=new BLAKE2S(outputLen,key);
 
	for(let i=0;i<data.length;i+=BLOCK_LEN){h.update(data.slice(i,i+BLOCK_LEN));}
 
	return h.digest();
 
}
src/chacha.js
Show inline comments
 
// https://tools.ietf.org/html/rfc7539
 
import {MASK,int32s2bytes,bytes2int32s} from "./util.js";
 
import {MASK,int32s2bytes,bytes2int32s,zeroPad} from "./util.js";
 

	
 
function lrot(x,shift){
 
	return (x<<shift|x>>>(32-shift))&MASK;
 
}
 

	
 
function createNonce(){
 
	let nonce=new Uint8Array(12);
 
	window.crypto.getRandomValues(nonce);
 
	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){
 
		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());
 
}
src/util.js
Show inline comments
 
export const MASK=0xffffffff;
 

	
 
export function zeroPad(arr,length){
 
	return arr.concat((new Array(length)).fill(0)).slice(0,length);
 
}
 

	
 
export function bytes2int32(arr){
 
	return arr.reduce((acc,b,i)=>acc|b<<(i*8));
 
}
 

	
 
export function bytes2int32s(arr){
 
	let res=[];
 
	for(let i=0;i<arr.length;i+=4){
 
		res.push(bytes2int32(arr.slice(i,i+4)));
 
	}
 
	return res;
 
}
 

	
 
export function int322bytes(x){
 
	let res=[];
 
	for(let i=0;i<4;i++){
 
		res.push(x&0xff);
 
		x>>>=8;
 
	}
 
	return res;
 
}
 

	
 
export function int32s2bytes(arr){
 
	return arr.map(int322bytes).reduce((acc,bytes)=>acc.concat(bytes));
 
}
 

	
 
export function bytes2hex(arr){
 
	return arr.map(x=>x.toString(16).padStart(2,"0")).join("");
 
}
 

	
 
export function str2utf8(s){
 
	let res=[];
 
	let c=s.codePointAt(0);
 
	for(let i=0; c!==undefined; i++,c=s.codePointAt(i)){
 
		if(c<0x80){res.push(c);}
 
		else if(c<0x800){
 
			res.push(0b11000000|(c>>>6));
 
			res.push(0b10000000|(c&0b111111));
 
		}
 
		else if(c<0x10000){
 
			res.push(0b11100000|(c>>>12));
 
			res.push(0b10000000|((c>>>6)&0b111111));
 
			res.push(0b10000000|(c&0b111111));
 
		}
 
		else{
 
			res.push(0b11110000|(c>>>18));
 
			res.push(0b10000000|((c>>>12)&0b111111));
 
			res.push(0b10000000|((c>>>6)&0b111111));
 
			res.push(0b10000000|(c&0b111111));
 
		}
 
		if(c>0xffff){i++;} // skip surrogate
 
	}
 
	return res;
 
}
 

	
 
export function utf82str(arr){
 
	let res=[];
 
	for(let i=0;i<arr.length;i++){
 
		let x=arr[i];
 
		if(x<=0b1111111){res.push(x);}
 
		else if(x<=0b11011111){
 
			let a=x&0b11111;
 
			let b=arr[++i]&0b111111;
 
			res.push(a<<6|b);
 
		}
 
		else if(x<=0b11101111){
 
			let a=x&0b1111;
 
			let b=arr[++i]&0b111111;
 
			let c=arr[++i]&0b111111;
 
			res.push(a<<12|b<<6|c);
 
		}
 
		else{
 
			let a=x&0b111;
 
			let b=arr[++i]&0b111111;
 
			let c=arr[++i]&0b111111;
 
			let d=arr[++i]&0b111111;
 
			res.push(a<<18|b<<12|c<<6|d);
 
		}
 
	}
 
	return res.map(x=>String.fromCodePoint(x)).join("");
 
}
0 comments (0 inline, 0 general)