Skip to content
This repository was archived by the owner on Oct 18, 2024. It is now read-only.

Commit

Permalink
Implement folder encryption and decryption fix #12 #37
Browse files Browse the repository at this point in the history
  • Loading branch information
HR committed Jun 29, 2019
1 parent 3806315 commit 2d20ac9
Show file tree
Hide file tree
Showing 14 changed files with 131 additions and 153 deletions.
2 changes: 1 addition & 1 deletion app/core/Db.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
******************************/

const _ = require('lodash')
const logger = require('../script/logger')
const logger = require('../utils/logger')
const fs = require('fs-extra')

/**
Expand Down
237 changes: 106 additions & 131 deletions app/core/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
const fs = require('fs-extra')
const path = require('path')
const scrypto = require('crypto')
const logger = require('../script/logger')
const Readable = require('stream').Readable
const logger = require('../utils/logger')
const Readable = require('stream')
.Readable
const tar = require('tar-fs')
const {CRYPTO, REGEX, ERRORS} = require('../config')
const { CRYPTO, REGEX, ERRORS } = require('../config')

// Helper functions

Expand Down Expand Up @@ -54,17 +55,19 @@ exports.encrypt = (origpath, mpkey) => {
exports.deriveKey(mpkey, null, CRYPTO.DEFAULTS.ITERATIONS)
.then((dcreds) => {
let tag
let isDirectory = fs.lstatSync(origpath)
.isDirectory()
let tempd = `${path.dirname(origpath)}/${CRYPTO.ENCRYPTION_TMP_DIR}`
let dataDestPath = `${tempd}/data`
let credsDestPath = `${tempd}/creds`
logger.verbose(`tempd: ${tempd}, dataDestPath: ${dataDestPath}, credsDestPath: ${credsDestPath}`)
logger.verbose(`tempd: ${tempd}, dataDestPath: ${dataDestPath}, credsDestPath: ${credsDestPath}, isDirectory: ${isDirectory}`)
// create tempd temporary directory
fs.mkdirs(tempd, (err) => {
if (err)
reject(err)
logger.verbose(`Created ${tempd} successfully`)
// readstream to read the (unencrypted) file
const origin = fs.createReadStream(origpath)
const origin = isDirectory ? tar.pack(origpath) : fs.createReadStream(origpath)
// create data and creds file
const dataDest = fs.createWriteStream(dataDestPath)
const credsDest = fs.createWriteStream(credsDestPath)
Expand All @@ -75,74 +78,53 @@ exports.encrypt = (origpath, mpkey) => {

// Read file, apply tranformation (encryption) to stream and
// then write stream to filesystem
// origin.pipe(zip).pipe(cipher).pipe(dest, { end: false })
origin.pipe(cipher).pipe(dataDest)

cipher.on('end', () => {
// get the generated Message Authentication Code
tag = cipher.getAuthTag()
// Write crdentials used to encrypt in creds file
// write in format Crypter#iv#authTag#salt
credsDest.end(`Crypter#${iv.toString('hex')}#${tag.toString('hex')}#${dcreds.salt.toString('hex')}`)
})

// readstream error handler
origin.on('error', (err) => {
// reject on readstream error
reject(err)
})

// writestream error handler
dataDest.on('error', (err) => {
// reject on writestream
reject(err)
})

credsDest.on('error', (err) => {
// reject on writestream
reject(err)
})
origin
.on('error', (err) => reject(err))
.pipe(cipher)
.on('error', (err) => reject(err))
.pipe(dataDest)
.on('error', (err) => reject(err))
.on('finish', () => {
// get the generated Message Authentication Code
tag = cipher.getAuthTag()
// Write crdentials used to encrypt in creds file
// write in format Crypter#iv#authTag#salt#isDir
let creds = `Crypter#${iv.toString('hex')}#${tag.toString('hex')}#${dcreds.salt.toString('hex')}`
if (isDirectory) {
creds += '#dir'
}
credsDest.end(creds)
})

// writestream finish handler
credsDest.on('finish', () => {
let tarDestPath = origpath + CRYPTO.EXT
const tarDest = fs.createWriteStream(tarDestPath)
const tarPack = tar.pack(tempd)
// Pack directory and zip into a .crypto file
tarPack.pipe(tarDest)

tarDest.on('error', (err) => {
// reject on writestream
reject(err)
})

tarPack.on('error', (err) => {
// reject on writestream
reject(err)
})

tarDest.on('finish', () => {
// Remove temporary dir tempd
fs.remove(tempd, (err) => {
if (err) reject(err)
// return all the credentials and parameters used for encryption
logger.verbose('Successfully deleted tempd!')
resolve({
salt: dcreds.salt,
key: dcreds.key,
cryptpath: tarDestPath,
tag: tag,
iv: iv
tarPack
.on('error', (err) => reject(err))
.pipe(tarDest)
.on('error', (err) => reject(err))
.on('finish', () => {
// Remove temporary dir tempd
fs.remove(tempd, (err) => {
if (err) reject(err)
// return all the credentials and parameters used for encryption
logger.verbose('Successfully deleted tempd!')
resolve({
salt: dcreds.salt,
key: dcreds.key,
cryptpath: tarDestPath,
tag: tag,
iv: iv
})
})
})
})
})
})
})
.catch((err) => {
// reject if error occured while deriving key
reject(err)
})
.catch((err) => reject(err))
})
}

Expand All @@ -158,33 +140,30 @@ exports.decrypt = (origpath, mpkey) => {
let tarOrig = fs.createReadStream(origpath)
let tarExtr = tar.extract(tempd)
// Extract tar to CRYPTO.DECRYPTION_TMP_DIR directory
tarOrig.pipe(tarExtr)

tarOrig.on('error', (err) => {
// reject on writestream
reject(err)
})

tarExtr.on('error', (err) => {
// reject on extraction error
reject(err)
})

tarExtr.on('finish', () => {
// Now read creds and use to decrypt data
logger.verbose('Finished extracting')
tarOrig
.on('error', (err) => reject(err))
.pipe(tarExtr)
.on('error', (err) => reject(err))
.on('finish', () => {
// Now read creds and use to decrypt data
logger.verbose('Finished extracting')

readFile(credsOrigPath)
.then((credsLines) => {
let credsLine = credsLines.trim()
.match(REGEX.ENCRYPTION_CREDS)

if (!credsLine) {
reject(new Error(ERRORS.DECRYPT))
}

readFile(credsOrigPath)
.then((credsLines) => {
let credsLine = credsLines.trim().match(REGEX.ENCRYPTION_CREDS)

if (credsLine) {
let creds = credsLine[0].split('#')
logger.verbose(`creds: ${creds}, credsLine: ${credsLine}`)

const iv = Buffer.from(creds[1], 'hex')
const authTag = Buffer.from(creds[2], 'hex')
const salt = Buffer.from(creds[3], 'hex')
const isDir = creds[4]
logger.verbose(`Extracted data, iv: ${iv}, authTag: ${authTag}, salt: ${salt}`)
// Read encrypted data stream
const dataOrig = fs.createReadStream(dataOrigPath)
Expand All @@ -194,51 +173,42 @@ exports.decrypt = (origpath, mpkey) => {
try {
let decipher = scrypto.createDecipheriv(CRYPTO.DEFAULTS.ALGORITHM, dcreds.key, iv)
decipher.setAuthTag(authTag)
const dataDest = fs.createWriteStream(dataDestPath)
dataOrig.pipe(decipher).pipe(dataDest)

decipher.on('error', (err) => {
reject(err)
})

dataOrig.on('error', (err) => {
reject(err)
})

dataDest.on('error', (err) => {
reject(err)
})

dataDest.on('finish', () => {
logger.verbose(`Encrypted to ${dataDestPath}`)
// Now delete tempd (temporary directory)
fs.remove(tempd, (err) => {
if (err)
reject(err)
logger.verbose(`Removed temp dir ${tempd}`)
resolve({
op: CRYPTO.DECRYPT_OP,
name: path.basename(origpath),
path: origpath,
cryptPath: dataDestPath,
salt: salt.toString('hex'),
key: dcreds.key.toString('hex'),
iv: iv.toString('hex'),
authTag: authTag.toString('hex')
let dataDest = isDir ? tar.extract(dataDestPath) : fs.createWriteStream(dataDestPath)

dataOrig
.on('error', (err) => reject(err))
.pipe(decipher)
.on('error', (err) => reject(err))
.pipe(dataDest)
.on('error', (err) => reject(err))
.on('finish', () => {
logger.verbose(`Encrypted to ${dataDestPath}`)
// Now delete tempd (temporary directory)
fs.remove(tempd, (err) => {
if (err)
reject(err)
logger.verbose(`Removed temp dir ${tempd}`)
resolve({
op: CRYPTO.DECRYPT_OP,
name: path.basename(origpath),
path: origpath,
cryptPath: dataDestPath,
salt: salt.toString('hex'),
key: dcreds.key.toString('hex'),
iv: iv.toString('hex'),
authTag: authTag.toString('hex')
})
})
})
})
} catch (err) {
reject(err)
}
})
} else {
reject(new Error(ERRORS.DECRYPT))
}
}).catch((err) => {
reject(err)
})
.catch((err) => {
reject(err)
})
})
})
})
}

Expand All @@ -251,17 +221,17 @@ exports.deriveKey = (pass, psalt) => {
// If psalt is provided and is not a Buffer then coerce it and assign it
// If psalt is not provided then generate a cryptographically secure salt
// and assign it
const salt = (psalt)
? ((Buffer.isBuffer(psalt))
? psalt
: Buffer.from(psalt))
: scrypto.randomBytes(CRYPTO.DEFAULTS.KEYLENGTH)
const salt = (psalt) ?
((Buffer.isBuffer(psalt)) ?
psalt :
Buffer.from(psalt)) :
scrypto.randomBytes(CRYPTO.DEFAULTS.KEYLENGTH)

// derive the key using the salt, password and default crypto setup
scrypto.pbkdf2(pass, salt, CRYPTO.DEFAULTS.MPK_ITERATIONS, CRYPTO.DEFAULTS.KEYLENGTH, CRYPTO.DEFAULTS.DIGEST, (err, key) => {
if (err) reject(err)
// return the key and the salt
resolve({key, salt})
resolve({ key, salt })
})
})
}
Expand All @@ -278,15 +248,20 @@ exports.genPassHash = (masterpass, salt) => {
if (salt) {
// create hash from the contanation of the pass and salt
// assign the hex digest of the created hash
const hash = scrypto.createHash(CRYPTO.DEFAULTS.HASH_ALG).update(`${pass}${salt}`).digest('hex')
resolve({hash, key: masterpass})
const hash = scrypto.createHash(CRYPTO.DEFAULTS.HASH_ALG)
.update(`${pass}${salt}`)
.digest('hex')
resolve({ hash, key: masterpass })
} else {
// generate a cryptographically secure salt and use it as the salt
const salt = scrypto.randomBytes(CRYPTO.DEFAULTS.KEYLENGTH).toString('hex')
const salt = scrypto.randomBytes(CRYPTO.DEFAULTS.KEYLENGTH)
.toString('hex')
// create hash from the contanation of the pass and salt
// assign the hex digest of the created hash
const hash = scrypto.createHash(CRYPTO.DEFAULTS.HASH_ALG).update(`${pass}${salt}`).digest('hex')
resolve({hash, salt, key: masterpass})
const hash = scrypto.createHash(CRYPTO.DEFAULTS.HASH_ALG)
.update(`${pass}${salt}`)
.digest('hex')
resolve({ hash, salt, key: masterpass })
}
})
}
Expand All @@ -309,4 +284,4 @@ exports.timingSafeEqual = (a, b) => {
result |= a[l] ^ b[l]
}
return result === 0
}
}
4 changes: 2 additions & 2 deletions app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ global.paths = {
documents: app.getPath('documents')
}

const logger = require('./script/logger')
const { checkUpdate } = require('./script/utils')
const logger = require('./utils/logger')
const { checkUpdate } = require('./utils/update')
// Core
const Db = require('./core/Db')
// Windows
Expand Down
2 changes: 1 addition & 1 deletion app/src/crypter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const {app, ipcMain, Menu, BrowserWindow} = require('electron')
const {VIEWS, ERRORS, WINDOW_OPTS} = require('../config')
const crypto = require('../core/crypto')
const menuTemplate = require('./menu')
const logger = require('../script/logger')
const logger = require('../utils/logger')

exports.window = function (global, callback) {
// creates a new BrowserWindow
Expand Down
2 changes: 1 addition & 1 deletion app/src/masterPassPrompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const {app, ipcMain, BrowserWindow} = require('electron')
const {VIEWS, WINDOW_OPTS} = require('../config')
const MasterPass = require('../core/MasterPass')
const MasterPassKey = require('../core/MasterPassKey')
const logger = require('../script/logger')
const logger = require('../utils/logger')


exports.window = function (global, callback) {
Expand Down
2 changes: 1 addition & 1 deletion app/src/settings.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const {app, ipcMain, BrowserWindow} = require('electron')
const {CRYPTO, VIEWS, SETTINGS, ERRORS, WINDOW_OPTS} = require('../config')
const logger = require('../script/logger')
const logger = require('../utils/logger')
const fs = require('fs-extra')
const _ = require('lodash')

Expand Down
2 changes: 1 addition & 1 deletion app/src/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const {app, ipcMain, BrowserWindow} = require('electron')
const {VIEWS, WINDOW_OPTS} = require('../config')
const MasterPass = require('../core/MasterPass')
const MasterPassKey = require('../core/MasterPassKey')
const logger = require('../script/logger')
const logger = require('../utils/logger')

exports.window = function (global, callback) {
// setup view controller
Expand Down
2 changes: 1 addition & 1 deletion app/static/js/crypter.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function handler () {
dialog.showOpenDialog({
title: 'Choose a file to Encrypt',
defaultPath: paths.documents, // open dialog at home directory
properties: ['openFile']
properties: ['openFile', 'openDirectory']
}, function (filePath) {
// callback for selected file returns undefined if file not selected by user
if (filePath && filePath.length === 1) {
Expand Down
File renamed without changes.
Loading

0 comments on commit 2d20ac9

Please sign in to comment.