bitchan / eccrypto

JavaScript Elliptic curve cryptography library
Creative Commons Zero v1.0 Universal
302 stars 98 forks source link

Node vs Browser implementations incompatibility? #81

Open fredeno opened 2 years ago

fredeno commented 2 years ago

We are opening this new issue following this question on Bad MAC errors occurring sometimes when decrypting data : https://github.com/bitchan/eccrypto/issues/63

Indeed we are facing this problem in our application. Sometimes data encrypted in the browser can't be decrypted in Node.js, sometimes data encrypted in Node.js can't be decrypted by the browser. This happens around once for 300 occurrences.

In our case, eccrypto is used classically in a Node.js backend application and also used in a React web application with the help of browserify and Node Buffer API for browser (https://github.com/feross/buffer) so that the same code can run on both environments.

We are reaching the step where we think it is an eccrypto incompatibility between browser and Node implementations but that still may be in the way we use it…

We managed to reproduce the behavior with this code :

const key = {
    privateKey: '507e1910e86cb57f66a36ae55660c66c130b60de4c2016a4d548357d054ca1a5',
    publicKey: '0499d72de2a4ac8e980ca664110d295bde3d83c122d07ac4fb6f9f34413e06b0cf20022672373968c0c19956af82864b1799141935965da8fb196348174c95bc1a'
}
const value = 'This is just a string to encrypt'
const iv = "1cb7e5a8f77f19535226730204ca6ef0"

const multipleEncrypt = async (n) => {
    const arrayOfEncrypted = []
    for (let i = 0; i < n; i++) {
        const encrypted = await eccrypto.encrypt(Buffer.from(key.publicKey, "hex"), Buffer.from(value), { iv: Buffer.from(iv, "hex") })
        const result = Buffer.from(encrypted.ephemPublicKey).toString("base64").concat(Buffer.from(encrypted.mac).toString("base64"), Buffer.from(encrypted.ciphertext).toString("base64"))
        arrayOfEncrypted.push(result)
    }
    console.log("array of encrypted values: ")
    const util = require('util')
    console.log(util.inspect(arrayOfEncrypted, { maxArrayLength: null }))
}

const multipleDecrypt = async (encrypted) => {
    for (let i = 0; i < encrypted.length; i++) {
        try {
            const ephemPublicKey = encrypted[i].substr(0, 88)
            const mac = encrypted[i].substr(88, 44)
            const ciphertext = encrypted[i].substr(132, encrypted.length)
            const data = {
                iv: Buffer.from(iv, "hex"),
                ephemPublicKey: Buffer.from(ephemPublicKey, "base64"),
                ciphertext: Buffer.from(ciphertext, "base64"),
                mac: Buffer.from(mac, "base64")
            };
            const result = await eccrypto.decrypt(Buffer.from(key.privateKey, "hex"), data)
            console.log("'" + encrypted[i] + "' decrypted => " + result.toString())
        } catch (e) {
            console.error("Cannot decrypt '" + encrypted[i] + "'", e)
        }
    }
}

First we execute multipleEncrypt(300) in Node.js which encrypts 300 times the same data with the same key and parameters.

Then we inject the resulting array of values in this code to execute it in the web application:

const arrayOfEncrypted = [
        'BIg5dEMKeM8iOm3r0Y0hgdpVRhCLl7pWQp5Rd5FV4uZKd7DMpPPpRMvyKrP7iVOhr7/6EsOvOQi0tO15nHRIap4=/ug6ZlyN07kVgpweJfy7lKKq5dDlxbfNuJ97cfD8ad8=PRG9x9R7ZOHIyLOiA9K0DgkzK7TUyHaKIb63r2PiCHjVMsUBNfFh96cItLgV97Jx',
        'BPKGILr53DScAxO/+LX6dSmDanSwtZtkskKx9MUyu3FG391glc4HUzyfKc+CTM3/jqui8Q+Pj2/nzXq2YtobOUM=NGuVEwasLdMWEQD+4B7FapCbOW2XTworwd7Oih1al4E=luxDF4Sc71d42DBfZaap+gaB3iAsa0LHZOULOaeN6lfEgtRogbGVBnsz98kYmQcE',
        // and 298 other occurrences...
        ]
multipleDecrypt(arrayOfEncrypted)

When run in the browser, all values are decrypted except one (the second one in this example) for which we get a "Bad MAC" error.

We have the same behavior when encrypting in browser and decrypting in Node.js. But when we force to use the eccrypto browser implementation in Node.js, that works perfectly! This is an interesting workaround but not very satisfying and this alternative is only available on Node anyway…

Is there something to fix in eccrypto or in the way we use it? Thanks in advance for your help.

ArjunRajJain commented 2 years ago

@fredeno did you ever find a solution to this?

fredeno commented 2 years ago

@fredeno did you ever find a solution to this?

@ArjunRajJain No news about this problem unfortunately. The only solution is the workaround I described and we've been living with it up to now: if decryption fails in Node.js, we try again forcing the browser implementation. It is not a clean solution because performance is bad on this case but it is OK for our goal as it happens around once every 300 decryption attempts. By chance, we have no need yet to proceed the other way round (encryption in Node and decryption in browser).

mystiko0x1 commented 8 months ago

@fredeno We found the possible root cause of this issue, please refer to PR #96 for more information.