Home > database >  Storing password, salt and IV on backend
Storing password, salt and IV on backend

Time:10-31

Consider this encryption function:

async function encrypt(message: string) {
  const salt = randomBytes(16);
  const iv = randomBytes(16);
  const password = 'Password used to generate key';

  const key = (await promisify(scrypt)(password, salt, 32)) as Buffer;
  const cipher = createCipheriv('aes-256-ctr', key, iv);

  const encryptedMessage = Buffer.concat([
    cipher.update(message),
    cipher.final(),
  ]);

  return { encryptedMessage, iv, salt };
}

and this section from crypto docs where it is said that the password, IV, and salt should not be stored as strings, rather byte sequences.

From what I've learned and know about symmetric encryption, I have to store salt and initialization vector with the encrypted message.

The questions I want to ask are:

  • Should I store the password as a byte sequence? If yes, can I store it in a .env file? And, if yes, how would that look like?
  • How do I store salt, IV, and encrypted message as bytes in a database? Is it possible to be stored in one field, separated by some special character?

CodePudding user response:

You misunderstood the documentation here it says that using strings in general is not a good way to transmit byte arrays as string come in many encodings and some of them are just not capable of representing all possible byte sequences namely UTF-8, which was created for human readable text not random byte sequences.

However there are encodings that were created to safely encode byte array as ascii characters like base64 or hex for that reason you can use them to encode your message. Like so for example:

async function encrypt(message) {
    const salt = randomBytes(16);
    const iv = randomBytes(16);
    const password = 'Password used to generate key';
  
    const key = (await promisify(scrypt)(password, salt, 32));
    const cipher = createCipheriv('aes-256-ctr', key, iv);
  
    let encryptedMessage = cipher.update(message, 'utf8', 'hex');
    encryptedMessage  = cipher.final('hex');
  
    return `${encryptedMessage}:${iv.toString('hex')}:${salt.toString('hex')}`;
  }

The result string can then be stored just like that in a single database field and you can later restore your buffers by doing something like this:

let encryptedMessage = Buffer.from(message_part, "hex");
const iv = Buffer.from(iv_part, "hex");
const salt = Buffer.from(salt_part, "hex");

Regarding .env i am not sure what you mean. The password is a normal string so you can simple store it in a .env file like this:

CRYPTO_PASSWORD="Password used to generate key"

and then load it like this using the dotenv package:

import * as dotenv from 'dotenv'
dotenv.config()
...
const password = process.env.CRYPTO_PASSWORD;

CodePudding user response:

I think you misunderstand what the docs say. All it says is that you should use raw bytes arrays, instead of strings, when interacting with the lib. Because of two reasons:

  1. If you pass a string to the lib, it will be converted into byte array, and this process generally lowers the entropy (e.g. a random utf-8 string is not as random as random sequence of bytes), and results in worse cryptographic properties of the cipher;
  2. Not every sequence of bytes is a utf-8 string, and so the default conversion of output to a string is not always possible. But there are other ways of course.

But this has nothing to do with what and how you store data.

Should I store the password as a byte sequence?

You can store your password however you want. That being said passwords are typically provided by users as strings instead of raw bytes. And so I don't see any advantage in converting them to byte arrays.

If yes, can I store it in a .env file? And, if yes, how would that look like?

Of course you can. If you mean the standard env file then simply

PASSWORD="my password"

I'm not sure what you mean here?

If you want to put a sequence of bytes here, then you have to encode it into a string. One way to do that is by using base64 encoding. Another one is to use hex encoding. But then of course you need an additional logic in your app for decoding.

How do I store salt, IV, and encrypted message as bytes in a database? Is it possible to be stored in one field, separated by some special character?

Putting them all in one field requires you to have some separation logic. Since this data can potentially have any sequence of bytes, then there is no special character that can serve as a separator. So you have the following options:

  1. Encode all of that data into for example base64 or hex, and then join them by a character that is not used by the encoding. For example for both base64 and hex the : char will work.
  2. Put them into separate fields.

Solution 2. may be more efficient (especially if the database supports binary formats) and may take less space (always measure). And I encourage you to do that. I don't see much benefit in storing this data in a single field.

  • Related