如何在 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 等于预期值。
我有一个共享密钥,我需要从中派生一个 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 等于预期值。