CyberChef/src/core/operations/DecryptKeystoreFile.mjs
2023-04-26 10:24:39 -04:00

140 lines
5.9 KiB
JavaScript

/**
* Decrypts ETH keystore files, given the password.
*
* @author dgoldenberg [virtualcurrency@mitre.org]
* @copyright MITRE 2023
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import JSSHA3 from "js-sha3";
import Utils from "../Utils.mjs";
import {fromHex} from "../lib/Hex.mjs";
import scryptsy from "scryptsy";
import { isWorkerEnvironment } from "../Utils.mjs";
import forge from "node-forge";
/**
* JPath expression operation
*/
class DecryptKeystoreFile extends Operation {
/**
* Decrypt Keystore constructor
*/
constructor() {
super();
this.name = "Decrypt Keystore File";
this.module = "Crypto";
this.description = "Attempts to decrypt the given ETH keystore file, with the passed in password. Will return the private key if successful, error out if not.";
this.inputType = "string";
this.outputType = "string";
this.infoURL = "https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition";
this.args = [
{
name: "password",
type: "string",
value: ""
},
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const password = args[0];
let jsonObj, dkey;
// We parse the JSON object first and throw an error if its not JSON.
try {
jsonObj = JSON.parse(input);
} catch (err) {
throw new OperationError(`Invalid input, not JSON. Data: ${err.message}`);
}
// We then check for a crypto property, and that crypto should have a kdf and kdfparams property.
if (!Object.prototype.hasOwnProperty.call(jsonObj, "crypto") || !Object.prototype.hasOwnProperty.call(jsonObj.crypto, "kdfparams") || !Object.prototype.hasOwnProperty.call(jsonObj.crypto, "kdf")) {
throw new OperationError(`Error. Invalid JSON blob, missing either a crypto, crypto.kdf or crypto.kdfparams object.`);
}
const kdfParams = jsonObj.crypto.kdfparams;
const kdfType = jsonObj.crypto.kdf;
// We compute the kdf.
if (kdfType === "scrypt") {
try {
// We compute the salt, and the compute the scrypt.
const salt = Buffer.from(Utils.convertToByteArray(kdfParams.salt, "hex"));
const data = scryptsy(password, salt, kdfParams.n, kdfParams.r, kdfParams.p, kdfParams.dklen,
p =>{
if (isWorkerEnvironment()) self.sendStatusMessage(`Progress: ${p.percent.toFixed(0)}%`);
});
// Result of the SCRYPT in hex.
dkey = data.toString("hex");
} catch (err) {
throw new OperationError("Error: " + err.toString());
}
} else if (kdfType === "pbkdf2") {
// If the kdf is PBKDF2, we check to make sure it has a prf property, and that property is hmac-sha256
if (!Object.prototype.hasOwnProperty.call(kdfParams, "prf") || kdfParams.prf !== "hmac-sha256") {
throw new OperationError(`Error with PBKDF2. Either HMAC function not present, or is not hmac-sha256. It is: ` + JSON.stringify(kdfParams));
}
// We compute the pbkdf2 and cast to hex.
const iterations = kdfParams.c;
const keyLength = kdfParams.dklen;
const salt = Utils.convertToByteString(kdfParams.salt, "hex");
dkey = forge.util.bytesToHex(forge.pkcs5.pbkdf2(password, salt, iterations, keyLength, "sha256"));
} else {
// If there's a different KDF, we err out.
throw new OperationError("We don't support KDF type " + kdfType + " ... yet.");
}
const mackey = dkey.slice(-32,);
const decryptionkey = dkey.slice(0, 32);
const ciphertext = jsonObj.crypto.ciphertext;
const hmac = jsonObj.crypto.mac;
const algo = JSSHA3.keccak256;
const testmac = algo(fromHex(mackey + ciphertext, "hex"));
if (testmac === hmac) {
// If the MAC passes, we can decrypt.
// We check for the right data to decrypt.
if (!Object.prototype.hasOwnProperty.call(jsonObj.crypto, "cipherparams") || !Object.prototype.hasOwnProperty.call(jsonObj.crypto.cipherparams, "iv")) {
throw new OperationError("We are missing needed cipherparams and IV.");
}
if (!Object.prototype.hasOwnProperty.call(jsonObj.crypto, "ciphertext") || !Object.prototype.hasOwnProperty.call(jsonObj.crypto.cipherparams, "iv")) {
throw new OperationError("We are the ciphertext");
}
// We grab the key, and IV, and ciphertext
const key = Utils.convertToByteString(decryptionkey, "hex");
const iv = Utils.convertToByteString(jsonObj.crypto.cipherparams.iv, "hex");
const cipherinput = Utils.convertToByteString(jsonObj.crypto.ciphertext, "hex");
// We create the decryptor.
const decipher = forge.cipher.createDecipher("AES-CTR", key);
// We do the decryption.
decipher.start({
iv: iv.length === 0 ? "" : iv,
tag: undefined,
additionalData: undefined
});
decipher.update(forge.util.createBuffer(cipherinput));
const result = decipher.finish();
if (result) {
return decipher.output.toHex();
} else {
throw new OperationError("Unable to decrypt keystore with these parameters.");
}
} else {
// In this case the MAC failed so we error out.
throw new OperationError("MAC error. We got: " + testmac + " , but we wanted. " + hmac);
}
}
}
export default DecryptKeystoreFile;