paragonie/ciphersweet

Boring Crypto Incompatible with Node.js

timsweb opened this issue · 2 comments

It's not possible to encrypt/decrypt between the PHP library (v3.0.5) and Node library (v2.0.5). I believe this is because they treat AAD differently. In node the AAD is concatenated to the nonce when calculating the MAC. In PHP the AAD is not concatenated.

In node:

       if (aad.length >= 0) {
            if (!Buffer.isBuffer(aad)) {
                aad = await Util.toBuffer(aad);
            }
            aad = Buffer.concat([nonce, aad]);
        } else {
            aad = nonce;
        }

In PHP:

        if (is_null($aad)) {
            $aad = '';
        }

I'm not sure if this is the only source of incompatibility, but as it stands I can't find a way to have portability between PHP and Node. I think care would be needed in any change around this to prevent breaking decrypting of any stored values. Perhaps the behaviour should be configurable.

Edit: I tried making the behaviour configurable and that got me past the MAC check, but decrypting in node a value encrypted from PHP resulted in corrupt data. I believe the inputs to the xchacha20 functions are the same in both languages, but the output I'm getting is different.

In Node:

        const xchacha = new XChaCha20();
        const decrypted = await xchacha.decrypt(
            encrypted,
            nonce,
            (await this.getEncryptionKey(encKey)).getRawKey()
        );

        await sodium.sodium_memzero(encKey);
        return decrypted;

in PHP:

        return \sodium_crypto_stream_xchacha20_xor(
            $encrypted,
            $nonce,
            $this->getEncryptionKey($key)->getRawKey()
        );

To test this hypothesis I've thrown together a quick test case:

echo \sodium_crypto_stream_xchacha20_xor(
    hex2bin('549cad2cd9bc64'),
    hex2bin('9e985dfdfdf85321b3171596a213fd3e819f0d9800fce29f'),
    hex2bin('82a9032e85e88ac6ed8365b8f3280d9ff9d8a88728886d18d9b52dd0fc6c5da1')
);
//outputs "qwertyu"
const { ChaCha20, HChaCha20, XChaCha20 } = require('xchacha20-js');

const xchacha = new XChaCha20();
xchacha.decrypt(
    Buffer.from('549cad2cd9bc64', 'hex'),
    Buffer.from('9e985dfdfdf85321b3171596a213fd3e819f0d9800fce29f', 'hex'),
    Buffer.from('82a9032e85e88ac6ed8365b8f3280d9ff9d8a88728886d18d9b52dd0fc6c5da1', 'hex')
).then(b => console.log(b.toString('utf-8')));
//outputs ")���<\"

Thanks
Tim

This is an issue with the JS implementation; the PHP implementation is canonical. We'll look into it.