dchest / scrypt-async-js

Fast "async" scrypt implementation in JavaScript
http://dchest.github.io/scrypt-async-js/
BSD 2-Clause "Simplified" License
139 stars 26 forks source link

how to migrate from node-scrypt to scrypt-async? #44

Closed ccoenen closed 6 years ago

ccoenen commented 6 years ago

I am trying to read "old" hashes from node-scrypt. I can read the structure and parse it into individual things like logN, r, p, and so on. I still fail to verify a node-scrypt hash using scrypt-async. Can anyone help me achieve this?

Here's a short gist, including

https://gist.github.com/ccoenen/3b228a32790ad0a5e1b29adfde963e96

Can anyone help me find my error?

dchest commented 6 years ago

node-scrypt uses a part of scrypt utility file format, which you almost successfully figured out. The error is that you're comparing the "signature" created by node-scrypt — HMAC of the data with the key derived with scrypt KDF — with the key derived using scrypt-async. That is, instead of comparing the result of scrypt-async, you need to compare HMACs:

var scrypt = require('scrypt-async');
var scryptOld = require('scrypt');
var crypto = require('crypto');

// Format as seen in node-scrypt, written by Barry Steyn. Scrypt by Colin Percival.
// https://github.com/barrysteyn/node-scrypt/blob/master/scrypt/scrypt-1.2.0/keyderivation.c#L60-L77 and
// https://security.stackexchange.com/a/91050/50616
// scrypt paper: https://www.tarsnap.com/scrypt/scrypt.pdf (not helpful for this :-( )
function parseScrypt(hexString) {
    var b = Buffer.from(hexString, "hex");
    var sha256 = crypto.createHash('SHA256');

    if (b.toString("ASCII", 0, 6) !== 'scrypt') {
        throw "this is not scrypt";
    }

    var parsed = {
        first64bytes: b.slice(0, 64),
        logN: b.readUInt8(7),
        r: b.readUInt32BE(8),
        p: b.readUInt32BE(12),
        salt: b.slice(16, 48),
        checksum: b.slice(48, 64),
        signature: b.slice(64, 96)
    };

    sha256.update(b.slice(0, 48));
    var digest = sha256.digest().slice(0, 16);
    if (Buffer.compare(digest, parsed.checksum) !== 0) {
        throw "something is odd while parsing: checksums do not match";
    }

    return parsed;
}

// testing: this generates a hash from node-scrypt.
var hash = scryptOld.kdfSync("hello", scryptOld.paramsSync(0.1)).toString("hex"); 

// we'll parse it using our function from above.
var scryptParams = parseScrypt(hash);
console.log(scryptParams);
console.log("->   original: %s", scryptParams.signature.toString("hex"));

// let's see if we can verify it! This means recreating a hash with the same parameters and trying to get the same result.
scrypt(Buffer.from("hello", "UTF-8"), scryptParams.salt, {
    logN: scryptParams.logN,
    r: scryptParams.r,
    p: scryptParams.p,
    dkLen: 64,  // **** CHANGED ***
    encoding: "binary" // *** CHANGED ***
}, function (key) {
         // ***** ADDED ****
    var hmacKey = key.subarray(32);
    var result = crypto.createHmac('sha256', hmacKey).update(scryptParams.first64bytes).digest("hex");
    console.log("-> comparison: %s", result);
});

According to scrypt FORMAT document linked above, we derive 64-byte key, the first part of which is encryption key (which we discard for our case), and the second part is HMAC key, which we use to get the signature of the first 64 bytes of original data.

ccoenen commented 6 years ago

Amazing! Thank you for your help! I stared at this for the better part of the evening and couldn't figure it out on my own.

Would you be interested in a PR that reads and verifies node-scrypt style output? (if you want I can also write something that generates this output, I'll probably need this anyway).

dchest commented 6 years ago

I think it would make more sense to have it as a separate project. If you do it, feel free to mention here the repo URL and I'll paste it into the README. Thanks!

ccoenen commented 6 years ago

Here's the package. It does not cover all the api possibilities of either one, but it should be a good start. https://www.npmjs.com/package/node-scrypt-async-compatibility