从字符串中解密在 python 应用程序中加密的 NodeJS 中的 ChaCha20-Poly1305 二进制数据
Decrypting ChaCha20-Poly1305 binary data in NodeJS that was encrypted in python application from a string
我们有一个 Python 应用程序将字符串作为加密的二进制数据存储在 MongoDB 中,它使用
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
在 NodeJS 方面,我一直无法弄清楚如何解密数据,我有我们的盐和密钥,但据我所知,没有 IV 或 python 模块可能只是将所有这些隐藏在引擎盖下,因为所有 python 应用程序必须做的就是调用 encrypt(value, salt) 和 decrypt(value, salt)
Python:
class ChaChaEncryptedStringField(EncryptedStringField):
"""
A field which, given an encryption key and salt, will automatically encrypt/decrypt
sensitive data to avoid needing to do this before passing in. This encryption
method reliably produces a searchable string.
"""
def __init__(self, key, salt, *args, **kwargs):
"""Initialize the ChaChaEncryptedStringField.
Args:
key (str) -
salt (str) -
"""
class Hook:
def __init__(self, key, salt):
self.salt = salt
self.chacha = ChaCha20Poly1305(key)
def encrypt(self, value):
return self.chacha.encrypt(self.salt, value, None)
def decrypt(self, value):
return self.chacha.decrypt(self.salt, value, None)
self.encryption_hook = Hook(b64decode(key), b64decode(salt))
super(EncryptedStringField, self).__init__(*args, **kwargs)
Javascript(不工作但关闭):
const authTagLocation = data.buffer.length - 16;
const ivLocation = data.buffer.length - 28;
const authTag = data.buffer.slice(authTagLocation);
const iv = data.buffer.slice(ivLocation, authTagLocation);
const encrypted = data.buffer.slice(0, ivLocation);
const decipher = crypto.createDecipheriv('chacha20-poly1305', keyBuffer, iv,{ authTagLength: 16 } );
let dec = decipher.update(
data.buffer, 'utf-8', 'utf-8'
);
dec += decipher.final('utf-8');
return dec.toString();
通过一些研究和反复试验,我克服了抱怨不正确的 IV,并且密钥长度是正确的,但仍然得到乱码数据
所以我实际上得到了以下代码,但我不会声称完全理解正在发生的事情:
正在工作Javascript(盐是从秘密中提取的,使用提取的 IV 失败)
const authTagLength = 16
const authTagLocation = data.buffer.length - authTagLength;
const ivLocation = data.buffer.length - 16;
const authTag = data.buffer.slice(authTagLocation);
const iv = data.buffer.slice(ivLocation, authTagLocation);
const encrypted = data.buffer.slice(0, ivLocation);
const decipher = crypto.createDecipheriv('chacha20-poly1305', keyBuffer, saltBuffer,{ authTagLength: authTagLength } );
let dec = decipher.update(
encrypted, 'utf-8', 'utf-8'
);
dec += decipher.final('utf-8');
return dec.toString();
Python代码中所谓的salt其实就是nonce(或IV),见密码学 ChaCha20Poly1305
). The difference between nonce and salt is explained e.g. here 的文档。在下文中,我使用术语 nonce。
在NodeJS代码中,密文和标签的分离以一种过于复杂的方式进行,但(巧合的是)得到了正确的结果。 IV 在分离中不起作用。 tag为最后16字节,实际密文为tag前剩余数据
此外,目前没有进行身份验证,这是不安全的。要启用身份验证,必须在 final()
调用之前使用 setAuthTag()
设置标记。如果认证失败,则抛出异常。
以下示例显示了用于解密的可能的 NodeJS 实现。密文是使用发布的 Python 代码生成的:
const crypto = require('crypto');
const keyBuffer = Buffer.from('MDEyMzQ1Njc4OTAxMjM0NTAxMjM0NTY3ODkwMTIzNDU=', 'base64');
const nonceBuffer = Buffer.from('MDEyMzQ1Njc4OTAx', 'base64')
const dataBuffer = Buffer.from('4bAaXOlQGhLI3tAsJju0e8Z737eF683Izik+6Uz4axPKj6NbmGLXcCgxukIyo8whOsu2lEgg3llInLA=', 'base64')
const authTagLength = 16
const encrypted = dataBuffer.slice(0, -authTagLength)
const tag = dataBuffer.slice(-authTagLength);
const decipher = crypto.createDecipheriv('chacha20-poly1305', keyBuffer, nonceBuffer, {authTagLength: authTagLength});
decipher.setAuthTag(tag)
let decrypted;
try {
decrypted = decipher.update(encrypted, '', 'utf-8');
decrypted += decipher.final('utf-8');
console.log(decrypted);
} catch(e) {
console.log("Decryption failed!");
}
请注意 Python 代码中的以下漏洞:密钥和随机数在实例化时传递给 ChaChaEncryptedStringField
class。这导致相同的 key/IV 对用于对此实例执行的所有加密,这是不安全的,请参阅 here。正确的做法是为每次加密创建一个随机数。 nonce 不是秘密的,它与密文和标签一起传递,通常是串联的。
我们有一个 Python 应用程序将字符串作为加密的二进制数据存储在 MongoDB 中,它使用
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
在 NodeJS 方面,我一直无法弄清楚如何解密数据,我有我们的盐和密钥,但据我所知,没有 IV 或 python 模块可能只是将所有这些隐藏在引擎盖下,因为所有 python 应用程序必须做的就是调用 encrypt(value, salt) 和 decrypt(value, salt)
Python:
class ChaChaEncryptedStringField(EncryptedStringField):
"""
A field which, given an encryption key and salt, will automatically encrypt/decrypt
sensitive data to avoid needing to do this before passing in. This encryption
method reliably produces a searchable string.
"""
def __init__(self, key, salt, *args, **kwargs):
"""Initialize the ChaChaEncryptedStringField.
Args:
key (str) -
salt (str) -
"""
class Hook:
def __init__(self, key, salt):
self.salt = salt
self.chacha = ChaCha20Poly1305(key)
def encrypt(self, value):
return self.chacha.encrypt(self.salt, value, None)
def decrypt(self, value):
return self.chacha.decrypt(self.salt, value, None)
self.encryption_hook = Hook(b64decode(key), b64decode(salt))
super(EncryptedStringField, self).__init__(*args, **kwargs)
Javascript(不工作但关闭):
const authTagLocation = data.buffer.length - 16;
const ivLocation = data.buffer.length - 28;
const authTag = data.buffer.slice(authTagLocation);
const iv = data.buffer.slice(ivLocation, authTagLocation);
const encrypted = data.buffer.slice(0, ivLocation);
const decipher = crypto.createDecipheriv('chacha20-poly1305', keyBuffer, iv,{ authTagLength: 16 } );
let dec = decipher.update(
data.buffer, 'utf-8', 'utf-8'
);
dec += decipher.final('utf-8');
return dec.toString();
通过一些研究和反复试验,我克服了抱怨不正确的 IV,并且密钥长度是正确的,但仍然得到乱码数据
所以我实际上得到了以下代码,但我不会声称完全理解正在发生的事情:
正在工作Javascript(盐是从秘密中提取的,使用提取的 IV 失败)
const authTagLength = 16
const authTagLocation = data.buffer.length - authTagLength;
const ivLocation = data.buffer.length - 16;
const authTag = data.buffer.slice(authTagLocation);
const iv = data.buffer.slice(ivLocation, authTagLocation);
const encrypted = data.buffer.slice(0, ivLocation);
const decipher = crypto.createDecipheriv('chacha20-poly1305', keyBuffer, saltBuffer,{ authTagLength: authTagLength } );
let dec = decipher.update(
encrypted, 'utf-8', 'utf-8'
);
dec += decipher.final('utf-8');
return dec.toString();
Python代码中所谓的salt其实就是nonce(或IV),见密码学 ChaCha20Poly1305
). The difference between nonce and salt is explained e.g. here 的文档。在下文中,我使用术语 nonce。
在NodeJS代码中,密文和标签的分离以一种过于复杂的方式进行,但(巧合的是)得到了正确的结果。 IV 在分离中不起作用。 tag为最后16字节,实际密文为tag前剩余数据
此外,目前没有进行身份验证,这是不安全的。要启用身份验证,必须在 final()
调用之前使用 setAuthTag()
设置标记。如果认证失败,则抛出异常。
以下示例显示了用于解密的可能的 NodeJS 实现。密文是使用发布的 Python 代码生成的:
const crypto = require('crypto');
const keyBuffer = Buffer.from('MDEyMzQ1Njc4OTAxMjM0NTAxMjM0NTY3ODkwMTIzNDU=', 'base64');
const nonceBuffer = Buffer.from('MDEyMzQ1Njc4OTAx', 'base64')
const dataBuffer = Buffer.from('4bAaXOlQGhLI3tAsJju0e8Z737eF683Izik+6Uz4axPKj6NbmGLXcCgxukIyo8whOsu2lEgg3llInLA=', 'base64')
const authTagLength = 16
const encrypted = dataBuffer.slice(0, -authTagLength)
const tag = dataBuffer.slice(-authTagLength);
const decipher = crypto.createDecipheriv('chacha20-poly1305', keyBuffer, nonceBuffer, {authTagLength: authTagLength});
decipher.setAuthTag(tag)
let decrypted;
try {
decrypted = decipher.update(encrypted, '', 'utf-8');
decrypted += decipher.final('utf-8');
console.log(decrypted);
} catch(e) {
console.log("Decryption failed!");
}
请注意 Python 代码中的以下漏洞:密钥和随机数在实例化时传递给 ChaChaEncryptedStringField
class。这导致相同的 key/IV 对用于对此实例执行的所有加密,这是不安全的,请参阅 here。正确的做法是为每次加密创建一个随机数。 nonce 不是秘密的,它与密文和标签一起传递,通常是串联的。