Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
```
Expand All @@ -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.
Expand Down
39 changes: 18 additions & 21 deletions index.js
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down Expand Up @@ -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"
}
18 changes: 13 additions & 5 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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)
})
Expand All @@ -52,14 +55,19 @@ 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' }

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')
Expand Down