0

I'm generating an access token for users and saving it in the user's browser cookie, i'm wondering if making iv and auth tag public like this is safe or not?

When i create a token i make it like iv.token.authTag

Here's my code for encrypting and decrypting the token

export const encryptAccessToken = (user: User): Result<string> => {
  try {
    const iv = crypto.randomBytes(8).toString('hex');
    const cipher = crypto.createCipheriv('aes-256-gcm', env.CIPHER_SECRET, iv);
    const payload = {
      created_at: dayjs().format('YYYY-MM-DDTHH:mm:ss'),
      expires_at: dayjs().add(1, 'day').format('YYYY-MM-DDTHH:mm:ss'),
      user,
    };
    const cipherStr = cipher.update(JSON.stringify(payload), 'utf-8', 'hex') + cipher.final('hex');
    const accessToken = [iv, cipherStr, cipher.getAuthTag().toString('hex')].join('.');

    return {
      ok: true,
      value: accessToken,
    };
  } catch (error) {
    return { ok: false, error };
  }
};

export const decryptAccessToken = (accessToken: string): Result<AccessTokenPayload> => {
  try {
    const [iv, cipher, authTag] = accessToken.split('.');
    if (!iv || !cipher || !authTag) {
      throw new Error('Invalid access token format');
    }

    const decipher = crypto.createDecipheriv('aes-256-gcm', env.CIPHER_SECRET, iv);
    decipher.setAuthTag(Buffer.from(authTag, 'hex'));
    const payloadStr = decipher.update(cipher, 'hex', 'utf-8') + decipher.final('utf-8');

    return { ok: true, value: JSON.parse(payloadStr) };
  } catch (error) {
    return { ok: false, error };
  }
};
  • 2
    Unlike the key, the IV/nonce and tag are not secret. It is common practice to concatenate IV/nonce, ciphertext and tag (typically also in this order). – Topaco Aug 07 '23 at 17:55
  • 1
    Concatenation is usually done on byte level without separator (instead of concatenation of hex encoded values separated by a delimiter). Separation on the decryption side is based on the known lengths of IV/nonce and tag (so no separator is required). – Topaco Aug 07 '23 at 19:53
  • 2
    In addition, an IV/nonce of 12 bytes and not 16 bytes is recommended for GCM. For this, a random 12 byte sequence should be used and not a half-length byte sequence that is blown up to the double by hex encoding. – Topaco Aug 07 '23 at 19:58
  • @Topaco oh okay thanks! can you show an example how concatenation at byte level would work? – xqcccccccccc Aug 07 '23 at 20:06
  • 1
    Take a look at this example: https://www.jdoodle.com/ia/KGW – Topaco Aug 07 '23 at 20:46
  • @Topaco just one more thing, is there a way to keep the encrypted token characters limited to alphanumeric? – xqcccccccccc Aug 07 '23 at 20:57
  • when creating the token if i update it to `Buffer.concat([....]).toString('hex')` i'm able to get just alphanumeric characters but i'm wondering if it makes it unsafe? – xqcccccccccc Aug 07 '23 at 21:05
  • 1
    Instead of Base64 encoding you can also use hex encoding, both are [binary-to-text encodings](https://en.wikipedia.org/wiki/Binary-to-text_encoding). Base64 has the advantage that it is more efficient (75%) than hex encoding (50%). Efficiency means the ratio of number of input bits to number of output bits. There is no difference in security. – Topaco Aug 07 '23 at 21:29
  • Got it. Thanks!! – xqcccccccccc Aug 07 '23 at 21:35
  • The question is language agnostic. You can get more information [here](https://stackoverflow.com/q/31851612/589259) – Maarten Bodewes Aug 07 '23 at 22:43

0 Answers0