├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── chacha20.js ├── package.json └── test └── chacha20.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "0.10" 5 | - "0.12" 6 | - "4" 7 | 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | ./node_modules/.bin/mocha --reporter list 3 | 4 | .PHONY: test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pure javascript implementation of [ChaCha20](http://cr.yp.to/chacha.html) originally written by [@devi](https://github.com/devi/chacha20poly1305) supporting [draft-irtf-cfrg-chacha20-poly1305-01](https://tools.ietf.org/html/draft-irtf-cfrg-chacha20-poly1305-01). 2 | 3 | Being packaged here as a simple node.js and browserify module. 4 | 5 | ## Usage 6 | 7 | ```` 8 | var chacha20 = require("chacha20"); 9 | 10 | var key = new Buffer(32); 11 | key.fill(0); 12 | var nonce = new Buffer(8); 13 | nonce.fill(0); 14 | 15 | var plaintext = "testing"; 16 | // pass in buffers, returns a buffer 17 | var ciphertext = chacha20.encrypt(key, nonce, new Buffer(plaintext)); 18 | console.log(ciphertext.toString("hex")); // prints "02dd93d9c99f5a" 19 | console.log(chacha20.decrypt(key, nonce, ciphertext).toString()); // prints "testing" 20 | ```` 21 | 22 | ## Nonce Size 23 | 24 | The handling of the nonce differs between the [reference](http://cr.yp.to/chacha.html) and [IETF Draft](https://tools.ietf.org/html/draft-irtf-cfrg-chacha20-poly1305-01#section-2.3), where the reference uses an 8-byte nonce and the draft uses a 12-byte one with the first 4 bytes being a `sender` unique identifier. Passing a difference nonce buffer size will choose either mode. -------------------------------------------------------------------------------- /chacha20.js: -------------------------------------------------------------------------------- 1 | /* chacha20 - 256 bits */ 2 | 3 | // Written in 2014 by Devi Mandiri. Public domain. 4 | // 5 | // Implementation derived from chacha-ref.c version 20080118 6 | // See for details: http://cr.yp.to/chacha/chacha-20080128.pdf 7 | 8 | function U8TO32_LE(x, i) { 9 | return x[i] | (x[i+1]<<8) | (x[i+2]<<16) | (x[i+3]<<24); 10 | } 11 | 12 | function U32TO8_LE(x, i, u) { 13 | x[i] = u; u >>>= 8; 14 | x[i+1] = u; u >>>= 8; 15 | x[i+2] = u; u >>>= 8; 16 | x[i+3] = u; 17 | } 18 | 19 | function ROTATE(v, c) { 20 | return (v << c) | (v >>> (32 - c)); 21 | } 22 | 23 | var Chacha20 = function(key, nonce, counter) { 24 | this.input = new Uint32Array(16); 25 | 26 | // https://tools.ietf.org/html/draft-irtf-cfrg-chacha20-poly1305-01#section-2.3 27 | this.input[0] = 1634760805; 28 | this.input[1] = 857760878; 29 | this.input[2] = 2036477234; 30 | this.input[3] = 1797285236; 31 | this.input[4] = U8TO32_LE(key, 0); 32 | this.input[5] = U8TO32_LE(key, 4); 33 | this.input[6] = U8TO32_LE(key, 8); 34 | this.input[7] = U8TO32_LE(key, 12); 35 | this.input[8] = U8TO32_LE(key, 16); 36 | this.input[9] = U8TO32_LE(key, 20); 37 | this.input[10] = U8TO32_LE(key, 24); 38 | this.input[11] = U8TO32_LE(key, 28); 39 | // be compatible with the reference ChaCha depending on the nonce size 40 | if(nonce.length == 12) 41 | { 42 | this.input[12] = counter; 43 | this.input[13] = U8TO32_LE(nonce, 0); 44 | this.input[14] = U8TO32_LE(nonce, 4); 45 | this.input[15] = U8TO32_LE(nonce, 8); 46 | }else{ 47 | this.input[12] = counter; 48 | this.input[13] = 0; 49 | this.input[14] = U8TO32_LE(nonce, 0); 50 | this.input[15] = U8TO32_LE(nonce, 4); 51 | 52 | } 53 | }; 54 | 55 | Chacha20.prototype.quarterRound = function(x, a, b, c, d) { 56 | x[a] += x[b]; x[d] = ROTATE(x[d] ^ x[a], 16); 57 | x[c] += x[d]; x[b] = ROTATE(x[b] ^ x[c], 12); 58 | x[a] += x[b]; x[d] = ROTATE(x[d] ^ x[a], 8); 59 | x[c] += x[d]; x[b] = ROTATE(x[b] ^ x[c], 7); 60 | }; 61 | 62 | Chacha20.prototype.encrypt = function(dst, src, len) { 63 | var x = new Uint32Array(16); 64 | var output = new Uint8Array(64); 65 | var i, dpos = 0, spos = 0; 66 | 67 | while (len > 0 ) { 68 | for (i = 16; i--;) x[i] = this.input[i]; 69 | for (i = 20; i > 0; i -= 2) { 70 | this.quarterRound(x, 0, 4, 8,12); 71 | this.quarterRound(x, 1, 5, 9,13); 72 | this.quarterRound(x, 2, 6,10,14); 73 | this.quarterRound(x, 3, 7,11,15); 74 | this.quarterRound(x, 0, 5,10,15); 75 | this.quarterRound(x, 1, 6,11,12); 76 | this.quarterRound(x, 2, 7, 8,13); 77 | this.quarterRound(x, 3, 4, 9,14); 78 | } 79 | for (i = 16; i--;) x[i] += this.input[i]; 80 | for (i = 16; i--;) U32TO8_LE(output, 4*i, x[i]); 81 | 82 | this.input[12] += 1; 83 | if (!this.input[12]) { 84 | this.input[13] += 1; 85 | } 86 | if (len <= 64) { 87 | for (i = len; i--;) { 88 | dst[i+dpos] = src[i+spos] ^ output[i]; 89 | } 90 | return; 91 | } 92 | for (i = 64; i--;) { 93 | dst[i+dpos] = src[i+spos] ^ output[i]; 94 | } 95 | len -= 64; 96 | spos += 64; 97 | dpos += 64; 98 | } 99 | }; 100 | 101 | Chacha20.prototype.keystream = function(dst, len) { 102 | for (var i = 0; i < len; ++i) dst[i] = 0; 103 | this.encrypt(dst, dst, len); 104 | }; 105 | 106 | // additions to make it easier and export it as a module 107 | 108 | exports.Cipher = Chacha20; 109 | 110 | exports.encrypt = exports.decrypt = function(key, nonce, data) 111 | { 112 | var cipher = new Chacha20(key, nonce, 1); 113 | var ret = Buffer.alloc(data.length); 114 | cipher.encrypt(ret, data, data.length); 115 | return ret; 116 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chacha20", 3 | "version": "0.1.4", 4 | "main": "chacha20.js", 5 | "dependencies": {}, 6 | "devDependencies": { 7 | "chai": "*", 8 | "mocha": "*" 9 | }, 10 | "keywords": [ 11 | "chacha", 12 | "chacha20", 13 | "salsa20", 14 | "browserify" 15 | ], 16 | "description": "A pure JavaScript implementation of the ChaCha20 cipher", 17 | "homepage": "https://github.com/quartzjer/chacha20", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/quartzjer/chacha20.git" 21 | }, 22 | "license": "CC0-1.0", 23 | "scripts": { 24 | "test": "mocha" 25 | }, 26 | "author": { 27 | "name": "Devi Mandiri", 28 | "email": "me@devi.web.id", 29 | "url": "https://github.com/devi" 30 | }, 31 | "maintainers": [ 32 | { 33 | "name": "Jeremie Miller", 34 | "email": "jeremie@jabber.org", 35 | "url": "http://jeremie.com/" 36 | } 37 | ], 38 | "engines": { 39 | "node": ">=0.10.x", 40 | "npm": ">=1.2.x" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/chacha20.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var chacha20 = require('..'); 3 | 4 | 5 | describe('chacha20', function(){ 6 | 7 | it('exports an object', function(){ 8 | expect(chacha20).to.be.a('object'); 9 | }); 10 | 11 | it('reference tests', function(){ 12 | var key = new Buffer(32); 13 | key.fill(0); 14 | var nonce = new Buffer(8); 15 | nonce.fill(0); 16 | var data = "\0\0\0\0\0\0\0\0\0"; // 9 17 | var out = chacha20.encrypt(key, nonce, new Buffer(data)); 18 | expect(out.toString('hex')).to.be.equal("76b8e0ada0f13d9040"); 19 | expect(chacha20.decrypt(key, nonce, out).toString()).to.be.equal(data); 20 | 21 | key.fill(0xff); 22 | nonce.fill(0xff); 23 | var ff = new Buffer(9); 24 | ff.fill(0xff); 25 | var out = chacha20.encrypt(key, nonce, ff); 26 | expect(out.toString('hex')).to.be.equal("2640c09431912f4abd"); 27 | expect(chacha20.decrypt(key, nonce, out).toString("hex")).to.be.equal(ff.toString("hex")); 28 | }); 29 | 30 | it('draft tests', function(){ 31 | var key = new Buffer(32); 32 | key.fill(0); 33 | var nonce = new Buffer(12); 34 | nonce.fill(0); 35 | var data = "\0\0\0\0\0\0\0\0\0"; // 9 36 | var out = chacha20.encrypt(key, nonce, new Buffer(data)); 37 | expect(out.toString('hex')).to.be.equal("76b8e0ada0f13d9040"); 38 | expect(chacha20.decrypt(key, nonce, out).toString()).to.be.equal(data); 39 | 40 | key.fill(0xff); 41 | nonce.fill(0xff); 42 | var ff = new Buffer(9); 43 | ff.fill(0xff); 44 | var out = chacha20.encrypt(key, nonce, ff); 45 | expect(out.toString('hex')).to.be.equal("2919cb6a15012803c4"); 46 | expect(chacha20.decrypt(key, nonce, out).toString("hex")).to.be.equal(ff.toString("hex")); 47 | }); 48 | 49 | it('original tests', function(){ 50 | var Chacha20 = chacha20.Cipher; 51 | 52 | //--------------------------- test -----------------------------// 53 | function fromHex(h) { 54 | h = h.replace(/([^0-9a-f])/g, ''); 55 | var out = [], len = h.length, w = ''; 56 | for (var i = 0; i < len; i += 2) { 57 | w = h[i]; 58 | if (((i+1) >= len) || typeof h[i+1] === 'undefined') { 59 | w += '0'; 60 | } else { 61 | w += h[i+1]; 62 | } 63 | out.push(parseInt(w, 16)); 64 | } 65 | return out; 66 | } 67 | 68 | function bytesEqual(a, b) { 69 | var dif = 0; 70 | if (a.length !== b.length) return 0; 71 | for (var i = 0; i < a.length; i++) { 72 | dif |= (a[i] ^ b[i]); 73 | } 74 | dif = (dif - 1) >>> 31; 75 | return (dif & 1); 76 | } 77 | 78 | function printHex(num, len, padlen, block) { 79 | var ret = '', pad = '', i; 80 | for (i=0; i