kkAyataka / plusaes

Header only C++ AES cipher library
https://kkayataka.github.io/plusaes/doc/index.html
Boost Software License 1.0
182 stars 41 forks source link

Keep getting Tag mismatches when using decrypt_gcm (encoded with NodeJS Crypto aes-256-gcm) #48

Open TheOnlyJoey opened 1 month ago

TheOnlyJoey commented 1 month ago
const encryptSymmetric = (key, plaintext) => {
  const iv = crypto.randomBytes(12).toString('base64');
  const cipher = crypto.createCipheriv(
    "aes-256-gcm", 
    Buffer.from(key, 'base64'), 
    Buffer.from(iv, 'base64')
  );
  cipher.setAAD("SuperSecretKey")
  let ciphertext = cipher.update(plaintext, 'binary', 'binary');
  ciphertext += cipher.final('binary');
  const tag = cipher.getAuthTag()
  return { ciphertext, iv, tag }
}

When using a bog standard Crypto node.js setup for encrypting some data, it seems that whatever i do, i keep getting tag mismatches in decrypt_gcm (kErrorInvalidTag). I tried changing the encoding (converting back/from hex or base64 strings for everything), and it seems the size matches, but i can't get past the if (memcmp(tag, tagd, tag_size) != 0) check.

Anything obvious i am missing?

Edit: Just for reference, if i am doing encoding in plusaes as well, I don't have this problem. Only with the javascript tag.

kkAyataka commented 1 month ago

I tried to encrypt it for now.

Node.js

const crypto = require('crypto');

const encryptSymmetric = (keyStr, ivStr, plaintext) => {
  const key =Buffer.from(keyStr).toString('base64');
  const iv = Buffer.from(ivStr).toString('base64');
  const cipher = crypto.createCipheriv(
    "aes-256-gcm",
    Buffer.from(key, 'base64'),
    Buffer.from(iv, 'base64')
  );
  cipher.setAAD("SuperSecretKey")
  let ciphertext = cipher.update(plaintext, 'binary', 'hex');
  ciphertext += cipher.final('hex');
  const tag = cipher.getAuthTag()
  return { ciphertext, iv, tag }
}

// key: 12345678901234567890123456789012
const r = encryptSymmetric(
  "12345678901234567890123456789012",
  "123456789012",
  "Hello, plusaes");

console.log(r);
// {
//   ciphertext: 'c89791826d6de19189674ce5b466',
//   iv: 'MTIzNDU2Nzg5MDEy',
//   tag: <Buffer 1b eb 31 9c aa fc 35 c3 bd 1c 85 59 90 aa f7 66>
// }

plusaes.

#include "plusaes/plusaes.hpp"

#include <string>
#include <vector>
#include <iostream>

void print_bytes(const unsigned char * const bytes, const std::size_t bytes_size) {
    for (std::size_t i = 0; i < bytes_size; ++i) {
        std::cout << std::hex << (int)bytes[i] << " ";
    }
    std::cout << std::endl;
}

int main() {
    // parameters
    const std::string raw_data = "Hello, plusaes";
    std::vector<unsigned char> data(raw_data.begin(), raw_data.end());
    const std::vector<unsigned char> key = plusaes::key_from_string(&"12345678901234567890123456789012");
    const unsigned char iv[12] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2'};

    const std::string raw_aadata = "SuperSecretKey";
    std::vector<unsigned char> aadata(raw_aadata.begin(), raw_aadata.end());

    unsigned char tag[16] = {};
    plusaes::encrypt_gcm(
        data.data(), data.size(),
        aadata.data(), aadata.size(),
        &key[0], key.size(),
        &iv[0], 12,
        tag, 16);

    print_bytes(data.data(), data.size()); // c89791826d6de19189674ce5b466 
    print_bytes(tag, 16); // 1b eb 31 9c aa fc 35 c3 bd 1c 85 59 90 aa f7 66

   // decrypt
    auto e = plusaes::decrypt_gcm(
        data.data(), data.size(),
        aadata.data(), aadata.size(),
        &key[0], key.size(),
        &iv[0], 12,
        tag, 16);

    std::cout << "plusaes::Error: " << e << std::endl; // 0 (plusaes::kErrorOk)
    std::cout << data.data() << std::endl; // Hello, plusaes
}
TheOnlyJoey commented 1 month ago

The problem is not Encrypting in both, but decrypting in plusaes what was encrypted by the NodeJS Crypto library. Whatever tag I get in NodeJS, i can't seem to get validated while decrypting in plusaes.

kkAyataka commented 1 month ago

I added the decryption code.

Encryption results are the same. So the result of Node.js can be decrypted with plusaes.

TheOnlyJoey commented 1 month ago

In your Plusaes example you encrypt/decrypt in Plusaes only, as mentioned i have no problem getting that to work consistently. In the c++ example, your tag and data are generated by plusaes on encryption, so it makes sense it all is correct on decryption.

To clarify again, the problem is when passing along the information from NodeJS/Crypto-Plusaes (enc/dec) (the tag, the IV, aadata etc), not with passing between nodejs-nodejs (enc/dec) or plusaes-plusaes (enc/dec)

kkAyataka commented 1 month ago
std::vector<unsigned char> encrypted_from_nodejs = {
    0xc8, 0x97, 0x91, 0x82, 0x6d, 0x6d, 0xe1, 0x91, 0x89, 0x67, 0x4c, 0xe5, 0xb4, 0x66
};
const std::string raw_aadata_from_nodejs = "SuperSecretKey";
std::vector<unsigned char> aadata_from_nodejs(raw_aadata_from_nodejs.begin(), raw_aadata_from_nodejs.end());
std::vector<unsigned char> iv_same_nodejs = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2'};
std::vector<unsigned char> key_same_nodejs = plusaes::key_from_string(&"12345678901234567890123456789012");
std::vector<unsigned char> tag_from_nodejs = {0x1b, 0xeb, 0x31, 0x9c, 0xaa, 0xfc, 0x35, 0xc3, 0xbd, 0x1c, 0x85, 0x59, 0x90, 0xaa, 0xf7, 0x66};

// decrypt
auto e = plusaes::decrypt_gcm(
    &encrypted_from_nodejs[0], encrypted_from_nodejs.size(),
    &aadata_from_nodejs[0], aadata_from_nodejs.size(),
    &key_same_nodejs[0], key_same_nodejs.size(),
    &iv_same_nodejs[0], 12,
    &tag_from_nodejs[0], 16);

std::cout << "plusaes::Error: " << e << std::endl; // 0 (plusaes::kErrorOk)
std::cout << (char*)encrypted_from_nodejs.data() << std::endl; // Hello, plusaes

Or perhaps you are asking about how to read and write binary files?