Ami*_*jaj 2 encryption node.js swift
我正在尝试在 NodeJS (Electron) 中创建一个应用程序作为跨平台桌面应用程序。这将与 iOS 上使用 SWIFT 开发的移动应用程序配对。作为共享数据的一部分,它使用 AES-256-GCM 算法进行加密。我在 SWIFT 中有以下加密和解密方法:
//listItems is an array of the following structure:
// - id: Int, title: String, data: String, ldate: String
func encrypt(listItems: [ListItem], pass: String) -> String{
let encoder = JSONEncoder()
do{
let data = try encoder.encode(listItems)
let key = SymmetricKey(data: SHA256.hash(data: pass.data(using: .utf8)!))
let iv = AES.GCM.Nonce()
let sealedBox = try AES.GCM.seal(data, using: key, nonce: iv)
return sealedBox.combined?.base64EncodedData()
}catch{
fatalError("Couldn't encrypt data\(error)")
}
}
func decrypt(data: Data, pass: String) -> [ListItem]{
do{
let key = SymmetricKey(data: SHA256.hash(data: pass.data(using: .utf8)!))
let mySealedBox = try AES.GCM.SealedBox(combined: Data(base64Encoded: data)!)
let content = try AES.GCM.open(mySealedBox, using: key)
return load(content)
}catch{
fatalError("Couldn't encrypt data\(error)")
}
}
func load<T: Decodable>(_ data: Data) -> T{
do{
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
}catch{
fatalError("Could not parse the data")
}
}
Run Code Online (Sandbox Code Playgroud)
对于 NodeJS,我有以下功能:
const crypto = require('crypto')
module.exports = {
encryptData(data,password){
let password_hash = crypto.createHash('sha256').update(password, 'utf-8').digest('hex').slice(0,32).toLowerCase();
let iv = crypto.pseudoRandomBytes(12);
iv = Buffer.from('mBj0tzBUxDFmix1T', 'base64');
let cipher = crypto.createCipheriv('aes-256-gcm', password_hash, iv);
let encryptedData = Buffer.from(cipher.update(data, 'utf8', 'hex') + cipher.final('hex'), 'hex');
console.log(' --------------- ENC BEGIN ---------------');
console.log(`IV Length: ${iv.length}`);
//console.log(`IV Base64 Length: ${iv.toString('base64').length}`);
console.log(iv.toString('base64'));
//console.log(`AuthTag Length: ${cipher.getAuthTag().length}`);
//console.log(`AuthTag Base64 Length: ${cipher.getAuthTag().toString('base64').length}`);
console.log(cipher.getAuthTag().toString('base64'));
//console.log(`Encrypted Data Length: ${encryptedData.length}`)
//console.log(`Encrypted Data Base64 Length: ${encryptedData.toString('base64').length}`)
console.log(encryptedData.toString('base64'));
console.log(' --------------- ENC END ---------------');
console.log(Buffer.concat([cipher.getAuthTag(), encryptedData]).toString('base64'));
console.log(Buffer.concat([encryptedData, cipher.getAuthTag()]).toString('base64'));
//let encryptedBuffer = Buffer.concat([iv, cipher.getAuthTag(), encryptedData]);
return iv.toString('base64') + cipher.getAuthTag().toString('base64') + encryptedData.toString('base64');
},
decryptData(data,password){
let password_hash = crypto.createHash('sha256').update(password, 'utf-8').digest('hex').slice(0,32).toLowerCase();
//let combinerBuffer = Buffer.from(data, 'base64');
//let iv = combinerBuffer.slice(0,16);
let iv = Buffer.from(data.slice(0,16), 'base64');
console.log(' --------------- DEC BEGIN ---------------');
console.log(iv.toString('base64'));
let at = Buffer.from(data.slice(16,32), 'base64');
console.log(at.toString('base64'));
let enc_buffer = Buffer.from(data.slice(32), 'base64');
console.log(enc_buffer.toString('base64'));
console.log(' --------------- DEC END ---------------');
let deciper = crypto.createDecipheriv('aes-256-gcm', password_hash, iv);
deciper.setAuthTag(at)
let dec_buf = deciper.update(enc_buffer, 'utf8') + deciper.final('utf8');
return dec_buf.toString('utf8');
}
}
Run Code Online (Sandbox Code Playgroud)
SWIFT 加密的内容无法被 NodeJS 解密。解密数据时,我收到错误:
Unsupported state or unable to authenticate data
我在 Java 中也有类似的代码,它可以很好地处理 SWIFT 生成的加密数据,但 NodeJS 代码根本不起作用。主要问题是如何从 SWIFT 生成的组合加密文本中获取 AAD 和 AuthTag。在 Java 中,我只需要提取 IV 的前 16 个字节,其余部分作为密文,其中还包括身份验证标签。但是,在 NodeJS 中,我需要手动提取 AuthTag 上的通行证。我尝试将 SWIFT 的合并数据分解为:
这两种方法都不起作用并产生与上面相同的错误。
下面是Java代码:
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;
class CryptoTest{
public static void main(String[] args){
try{
String enc = Crypto.encrypt("This is data", "password");
String dec = Crypto.decrypt(enc,"password");
System.out.println(enc);
System.out.println(dec);
}catch(Exception ex){
System.out.println("ERROR");
}
}
private static class Crypto {
private static final int GCM_TAG_LENGTH = 16;
private static final int GCM_IV_LENGTH = 12;
private static final String ALGORITHM = "AES_256/GCM/NoPadding";
private static final String ALGORITHM_SHORT_NAME = "AES";
private static final String HASH_ALGORITHM = "SHA-256";
public static String encrypt(String plaintext, String password) throws Exception
{
//Generate the key from password
MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM);
byte[] key = md.digest(password.getBytes(StandardCharsets.UTF_8));
SecureRandom sr = new SecureRandom(password.getBytes(StandardCharsets.UTF_8));
byte[] iv = new byte[GCM_IV_LENGTH];
// sr.nextBytes(iv);
iv = Base64.getDecoder().decode("mBj0tzBUxDFmix1T");
// Get Cipher Instance
Cipher cipher = Cipher.getInstance(ALGORITHM);
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM_SHORT_NAME);
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
// Initialize Cipher for ENCRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
// Perform Encryption
byte[] cipherText = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
//Return the IV and CipherText as Base64 encoded and appended strings
System.out.println(Base64.getEncoder().encodeToString(iv));
System.out.println(Base64.getEncoder().encodeToString(cipherText));
return Base64.getEncoder().encodeToString(iv)+Base64.getEncoder().encodeToString(cipherText);
}
public static String decrypt(String sourceText, String password) throws Exception
{
//Get the IV from cipherText
byte[] iv = Base64.getDecoder().decode(sourceText.substring(0,16));
//Get the reminder of cipherText after the iv
byte[] cipherText = Base64.getDecoder().decode(sourceText.substring(16));
//Generate the key from password
MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM);
byte[] key = md.digest(password.getBytes(StandardCharsets.UTF_8));
// Get Cipher Instance
Cipher cipher = Cipher.getInstance(ALGORITHM);
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM_SHORT_NAME);
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
// Initialize Cipher for DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
// Perform Decryption
byte[] decryptedText = cipher.doFinal(cipherText);
return new String(decryptedText);
}
}
}
Run Code Online (Sandbox Code Playgroud)
数据:这是数据
关键:密码
IV: mBj0tzBUxDFmix1T
Java 代码生成以下内容:
加密文本:UgNY3VAwNU07iEqU1Jq3m3Q+p6bDCZg6UI0h8w==
NodeJS 代码生成以下内容:
授权标签:BXQ0vZH4HBBpGb7Y7R9iJw==
加密文本:O+wuVJB06JO6rPrc
据我所知,Java 将生成包含 AuthTag 的加密文本。我尝试将 AuthTag 连接到加密文本,但输出永远不会等于 Java 生成的输出。
然而,Java 加密文本可以通过 SWIFT CryptoKit 代码解密,不会出现任何问题。
除了 Java 加密将 GCM authtag 放在密文末尾(如注释中所示)之外,您的 Java 代码使用密码的 SHA256 作为密钥,而您的 Nodejs 使用十六进制表示的 ASCII 字符SHA256的一半;这是一个完全不同的值,对称(传统)加密的要点是您必须在两端使用(完全相同)相同的密钥。另外,转换为base64然后与string-plus连接,并在解码之前相反地切片base64的方法,只有在数据和IV都是3的倍数时才有效:GCM IV/nonce是12,这是可以的,您的示例值“这是数据”也是如此,但大多数真实数据不会。
以下修改后的 js 与您的 Java 相匹配。我不做SWIFT,但如果正如你所说,它与你的Java匹配,它也应该与这个js匹配。
const crypto = require('crypto')
function encryptData(data,password){
//--let password_hash = crypto.createHash('sha256').update(password, 'utf-8').digest('hex').slice(0,32).toLowerCase();
let password_hash = crypto.createHash('sha256').update(password, 'utf-8').digest();
let iv = Buffer.from('mBj0tzBUxDFmix1T', 'base64'); // TEST ONLY SHOULD BE UNIQUE (such as random)
let cipher = crypto.createCipheriv('aes-256-gcm', password_hash, iv);
//--let encryptedData = Buffer.from(cipher.update(data, 'utf8', 'hex') + cipher.final('hex'), 'hex');
let encryptedData = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]);
//--return iv.toString('base64') + cipher.getAuthTag().toString('base64') + encryptedData.toString('base64');
return Buffer.concat([iv,encryptedData,cipher.getAuthTag()]).toString('base64');
// or just concat([iv,cipher.update(data,'utf8'),cipher.final(),cipher.getAuthTag()]).toString('base64')
}
function decryptData(data,password){
let password_hash = crypto.createHash('sha256').update(password, 'utf-8').digest(); //**
let combinerBuffer = Buffer.from(data, 'base64'); //**
let iv = combinerBuffer.slice(0,12); //**
let deciper = crypto.createDecipheriv('aes-256-gcm', password_hash, iv);
let temp = combinerBuffer.length-16;
deciper.setAuthTag(combinerBuffer.slice(temp));
return deciper.update(combinerBuffer.slice(12,temp), 'utf8') + deciper.final('utf8');
}
let p = 'password', i = 'This is data';
let c = encryptData(i,p); console.log(c);
let d = decryptData(c,p); console.log(d);
Run Code Online (Sandbox Code Playgroud)
最后,使用单个快速且无盐的密码哈希作为密钥的安全性非常低,并且可能会被破坏。但这是一个设计问题,对于SO来说是题外话。如果您有能力改变这种设计并关心实际的安全性,请参阅 security.SX,在那里您会发现许多建议,至少使用 PBKDF2(一种迭代的、加盐的 HMAC)之类的东西,甚至更好的更新的内存 -硬密码哈希,例如 scrypt 或 argon2。
另外,正如我所评论的,GCM 的 IV/nonce 只需要唯一;使用安全随机生成器是获取唯一值的一种常见方法,但不是唯一的方法。(这与 CBC 模式形成鲜明对比,在 CBC 模式中,IV 必须是唯一且不可预测的,实际上需要随机或 SIV。)
| 归档时间: |
|
| 查看次数: |
1361 次 |
| 最近记录: |