diff --git a/README.md b/README.md index 1f186df..df677f2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ const hypercore = require('hypercore') const Encoder = require('.') const encryptionKey = Encoder.encryptionKey() -const valueEncoding = Encoder(encrytionKey, { valueEncoding: 'utf-8' }) +const nonce = Encoder.generateNonce() +const valueEncoding = Encoder(encrytionKey, { nonce, valueEncoding: 'utf-8' }) const feed = hypercore(storage, { valueEncoding }) ``` @@ -25,12 +26,18 @@ const encryptionKey = Encoder.encryptionKey() ``` Generate a random 32 byte key to be used to encrypt. +```js +const nonce = Encoder.generateNonce() +``` +Generate a random nonce used to encrypt. + ```js const valueEncoding = Encoder(encryptionKey, opts) ``` Returns a message encoder used for encrypting messages in hypercore. - `encryptionKey` must be a buffer of length `Encoder.KEYBYTES`. - `opts` is an optional object which may contain: + - `ops.nonce` a buffer containing a 24 byte nonce - `opts.valueEncoder`, an additional encoder to be used before encryption. May be one of: - The string 'utf-8' - utf-8 encoded strings will be assumed. - The string 'JSON' - JSON encoding will be assumed. diff --git a/index.js b/index.js index 7c4f16c..1b716ef 100644 --- a/index.js +++ b/index.js @@ -1,39 +1,36 @@ const sodium = require('sodium-native') const assert = require('assert') const codecs = require('codecs') -const zero = sodium.sodium_memzero module.exports = encoder module.exports.encryptionKey = encryptionKey -module.exports.KEYBYTES = sodium.crypto_secretbox_KEYBYTES +module.exports.KEYBYTES = sodium.crypto_stream_KEYBYTES +module.exports.generateNonce = function () { + const nonce = Buffer.alloc(sodium.crypto_stream_NONCEBYTES) + sodium.randombytes_buf(nonce) + return nonce +} function encoder (encryptionKey, opts = {}) { assert(Buffer.isBuffer(encryptionKey), 'encryption key must be a buffer') - assert(encryptionKey.length === sodium.crypto_secretbox_KEYBYTES, `cobox-crypto: key must be a buffer of length ${sodium.crypto_secretbox_KEYBYTES}`) + assert(encryptionKey.length === sodium.crypto_stream_KEYBYTES, `Key must be a buffer of length ${sodium.crypto_stream_KEYBYTES}`) const encoder = codecs(opts.valueEncoding) + const nonce = opts.nonce + assert(nonce, 'Nonce must be provided') + + const encryptOrDecrypt = function (data) { + sodium.crypto_stream_xor(data, data, nonce, encryptionKey) + return data + } + return { encode (message, buffer, offset) { - // Run originally provided encoder if any - message = encoder.encode(message, buffer, offset) - const ciphertext = Buffer.alloc(message.length + sodium.crypto_secretbox_MACBYTES) - const nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES) - sodium.randombytes_buf(nonce) - sodium.crypto_secretbox_easy(ciphertext, message, nonce, encryptionKey) - zero(message) - return Buffer.concat([nonce, ciphertext]) + return encryptOrDecrypt(encoder.encode(message, buffer, offset)) }, - decode (buffer, start, end) { - const nonce = buffer.slice(0, sodium.crypto_secretbox_NONCEBYTES) - const messageWithMAC = buffer.slice(sodium.crypto_secretbox_NONCEBYTES) - const message = Buffer.alloc(messageWithMAC.length - sodium.crypto_secretbox_MACBYTES) - assert( - sodium.crypto_secretbox_open_easy(message, messageWithMAC, nonce, encryptionKey), - 'Decryption failed!' - ) - // Run originally provided encoder if any - return encoder.decode(message, start, end) + decode (message, start, end) { + return encoder.decode(encryptOrDecrypt(message), start, end) } } } diff --git a/package.json b/package.json index 4d126b6..9977d8e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "crypto-encoder", - "version": "1.0.0", + "version": "1.0.2", "description": "A simple crypto encoder compatible with hypercore", "main": "index.js", "directories": { @@ -28,6 +28,10 @@ "encoder", "hypercore" ], + "repository": { + "type": "git", + "url": "git+https://github.com/ameba23/crypto-encoder.git" + }, "author": "Magma Collective", "license": "AGPL-3.0-or-later" } diff --git a/test/index.test.js b/test/index.test.js index a5559cf..0c833dc 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -10,11 +10,13 @@ const { cleanup, tmp } = require('./util') describe('message encoding', (context) => { context('encrypt and decrypt a message', (assert, next) => { const key = Encoder.encryptionKey() - const encoder = Encoder(key, { valueEncoding: 'utf-8' }) + const nonce = Encoder.generateNonce() + const encoder = Encoder(key, { nonce, valueEncoding: 'utf-8' }) const encrypted = encoder.encode('Hello World') assert.ok(encrypted, 'Encrypts the message') assert.ok(encrypted instanceof Buffer, 'Returns a buffer') + assert.equals(encrypted.length, 'Hello World'.length, 'Ciphertext and plaintext are same length') const message = encoder.decode(encrypted) assert.same(message, 'Hello World', 'Decrypts the message') @@ -31,19 +33,20 @@ describe('hypercore', (context) => { context('encrypted the log', (assert, next) => { const key = Encoder.encryptionKey() - const encoder = Encoder(key, { valueEncoding: 'utf-8' }) + const nonce = Encoder.generateNonce() + const encoder = Encoder(key, { nonce, valueEncoding: 'utf-8' }) const feed = hypercore(storage, { valueEncoding: encoder }) feed.append('boop', (err) => { assert.error(err, 'no error') - var data = fs.readFileSync(path.join(storage, 'data')) + const data = fs.readFileSync(path.join(storage, 'data')) assert.notSame(data, 'boop', 'log entry is encrypted') assert.same(encoder.decode(data), 'boop', 'log entry is encrypted') feed.get(0, (err, entry) => { assert.error(err, 'no error') - assert.same('boop', entry, 'hypercore decrypts the message') + assert.same(entry, 'boop', 'hypercore decrypts the message') cleanup(storage, next) }) @@ -52,7 +55,8 @@ describe('hypercore', (context) => { context('encrypted the log, with a json object', (assert, next) => { const key = Encoder.encryptionKey() - const encoder = Encoder(key, { valueEncoding: 'json' }) + const nonce = Encoder.generateNonce() + const encoder = Encoder(key, { nonce, valueEncoding: 'json' }) const feed = hypercore(storage, { valueEncoding: encoder }) const message = { boop: 'beep' } @@ -60,6 +64,10 @@ describe('hypercore', (context) => { feed.append(message, (err) => { assert.error(err, 'no error') + const data = fs.readFileSync(path.join(storage, 'data')) + assert.notSame(JSON.stringify(data), JSON.stringify(message), 'log entry is encrypted') + assert.same(JSON.stringify(encoder.decode(data)), JSON.stringify(message), 'log entry is encrypted') + feed.get(0, (err, entry) => { assert.error(err, 'no error') assert.same(message, entry, 'hypercore decrypts the message')