如何在 Node.js 中导出密钥和初始向量

How to Derive the Key and Initial Vector in Node.js

我有一个共享密钥,我需要从中派生一个 iv,以便我可以解密。

苹果商务聊天文档状态:

生成派生密钥和初始向量 运行 通过 X9.63 密钥派生函数和 SHA256 哈希函数的共享密钥。这会产生一个 48 字节的有效载荷。您的结果应该是 rV3qrszd0PMPgeRhNnlOYA==

这是我尝试过的。我在许多 'salt' 配置中使用了 scryptSync 和 pbkdf2Sync 加密函数。我不确定这些功能是否适合这项工作。

const crypto = require('crypto');

const keyLength = 48;
// sharedKey is a base64 string
const sharedKey = "2lvSJsBO2keUHRfvPG6C1RMUmGpuDbdgNrZ9YD7RYnvAcfgq/fjeYr1p0hWABeif";
// publicKey is a base64 string
const publicKey = "BDiRKNnPiPUb5oala31nkmCaXMB0iyWy3Q93p6fN7vPxEQSUlFVsInkJzPBBqmW1FUIY1KBA3BQb3W3Qv4akZ8kblqbmvupE/EJzPKbROZFBNvxpvVOHHgO2qadmHAjHSg=="

const key1 = crypto.scryptSync(sharedKey, 'salt', keyLength);
console.log(key2.toString('base64'));

const key2 = crypto.pbkdf2Sync(sharedKey, 'salt', 10000, keyLength, 'sha256');
console.log(key2.toString('base64'));

// results should be:
// mAzkYatDlz4SzrCyM23NhgL/+mE3eGgfUz9h1CFPhZM=
// iv: rV3qrszd0PMPgeRhNnlOYA==

下面是使用 X9.63 密钥派生函数派生密钥和初始向量的 Apple 示例代码。

    def ITOSP(self, longint, length):
        """ITOSP, short for Integer-to-Octet-String Primitive, converts a non-negative integer
        to an octet string of a specified length. This particular function is defined in the
        PKCS #1 v2.1: RSA Cryptography Standard (June 14, 2002)
        https://www.cryptrec.go.jp/cryptrec_03_spec_cypherlist_files/PDF/pkcs-1v2-12.pdf"""

        hex_string = "%X" % longint
        assert len(hex_string) <= 2 * length, "ITOSP function: Insufficient length for encoding"
        return binascii.a2b_hex(hex_string.zfill(2 * length))

    def KDFX963(self, inbyte_x, shared_data, key_length, hashfunct=sha256, hash_len=32):
        """KDFX963 is a key derivation function (KDF) that takes as input byte sequence inbyte_x
        and additional shared data shared_data and outputs a byte sequence key of length
        key_length. This function is defined in ANSI-X9.63-KDF, and this particular flavor of
        KDF is known as X9.63. You can read more about it from:
        http://www.secg.org/sec1-v2.pdf"""

        assert key_length >= 0, "KDFX963 function: key_length should be positive integer"
        k = key_length / float(hash_len)
        k = int(ceil(k))

        acc_str = ""
        for i in range(1, k+1):
            h = hashfunct()
            h.update(inbyte_x)
            h.update(self.ITOSP(i, 4))
            h.update(shared_data)
            acc_str = acc_str + h.hexdigest()

        return acc_str[:key_length * 2]

X9.63 KDF 是一个密钥推导函数,描述如here and here。 scrypt 和 PBKDF2 也是 KDF,但不同,因此当然无法用它们重现预期结果。

所以你需要一个支持 X.963 KDF 的 NodeJS 库。如果找不到,您也可以实现自己的。

X9.63 KDF 需要共享密钥和共享信息,并确定 keysize 大密钥如下:

  • 创建一个 4 字节计数器 ci 从 0x0000001 开始递增。
  • 连接数据conci = shared secret | ci |共享信息
  • 哈希结果 hashi = hash(conci)
  • 连接散列 hash1 |散列2 | ...直到生成长度为 keysize 的输出。

更正式地说,包括各种检查,算法在上面的链接中有描述。 Python 稍后在问题中发布的代码也实现了这个逻辑。

一种可能的 NodeJS 实现(省略规范中的检查)是:

var crypto = require('crypto');

var digest = 'sha256';
var digestLen = 32;

function X963KDF(sharedSecret, sharedInfo, keySize){
  var maxCount = Math.ceil(keySize/digestLen);
  var result = Buffer.allocUnsafe(0);
  for (var count = 1; count < maxCount + 1; count++){
      var counter = Buffer.allocUnsafe(4);
      counter.writeUInt32BE(count, 0);
      var current = Buffer.concat([sharedSecret, counter, sharedInfo]);
      var hash = crypto.createHash(digest).update(current).digest();
      result = Buffer.concat([result, hash]);
  }
  
  return result.slice(0, keySize);
}

测试:

问题中发布了共享秘密,但没有发布共享信息。互联网搜索显示发布的问题被描述为例如here,以便也可以确定共享信息(不过,如果您将此信息添加到您的问题中会更好):

var sharedSecret = Buffer.from('2lvSJsBO2keUHRfvPG6C1RMUmGpuDbdgNrZ9YD7RYnvAcfgq/fjeYr1p0hWABeif', 'base64')
var sharedInfo = Buffer.from('04389128d9cf88f51be686a56b7d6792609a5cc0748b25b2dd0f77a7a7cdeef3f111049494556c227909ccf041aa65b5154218d4a040dc141bdd6dd0bf86a467c91b96a6e6beea44fc42733ca6d139914136fc69bd53871e03b6a9a7661c08c74a', 'hex');

var keyiv = X963KDF(sharedSecret, sharedInfo, 48);
var key = keyiv.slice(0,32).toString('base64');
var iv = keyiv.slice(32, 48).toString('base64');
console.log("Key: ", key); // Key:  mAzkYatDlz4SzrCyM23NhgL/+mE3eGgfUz9h1CFPhZM=
console.log("IV: ", iv); // IV:  rV3qrszd0PMPgeRhNnlOYA==

生成的密钥和 IV 等于预期值。