|
|
// https://tools.ietf.org/html/rfc7539
|
|
|
import {MASK,int32s2bytes,bytes2int32s,zeroPad,createRandomNonce} from "./util.js";
|
|
|
import {MASK, int32s2bytes, bytes2int32s, zeroPad, createRandomNonce} from "./util.js";
|
|
|
|
|
|
function lrot(x,shift){
|
|
|
return (x<<shift|x>>>(32-shift))&MASK;
|
|
|
function lrot(x, shift) {
|
|
|
return (x << shift | x >>> (32-shift)) & MASK;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -10,80 +10,80 @@ function lrot(x,shift){
|
|
|
* @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){
|
|
|
const NONCE_LEN=12;
|
|
|
if(nonce===undefined){
|
|
|
nonce=createRandomNonce(NONCE_LEN);
|
|
|
export function Chacha20(key, nonce) {
|
|
|
const NONCE_LEN = 12;
|
|
|
if (nonce === undefined) {
|
|
|
nonce = createRandomNonce(NONCE_LEN);
|
|
|
}
|
|
|
this._nonce=zeroPad(nonce,NONCE_LEN);
|
|
|
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._nonce = zeroPad(nonce, NONCE_LEN);
|
|
|
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=[];
|
|
|
this._buffer = [];
|
|
|
}
|
|
|
|
|
|
Chacha20.prototype.setPos=function(pos){
|
|
|
this._state[12]=pos;
|
|
|
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());
|
|
|
Chacha20.prototype.getByte = function() {
|
|
|
if (this._buffer.length == 0) {
|
|
|
this._buffer = int32s2bytes(this._computeBlock());
|
|
|
this._incrementPos();
|
|
|
}
|
|
|
return this._buffer.shift();
|
|
|
};
|
|
|
|
|
|
Chacha20.prototype.getNonce=function(){
|
|
|
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._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();
|
|
|
Chacha20.prototype._computeBlock = function() {
|
|
|
let state = this._state.slice();
|
|
|
|
|
|
for(let i=0;i<10;i++){ // 10 double rounds
|
|
|
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);
|
|
|
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);
|
|
|
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);
|
|
|
return state.map((xi, i) => (xi + this._state[i]) & MASK);
|
|
|
};
|
|
|
|
|
|
Chacha20.prototype._incrementPos=function(){
|
|
|
this._state[12]=(this._state[12]+1)&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,data.map(b=>b^cipher.getByte())];
|
|
|
export function encrypt(data, key, nonce) {
|
|
|
let cipher = new Chacha20(key, nonce);
|
|
|
nonce = cipher.getNonce();
|
|
|
return [nonce, data.map(b => b ^ cipher.getByte())];
|
|
|
}
|
|
|
|
|
|
export function decrypt(ciphertext,key,nonce){
|
|
|
let cipher=new Chacha20(key,nonce);
|
|
|
return ciphertext.map(b=>b^cipher.getByte());
|
|
|
export function decrypt(ciphertext, key, nonce) {
|
|
|
let cipher = new Chacha20(key, nonce);
|
|
|
return ciphertext.map(b => b ^ cipher.getByte());
|
|
|
}
|