├── .travis.yml ├── LICENSE ├── README.md ├── bin ├── decrypt ├── encrypt └── keygen ├── example.js ├── index.js ├── package.json └── test.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - lts/* 4 | deploy: 5 | provider: npm 6 | email: 7 | secure: "OPQVzB18iybp46uhori2Gj6q2W0joMCFf8xwam+V02Pi4vSmY+6iwvb/sS88sHAXi+z4vPsXXIHyRMsi3yiUCCSMSTHa81qHPlZt2eMj8z+W0sUAP7U0hjrD4wm+wMBWcWRF3Og2P8YV4fNh+VTotJWwG6Kp/LhTxZMyBVvJBA2bjlBWqO9hpm4dHz3YWswdBaW+Mr9M9ftzHLxN+8tM2ZiNhVNGqq1iicv9Rxf0y7Lwvdb77oQOcQM/NYcgydRVFp1wYU09eLEw4X4Dz+N+xT4VGjEQWB3cBc6q/Lk8a34+QaZtKFwd7aVZhcIp+NDl3cAb5fX+nV0hU3ygueEYuP7Q5yj9BRY7JCP0VuaRx6T4DhXBP35J4juvwhBBNrjv5zIAuMnAQmHgWqoQwE09vfXoxzoZIARyv9tU1x1GZ3UuZjaVn9Zyu7WiA3j1HDmwJbTKb7Zh+fFADw96HEaD3dM25j64TTBJMugOtUqLlUUpBEXcVil2G+IgDNMd6e1P9ZH1IXMOH8mblj1FaFyQhf92fFjkZx5cLFsJ5XsVCdIYih/jo2Vokbm7LoYNPeHCOMIL8zPCxXM/NuoC+RlN0klP/D4cg/DhVYRIOgMYV2hIV8cjVyAxKNMdy6k6DJGK2Q9LcxvNRKDN72HvnLGldGtB8ViqWH8ZN2EmMIrn43M=" 8 | api_key: # 85a5 9 | secure: "AtYlp1cRWJvqCBQSLY32Fc3mX2PKzuOFqCR2dGfSrrRZZsV3C116yj8O76br0u12YPEiLpDfQt+b/f08RxATZazpL/+hxUoGdsH4FhcUuzX/zJSelW96zUY1wH6ZJcNL99uLOZd/zrZ0wkE+alolif5/fkt78uEiDUcZW5JwrFiLXPHPKLzgU+kvhK8B2OhcvirmfouQfZ73cQL7xSgah5Mfz76b4Op6B9/Qn5Emxh4brGE0H0+3pqJwrVGePXFhAQ1ATx5ALWMLSV0IaDULXuZBT960ovk6U3zFTY0PPA+9Xv7K9lhfdPFPnIoq45GeQiyJSqpSWTaNUpvhln2/pZBRBbWonADMmtxYUB9RJ9BUpARf7X00GnhZbmM1fCLFhZaHkjS9Jt16pGdzJdq8AjnlxuOdohmE1kxUKaoQ1xyIJ0ukRlRxvfCmjEk1m2veow3aqfkmfEYJbunb5kc2CZeA2MsboioxnafRO94GkK+MG+3JiLJtWd3a9GBw3uv6/5UKRH5CBmk0+a0u22l7QBXl5Bl6abU4rMuAWpTZVER1mJQa5I13I6FwhR3hh26DX/Aep5zGRxHQh2QWPhB4wOv58APmsahvXdfNf1yrTFwHsLOHiUHWZxsKoG9dbTJFI7mg7JxkWXMtIZx3bUnqkX6JStU+RgOPtuf00drI4R0=" 10 | on: 11 | tags: true 12 | node: lts/* 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, Emil Bay 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `secret-blob` 2 | 3 | [![Build Status](https://travis-ci.org/emilbayes/secret-blob.svg?branch=master)](https://travis-ci.org/emilbayes/secret-blob) 4 | 5 | > Tiny module for easy encryption of Buffers 6 | 7 | ## Usage 8 | 9 | ```js 10 | const secretBlob = require('secret-blob') 11 | 12 | const key = secretBlob.keygen() 13 | 14 | const message = Buffer.from('Hello world') 15 | 16 | const encrypted = secretBlob.encrypt(message, key) 17 | 18 | const decrypted = secretBlob.decrypt(encrypted, key) 19 | 20 | console.log(decrypted.toString()) // Hello world 21 | ``` 22 | 23 | ## API 24 | 25 | ### `const key = secretBlob.keygen([key])` 26 | 27 | Generate a new key. Optionally pass `Buffer` `key` to write to. 28 | Must be `secretBlob.KEYBYTES` long. 29 | 30 | Returns a `sodium-native` secret `Buffer`. 31 | 32 | ### `const ciphertext = secretBlob.encrypt(plaintext, key, [ciphertext])` 33 | 34 | Encrypt `plaintext` under `key`. Optionally pass `Buffer` `ciphertext` to write 35 | to. Must be `secretBlob.encryptLength(plaintext)` long. 36 | 37 | Returns a normal `Buffer`. 38 | 39 | ### `const byteLength = secretBlob.encryptLength(plaintext)` 40 | 41 | Returns the number of bytes required to encrypt `plaintext`. 42 | Simply `plaintext.byteLength + secretBlob.HEADERBYTES`. 43 | 44 | ### `const plaintext = secretBlob.decrypt(ciphertext, key, [plaintext])` 45 | 46 | Decrypt `ciphertext` under `key`. Optionally pass `Buffer` `plaintext` to write 47 | to. Must be `secretBlob.decryptLength(plaintext)` long. 48 | 49 | Returns a `sodium-native` secret `Buffer`. 50 | 51 | ### `const byteLength = secretBlob.decryptLength(ciphertext)` 52 | 53 | Returns the number of bytes required to encrypt `ciphertext`. 54 | Simply `ciphertext.byteLength - secretBlob.HEADERBYTES`. 55 | 56 | ### Constants 57 | 58 | - `KEYBYTES`: Byte length of a key 59 | - `HEADERBYTES`: Byte length of the header added to ciphertext 60 | 61 | ## Install 62 | 63 | ```sh 64 | npm install secret-blob 65 | ``` 66 | 67 | ## License 68 | 69 | [ISC](LICENSE) 70 | -------------------------------------------------------------------------------- /bin/decrypt: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | const fs = require('fs') 3 | const path = require('path') 4 | const secretBlob = require('..') 5 | 6 | const keyString = process.argv[2] 7 | var ciphertextPath = process.argv[3] 8 | 9 | if (ciphertextPath == null || ciphertextPath === '-') ciphertextPath = '/dev/stdin' 10 | const ciphertext = fs.readFileSync(path.resolve(ciphertextPath)) 11 | 12 | const key = Buffer.from(keyString, 'base64') 13 | 14 | process.stdout.write(secretBlob.decrypt(ciphertext, key)) 15 | -------------------------------------------------------------------------------- /bin/encrypt: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | const fs = require('fs') 3 | const path = require('path') 4 | const secretBlob = require('..') 5 | 6 | const keyString = process.argv[2] 7 | var plaintextPath = process.argv[3] 8 | 9 | if (plaintextPath == null || plaintextPath === '-') plaintextPath = '/dev/stdin' 10 | const plaintext = fs.readFileSync(path.resolve(plaintextPath)) 11 | 12 | const key = Buffer.from(keyString, 'base64') 13 | 14 | process.stdout.write(secretBlob.encrypt(plaintext, key)) 15 | -------------------------------------------------------------------------------- /bin/keygen: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | const secretBlob = require('..') 3 | 4 | const key = secretBlob.keygen() 5 | 6 | console.log(key.toString('base64')) 7 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | const secretBlob = require('.') 2 | 3 | const key = secretBlob.keygen() 4 | 5 | const message = Buffer.from('Hello world') 6 | 7 | const encrypted = secretBlob.encrypt(message, key) 8 | 9 | const decrypted = secretBlob.decrypt(encrypted, key) 10 | 11 | console.log(decrypted.toString()) 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const assert = require('nanoassert') 2 | const sodium = require('sodium-native') 3 | 4 | const KEYBYTES = sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES 5 | const NONCEBYTES = sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES 6 | const TAGBYTES = sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES 7 | const HEADERBYTES = NONCEBYTES + TAGBYTES 8 | 9 | function keygen (key) { 10 | if (key == null) key = sodium.sodium_malloc(KEYBYTES) 11 | assert(key.byteLength === KEYBYTES, 'key must be KEYBYTES long') 12 | sodium.crypto_aead_xchacha20poly1305_ietf_keygen(key) 13 | return key 14 | } 15 | 16 | function encrypt (plaintext, key, ciphertext) { 17 | assert(Buffer.isBuffer(plaintext)) 18 | var clen = encryptLength(plaintext) 19 | if (ciphertext == null) ciphertext = Buffer.alloc(clen) 20 | assert(key.byteLength === KEYBYTES, 'key must be KEYBYTES long') 21 | assert(ciphertext.byteLength === clen, 'ciphertext must be plaintext + HEADERBYTES long') 22 | 23 | const nonce = ciphertext.subarray(0, NONCEBYTES) 24 | sodium.randombytes_buf(nonce) 25 | 26 | // Kinda SIV 27 | sodium.crypto_generichash_batch(nonce, [nonce, plaintext], key) 28 | 29 | const c = ciphertext.subarray(NONCEBYTES) 30 | 31 | sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(c, plaintext, null, null, nonce, key) 32 | 33 | return ciphertext 34 | } 35 | 36 | function encryptLength (plaintext) { 37 | assert(Buffer.isBuffer(plaintext)) 38 | return plaintext.byteLength + HEADERBYTES 39 | } 40 | 41 | function decrypt (ciphertext, key, plaintext) { 42 | assert(Buffer.isBuffer(ciphertext)) 43 | var plen = decryptLength(ciphertext) 44 | if (plaintext == null) plaintext = sodium.sodium_malloc(plen) 45 | assert(key.byteLength === KEYBYTES, 'key must be KEYBYTES long') 46 | assert(plaintext.byteLength === plen, 'plaintext must be ciphertext - HEADERBYTES long') 47 | 48 | const nonce = ciphertext.subarray(0, NONCEBYTES) 49 | const c = ciphertext.subarray(NONCEBYTES) 50 | 51 | sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, null, c, null, nonce, key) 52 | 53 | return plaintext 54 | } 55 | 56 | function decryptLength (ciphertext) { 57 | assert(Buffer.isBuffer(ciphertext)) 58 | return ciphertext.byteLength - HEADERBYTES 59 | } 60 | 61 | module.exports = { 62 | keygen, 63 | encrypt, 64 | encryptLength, 65 | decrypt, 66 | decryptLength, 67 | KEYBYTES, 68 | HEADERBYTES 69 | } 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secret-blob", 3 | "version": "1.1.0", 4 | "description": "Tiny module for easy encryption of Buffers", 5 | "main": "index.js", 6 | "bin": { 7 | "secret-blob-keygen": "./bin/keygen", 8 | "secret-blob-encrypt": "./bin/encrypt", 9 | "secret-blob-decrypt": "./bin/decrypt" 10 | }, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "dependencies": { 15 | "nanoassert": "^2.0.0", 16 | "sodium-native": "^3.1.1" 17 | }, 18 | "devDependencies": { 19 | "standard": "^14.3.4", 20 | "tape": "^5.0.1" 21 | }, 22 | "scripts": { 23 | "pretest": "standard", 24 | "test": "tape test.js" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/emilbayes/secret-blob.git" 29 | }, 30 | "keywords": [], 31 | "author": "Emil Bay ", 32 | "license": "ISC", 33 | "bugs": { 34 | "url": "https://github.com/emilbayes/secret-blob/issues" 35 | }, 36 | "homepage": "https://github.com/emilbayes/secret-blob#readme" 37 | } 38 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const secretBlob = require('.') 3 | 4 | test('simple', function (assert) { 5 | const key = secretBlob.keygen() 6 | 7 | const message = Buffer.from('Hello world') 8 | 9 | const encrypted = secretBlob.encrypt(message, key) 10 | 11 | const decrypted = secretBlob.decrypt(encrypted, key) 12 | 13 | assert.ok(decrypted.toString() === 'Hello world') 14 | 15 | assert.end() 16 | }) 17 | 18 | test('empty', function (assert) { 19 | const key = secretBlob.keygen() 20 | 21 | const message = Buffer.alloc(0) 22 | 23 | const encrypted = secretBlob.encrypt(message, key) 24 | 25 | const decrypted = secretBlob.decrypt(encrypted, key) 26 | 27 | assert.ok(encrypted.byteLength === secretBlob.HEADERBYTES) 28 | assert.ok(decrypted.toString() === '') 29 | 30 | assert.end() 31 | }) 32 | --------------------------------------------------------------------------------