Skip to content

Commit

Permalink
fix(weaver-fabric-node-sdk): made AES key length configurable in ECIE…
Browse files Browse the repository at this point in the history
…S functions

The Weaver Fabric interoperation-node-sdk used the "aes-128-ctr" algorithm
in a hardcoded manner for asymmetric encryption/decryption.
128-bit AES, though secure for classical computing, is quantum-unsafe.
So an option is added to use "aes-256-ctr" on demand.
The "aes-128-ctr" algorithm is still supported because many signing keys,
typically those used in the Fabric testnets, have embedded elliptic curve
parameters with key length 16 bytes, and those still need to be supported.

Signed-off-by: VRamakrishna <vramakr2@in.ibm.com>
  • Loading branch information
VRamakrishna authored and sandeepnRES committed Dec 21, 2023
1 parent e24458f commit e679801
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 9 deletions.
34 changes: 26 additions & 8 deletions weaver/sdks/fabric/interoperation-node-sdk/src/eciesCrypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const CURVE_P_384_Size = 384;
* Comments below indicate a mapping between variables defined here and in the
* 'Decrypt(...)' function implemented in 'ecies.go' in the Golang package
*/
function eciesDecryptMessage(recipientPrivateKey, cipherText, options) {
function eciesDecryptMessage(recipientPrivateKey, cipherText, options, aesKeyLength = AESKeyLength) {
const level = recipientPrivateKey.ecparams.keylen;
options.securityLevel = level;
processOption(options);
Expand Down Expand Up @@ -96,8 +96,8 @@ function eciesDecryptMessage(recipientPrivateKey, cipherText, options) {
const kdfOutput = hkdf(ZArray, ECIESKDFOutput, null, null, options);

const kbuf = Buffer.from(kdfOutput);
const aesKey = kdfOutput.slice(0, AESKeyLength); // 'Ke'
const hmacKey = kdfOutput.slice(AESKeyLength, AESKeyLength + HMACKeyLength);
const aesKey = kdfOutput.slice(0, aesKeyLength); // 'Ke'
const hmacKey = kdfOutput.slice(aesKeyLength, aesKeyLength + HMACKeyLength);

const hmacKeyHash = new options.hashFunctionKeyDerivation();
hmacKeyHash.update(bytesToBits(hmacKey));
Expand All @@ -108,7 +108,16 @@ function eciesDecryptMessage(recipientPrivateKey, cipherText, options) {
throw new Error("HMAC verify failed");
}
const iv = EM.slice(0, IVLength);
const cipher = crypto.createDecipheriv("aes-128-ctr", Buffer.from(aesKey), iv); // The Golang package implements AES-128-CTR
// The go-ethereum crypto/ecies package that encrypts/decrypts data uses AES-128-CTR by default for signing certs currently generated by Fabric
let aesAlgorithm;
if (aesKeyLength === 16) {
aesAlgorithm = "aes-128-ctr";
} else if (aesKeyLength === 32) {
aesAlgorithm = "aes-256-ctr";
} else {
throw new Error("Invalid AES key length supplied: " + aesKeyLength);
}
const cipher = crypto.createDecipheriv(aesAlgorithm, Buffer.from(aesKey), iv);
const decryptedBytes = cipher.update(EM.slice(IVLength));
return decryptedBytes;
}
Expand All @@ -135,7 +144,7 @@ function eciesDecryptMessage(recipientPrivateKey, cipherText, options) {
* @returns encrypted message as a Buffer.
*
*/
function eciesEncryptMessage(recipientPublicKey, msg, options) {
function eciesEncryptMessage(recipientPublicKey, msg, options, aesKeyLength = AESKeyLength) {
const level = recipientPublicKey.ecparams.keylen;
options.securityLevel = level;
processOption(options);
Expand All @@ -153,15 +162,24 @@ function eciesEncryptMessage(recipientPublicKey, msg, options) {
const Z = ephPrivKey.derive(pubKey.pub);
const kdfOutput = hkdf(Z.toArray(), ECIESKDFOutput, null, null, options);

const aesKey = kdfOutput.slice(0, AESKeyLength);
const hmacKey = kdfOutput.slice(AESKeyLength, AESKeyLength + HMACKeyLength);
const aesKey = kdfOutput.slice(0, aesKeyLength);
const hmacKey = kdfOutput.slice(aesKeyLength, aesKeyLength + HMACKeyLength);

const hmacKeyHash = new options.hashFunctionKeyDerivation();
hmacKeyHash.update(bytesToBits(hmacKey));
const hKm = bitsToBytes(hmacKeyHash.finalize());

const iv = crypto.randomBytes(IVLength);
const cipher = crypto.createCipheriv("aes-256-ctr", Buffer.from(aesKey), iv);
// The go-ethereum crypto/ecies package that encrypts/decrypts data uses AES-128-CTR by default for signing certs currently generated by Fabric
let aesAlgorithm;
if (aesKeyLength === 16) {
aesAlgorithm = "aes-128-ctr";
} else if (aesKeyLength === 32) {
aesAlgorithm = "aes-256-ctr";
} else {
throw new Error("Invalid AES key length supplied: " + aesKeyLength);
}
const cipher = crypto.createCipheriv(aesAlgorithm, Buffer.from(aesKey), iv);
const encryptedBytes = cipher.update(msg);
const EM = Buffer.concat([iv, encryptedBytes]);
const D = hmac(hKm, EM, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe("InteroperableHelper", () => {
});

describe("cryptographic functions", () => {
it("encrypt and decrypt a message", () => {
it("encrypt and decrypt a message with default 128-bit AES key", () => {
const data = '{ "data": "xyz" }';
const privKeyFile = `${__dirname}/data/privKey.pem`;
const privKeyPEM = fs.readFileSync(privKeyFile).toString();
Expand All @@ -128,6 +128,28 @@ describe("InteroperableHelper", () => {
});
});

describe("cryptographic functions", () => {
it("encrypt and decrypt a message with 256-bit AES key", () => {
const data = '{ "data": "xyz" }';
const privKeyFile = `${__dirname}/data/privKey.pem`;
const privKeyPEM = fs.readFileSync(privKeyFile).toString();
const privKey = keyutil.getKeyFromPlainPrivatePKCS8PEM(privKeyPEM);
const signCertFile = `${__dirname}/data/signCert.pem`;
const signCertPEM = fs.readFileSync(signCertFile).toString();
const pubKey = keyutil.getKey(signCertPEM);
const cryptoOptions = { hashAlgorithm: "SHA2" };
expect(() => {
const encryptedData = ecies.eciesEncryptMessage(pubKey, Buffer.from(data), cryptoOptions, 32); // 32 * 8 == 256
expect(encryptedData).to.be.a("Uint8Array");
expect(() => {
const decryptedData = ecies.eciesDecryptMessage(privKey, encryptedData, cryptoOptions, 32); // 32 * 8 == 256
expect(decryptedData).to.be.a("Uint8Array");
expect(decryptedData.toString()).to.equal(data);
}).to.not.throw();
}).to.not.throw();
});
});

describe("decrypt remote proposal response", () => {
it("decrypt proposal response using private key", () => {
const samplePropJSON = JSON.parse(fs.readFileSync(`${__dirname}/data/prop.json`).toString());
Expand Down

0 comments on commit e679801

Please sign in to comment.