From e67980141d2fb2e54ef8408e36dbab55b7195905 Mon Sep 17 00:00:00 2001 From: VRamakrishna Date: Mon, 18 Dec 2023 14:33:44 +0000 Subject: [PATCH] fix(weaver-fabric-node-sdk): made AES key length configurable in ECIES 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 --- .../src/eciesCrypto.js | 34 ++++++++++++++----- .../test/InteroperableHelper.js | 24 ++++++++++++- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/weaver/sdks/fabric/interoperation-node-sdk/src/eciesCrypto.js b/weaver/sdks/fabric/interoperation-node-sdk/src/eciesCrypto.js index 70496955eb..082084fcc1 100644 --- a/weaver/sdks/fabric/interoperation-node-sdk/src/eciesCrypto.js +++ b/weaver/sdks/fabric/interoperation-node-sdk/src/eciesCrypto.js @@ -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); @@ -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)); @@ -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; } @@ -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); @@ -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); diff --git a/weaver/sdks/fabric/interoperation-node-sdk/test/InteroperableHelper.js b/weaver/sdks/fabric/interoperation-node-sdk/test/InteroperableHelper.js index 8ebc612ca1..2b2f3a808b 100644 --- a/weaver/sdks/fabric/interoperation-node-sdk/test/InteroperableHelper.js +++ b/weaver/sdks/fabric/interoperation-node-sdk/test/InteroperableHelper.js @@ -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(); @@ -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());