Skip to content

Instantly share code, notes, and snippets.

@jordanbtucker
Last active November 10, 2023 20:00
Show Gist options
  • Save jordanbtucker/e9dde26b372048cf2cbe85a6aa9618de to your computer and use it in GitHub Desktop.
Save jordanbtucker/e9dde26b372048cf2cbe85a6aa9618de to your computer and use it in GitHub Desktop.

Revisions

  1. jordanbtucker revised this gist Jun 16, 2020. 2 changed files with 2 additions and 2 deletions.
    2 changes: 1 addition & 1 deletion prompt-for-password.js
    Original file line number Diff line number Diff line change
    @@ -40,7 +40,7 @@ async function run() {
    // scrypt uses the password, salt, and other parameters to derive the
    // encryption key. The other parameters determine how much time it takes the
    // CPU to derive the key, which mitigates brute force attacks, except for the
    // last parameter which specified the length of the key in bytes. A change to
    // last parameter which specifies the length of the key in bytes. A change to
    // any of these parameters will result in a different key.
    const key = scrypt(dbPass, dbSalt, 32768, 8, 1, 32)

    2 changes: 1 addition & 1 deletion store-password-in-keychain.js
    Original file line number Diff line number Diff line change
    @@ -50,7 +50,7 @@ async function run() {
    // scrypt uses the password, salt, and other parameters to derive the
    // encryption key. The other parameters determine how much time it takes the
    // CPU to derive the key, which mitigates brute force attacks, except for the
    // last parameter which specified the length of the key in bytes. A change to
    // last parameter which specifies the length of the key in bytes. A change to
    // any of these parameters will result in a different key.
    const key = scrypt(dbPass, dbSalt, 32768, 8, 1, 32)

  2. jordanbtucker revised this gist Jun 16, 2020. 2 changed files with 2 additions and 2 deletions.
    2 changes: 1 addition & 1 deletion prompt-for-password.js
    Original file line number Diff line number Diff line change
    @@ -37,7 +37,7 @@ async function run() {
    },
    ])

    // script uses the password, salt, and other parameters to derive the
    // scrypt uses the password, salt, and other parameters to derive the
    // encryption key. The other parameters determine how much time it takes the
    // CPU to derive the key, which mitigates brute force attacks, except for the
    // last parameter which specified the length of the key in bytes. A change to
    2 changes: 1 addition & 1 deletion store-password-in-keychain.js
    Original file line number Diff line number Diff line change
    @@ -47,7 +47,7 @@ async function run() {
    await keytar.setPassword('nedb-example', 'dbPass', dbPass)
    }

    // script uses the password, salt, and other parameters to derive the
    // scrypt uses the password, salt, and other parameters to derive the
    // encryption key. The other parameters determine how much time it takes the
    // CPU to derive the key, which mitigates brute force attacks, except for the
    // last parameter which specified the length of the key in bytes. A change to
  3. jordanbtucker created this gist Jun 16, 2020.
    112 changes: 112 additions & 0 deletions prompt-for-password.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,112 @@
    /**
    * This is an example of app that uses an ecrypted NeDB database. It prompts the
    * user for a password, decrypts the database, displays any existing records,
    * promtps the user for a new record to store, encrypts that record, then exits.
    * The password must be given each time the app is started.
    */

    const crypto = require('crypto')
    const inquirer = require('inquirer')
    const scrypt = require('scryptsy')
    const nedb = require('nedb-promises')

    // A salt is required for scrypt to derive an encryption key from a password. A
    // salt is a random value used to mitigate rainbow tables. It does not need to
    // be kept secret, but it needs to be consitent as only the same password and
    // salt combination will result in the same key. In this example, the same salt
    // is used for all instances of this app, which means that the encrypted
    // database is portable. But this also makes this app more susceptible to
    // rainbow tables.
    const dbSalt = Buffer.from('GagZgR/G2isc0IbKKYyFLg==')

    // Alternatively, you can generate a salt on the first run of the app and store
    // it somewhere. This will make the app more resilient to rainbow tables, but
    // the encrypted database will no longer be portable.

    // This is the main function. It will be called at the end of the file.
    async function run() {
    // Propmpt the user for the database encryption password. This, along with the
    // salt, will be used by scrypt to generate an encryption key for the database.
    // The same password must be used each time the app runs. If the password is
    // lost or forgotten, then the database cannot be decrypted or recovered.
    const {dbPass} = await inquirer.prompt([
    {
    name: 'dbPass',
    type: 'password',
    message: 'Database password:',
    },
    ])

    // script uses the password, salt, and other parameters to derive the
    // encryption key. The other parameters determine how much time it takes the
    // CPU to derive the key, which mitigates brute force attacks, except for the
    // last parameter which specified the length of the key in bytes. A change to
    // any of these parameters will result in a different key.
    const key = scrypt(dbPass, dbSalt, 32768, 8, 1, 32)

    // We're using nedb-promises so that we can await the database operations, but
    // regular nedb works too.
    const db = nedb.create({
    filename: 'example.db',

    // This is the encryption function. It takes plaintext, which is JSON,
    // encrypts it with the derived key, and returns the encrypted ciphertext as
    // a Base 64 string. A random IV is generated and stored for each record to
    // mitigate padding attacks. Note that you don't need to return JSON; any
    // string will do.
    afterSerialization(plaintext) {
    const iv = crypto.randomBytes(16)
    const aes = crypto.createCipheriv('aes-256-cbc', key, iv)
    let ciphertext = aes.update(plaintext)
    ciphertext = Buffer.concat([iv, ciphertext, aes.final()])
    return ciphertext.toString('base64')
    },

    // This is the decryption function. It takes the encrypted ciphertext,
    // decrypts it with the stored IV and derived key, and returns the decrypted
    // plaintext, which is JSON. Note that this function must return JSON, since
    // that is what NeDB expects.
    beforeDeserialization(ciphertext) {
    const ciphertextBytes = Buffer.from(ciphertext, 'base64')
    const iv = ciphertextBytes.slice(0, 16)
    const data = ciphertextBytes.slice(16)
    const aes = crypto.createDecipheriv('aes-256-cbc', key, iv)
    let plaintextBytes = Buffer.from(aes.update(data))
    plaintextBytes = Buffer.concat([plaintextBytes, aes.final()])
    return plaintextBytes.toString()
    },
    })

    // In the event that the wrong password is entered, when NeDB tries to decrypt
    // the records, it will be given garbage (i.e. not JSON) so it will return an
    // error indicating the the database is corrupt.

    // Display the currently stored values, if any, which were decrypted from the
    // database file.
    console.log('Current values stored in database: ')
    const rows = await db.find()
    for (const row of rows) {
    console.log(`${row.date}: ${row.value}`)
    }

    // Prompt for a new value to store, which is stored along with a timestamp.
    console.log('Store a new value in the database.')
    const {value} = await inquirer.prompt([
    {
    name: 'value',
    type: 'input',
    message: 'Value:',
    },
    ])

    await db.insert({value, date: new Date()})

    console.log('Value stored. Restart the app to see the values.')
    }

    // Run the async main function. If there are any errors, report them and close
    // the process with an exit code.
    run().catch(err => {
    console.error(err)
    process.exitCode = err.code || 1
    })
    122 changes: 122 additions & 0 deletions store-password-in-keychain.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,122 @@
    /**
    * This is an example of app that uses an ecrypted NeDB database. It prompts the
    * user for a password, decrypts the database, displays any existing records,
    * promtps the user for a new record to store, encrypts that record, then exits.
    * The password must be given each time the app is started.
    */

    const crypto = require('crypto')
    const keytar = require('keytar')
    const inquirer = require('inquirer')
    const scrypt = require('scryptsy')
    const nedb = require('nedb-promises')

    // A salt is required for scrypt to derive an encryption key from a password. A
    // salt is a random value used to mitigate rainbow tables. It does not need to
    // be kept secret, but it needs to be consitent as only the same password and
    // salt combination will result in the same key. In this example, the same salt
    // is used for all instances of this app, which means that the encrypted
    // database is portable. But this also makes this app more susceptible to
    // rainbow tables.
    const dbSalt = Buffer.from('GagZgR/G2isc0IbKKYyFLg==')

    // Alternatively, you can generate a salt on the first run of the app and store
    // it somewhere. This will make the app more resilient to rainbow tables, but
    // the encrypted database will no longer be portable.

    // This is the main function. It will be called at the end of the file.
    async function run() {
    // Retrieve the database encryption password from the system keychain. If no
    // password exists in the keychain, prompt the user for one, then store it in
    // the keychain. This, along with the salt, will be used by scrypt to generate
    // an encryption key for the database. The same password must be used each
    // time the app runs. If the password is lost or forgotten, then the database
    // cannot be decrypted or recovered.
    let dbPass = await keytar.getPassword('nedb-example', 'dbPass')
    if (dbPass == null) {
    const {userDBPass} = await inquirer.prompt([
    {
    name: 'userDBPass',
    type: 'password',
    message: 'Database password:',
    },
    ])

    dbPass = userDBPass

    await keytar.setPassword('nedb-example', 'dbPass', dbPass)
    }

    // script uses the password, salt, and other parameters to derive the
    // encryption key. The other parameters determine how much time it takes the
    // CPU to derive the key, which mitigates brute force attacks, except for the
    // last parameter which specified the length of the key in bytes. A change to
    // any of these parameters will result in a different key.
    const key = scrypt(dbPass, dbSalt, 32768, 8, 1, 32)

    // We're using nedb-promises so that we can await the database operations, but
    // regular nedb works too.
    const db = nedb.create({
    filename: 'example.db',

    // This is the encryption function. It takes plaintext, which is JSON,
    // encrypts it with the derived key, and returns the encrypted ciphertext as
    // a Base 64 string. A random IV is generated and stored for each record to
    // mitigate padding attacks. Note that you don't need to return JSON; any
    // string will do.
    afterSerialization(plaintext) {
    const iv = crypto.randomBytes(16)
    const aes = crypto.createCipheriv('aes-256-cbc', key, iv)
    let ciphertext = aes.update(plaintext)
    ciphertext = Buffer.concat([iv, ciphertext, aes.final()])
    return ciphertext.toString('base64')
    },

    // This is the decryption function. It takes the encrypted ciphertext,
    // decrypts it with the stored IV and derived key, and returns the decrypted
    // plaintext, which is JSON. Note that this function must return JSON, since
    // that is what NeDB expects.
    beforeDeserialization(ciphertext) {
    const ciphertextBytes = Buffer.from(ciphertext, 'base64')
    const iv = ciphertextBytes.slice(0, 16)
    const data = ciphertextBytes.slice(16)
    const aes = crypto.createDecipheriv('aes-256-cbc', key, iv)
    let plaintextBytes = Buffer.from(aes.update(data))
    plaintextBytes = Buffer.concat([plaintextBytes, aes.final()])
    return plaintextBytes.toString()
    },
    })

    // In the event that the wrong password is entered, when NeDB tries to decrypt
    // the records, it will be given garbage (i.e. not JSON) so it will return an
    // error indicating the the database is corrupt.

    // Display the currently stored values, if any, which were decrypted from the
    // database file.
    console.log('Current values stored in database: ')
    const rows = await db.find()
    for (const row of rows) {
    console.log(`${row.date}: ${row.value}`)
    }

    // Prompt for a new value to store, which is stored along with a timestamp.
    console.log('Store a new value in the database.')
    const {value} = await inquirer.prompt([
    {
    name: 'value',
    type: 'input',
    message: 'Value:',
    },
    ])

    await db.insert({value, date: new Date()})

    console.log('Value stored. Restart the app to see the values.')
    }

    // Run the async main function. If there are any errors, report them and close
    // the process with an exit code.
    run().catch(err => {
    console.error(err)
    process.exitCode = err.code || 1
    })