对称加密算法可以分为两大类:
但实际上对称加密的分类更为复杂。分组密码可以通过不同的"模式"进行操作,某些模式下分组密码可以像流密码一样工作(但流密码不能转换为分组密码)。因此我们有以下分类:
分组密码操作固定大小的数据块(通常为 64、 128 或 256 位)。在不转换为流模式的场景下,如果明文长度不是块大小的整数倍,就需要进行填充。而流密码或作为流模式工作的分组密码则不需要填充。
BouncyCastle .NET API 主要由一系列"引擎"和"模式"组成:
2^k 位应用到 n 位字符串的双射)使用 BouncyCastle 进行对称加密的基本步骤:
import BouncyCastle;
// 生成256位随机密钥
function generateRandomKey(keySize) {
var keyGen = BouncyCastle.Crypto.CipherKeyGenerator();
keyGen.Init(BouncyCastle.Crypto.KeyGenerationParameters(
BouncyCastle.Security.SecureRandom(),
keySize
));
return keyGen.GenerateKeyParameter();
}
var keyParam = generateRandomKey(256);
建议使用此方法生成密码学安全的随机密钥,而不是使用自定义字节作为密钥。
import BouncyCastle;
// 使用已有密钥生成 KeyParameter
function generateFixedKey(myKey) {
return BouncyCastle.Crypto.Parameters.KeyParameter(myKey);
}
var myKey = raw.buffer(32); // 32 字节密钥
var keyParam = generateFixedKey(myKey);
import BouncyCastle;
import console;
// ECB 模式加密
function ecbEncrypt(keyParam, plainText) {
// 选择AES引擎
var cipher = BouncyCastle.Crypto.Engines.AesEngine();
// 选择ECB模式
var mode = BouncyCastle.Crypto.Modes.EcbBlockCipher(cipher);
// 选择PKCS7填充
var padding = BouncyCastle.Crypto.Paddings.Pkcs7Padding();
// 创建带填充的缓冲分组密码
var ecbCipher = BouncyCastle.Crypto.Paddings.PaddedBufferedBlockCipher(mode, padding);
// 初始化加密器
ecbCipher.Init(true, keyParam);
// 处理数据
var cipherText = dotNet.buffer(ecbCipher.GetOutputSize(#plainText));
var len = ecbCipher.ProcessBytes(plainText, 0, #plainText, cipherText, 0);
ecbCipher.DoFinal(cipherText, len);
return cipherText.Value;
}
// ECB模式解密
function ecbDecrypt(keyParam, cipherText) {
var cipher = BouncyCastle.Crypto.Engines.AesEngine();
var mode = BouncyCastle.Crypto.Modes.EcbBlockCipher(cipher);
var padding = BouncyCastle.Crypto.Paddings.Pkcs7Padding();
var ecbCipher = BouncyCastle.Crypto.Paddings.PaddedBufferedBlockCipher(mode, padding);
ecbCipher.Init(false, keyParam);
var plainText = dotNet.buffer(ecbCipher.GetOutputSize(#cipherText));
var len = ecbCipher.ProcessBytes(cipherText, 0, #cipherText, plainText, 0);
ecbCipher.DoFinal(plainText, len);
return plainText.Value;
}
// 生成256位随机密钥
function generateRandomKey(keySize) {
var keyGen = BouncyCastle.Crypto.CipherKeyGenerator();
keyGen.Init(BouncyCastle.Crypto.KeyGenerationParameters(
BouncyCastle.Security.SecureRandom(),
keySize
));
return keyGen.GenerateKeyParameter();
}
// 测试
var key = generateRandomKey(256);
var data = raw.buffer("测试数据");
var encrypted = ecbEncrypt(key, data);
console.log("加密结果:", string.hex(encrypted));
var decrypted = ecbDecrypt(key, encrypted);
console.log("解密结果:", decrypted); //decrypted 在 aardio 里是 buffer 类型字节数组
console.pause();
BouncyCastle支持多种分组密码操作模式,下表列出了最常见的模式:
| 模式 | 算法描述 |
|---|---|
| CBC | 密码块链接模式,需要初始化向量(IV) |
| CCM | 计数器密码块链接消息认证模式,需要IV,用于AEAD算法 |
| CFB | 密码反馈模式,可作为流密码使用,需要IV |
| CTR | 计数器模式,可作为流密码使用,需要IV和计数器 |
| EAX | 加密-认证-转换模式,用于AEAD算法 |
| ECB | 电子密码本模式 |
| FF1 | 基于Feistel的格式保留加密模式 |
| GCM | 伽罗瓦计数器模式,可作为流密码使用,需要IV |
| OCB | 偏移码本模式,需要IV和计数器 |
| OFB | 输出反馈模式,可作为流密码使用,需要IV |
下表列出了支持的密码引擎及其兼容模式:
| 引擎算法 | 密钥长度 | 兼容模式 |
|---|---|---|
| AES | 128,192,256 | ECB,CBC,CFB8,CFB128,OFB,CTR,CCM,GCM,FF |
| ARIA | 128,192,256 | ECB,CBC,CFB8,CFB128,OFB,CTR,CCM,GCM,FF |
| Blowfish | 128(可变) | ECB,CBC,CCM,CFB8,CFB128,CTR,GCM,OFB |
| Camellia | 128,192,256 | CBC,CCM,CFB8,CFB128,CTR,ECB,GCM,OCB,OFB |
| Cast5(CAST-128) | 128 | ECB,CBC,CCM,CFB8,CFB128,CTR,GCM,OFB |
| Cast6(CAST-256) | 128,160,192,224,256 | ECB,CBC,CCM,CFB8,CFB128,CTR,GCM,OFB |
| GOST | 256 | ECB,CBC,CCM,CFB8,CFB128,CTR,GCM,OFB |
| IDEA | 128 | ECB,CBC,CCM,CFB8,CFB128,CTR,GCM,OFB |
| SEED | 128 | ECB,CBC,CCM,CFB8,CFB128,CTR,GCM,OFB |
| Serpent | 129,192,256 | ECB,CBC,CCM,CFB8,CFB128,CTR,GCM,OFB |
| Skipjack | - | ECB,CBC,CCM,CFB8,CFB128,CTR,GCM,OFB |
| Threefish | 256,512,1024 | ECB,CBC,CCM,CFB8,CFB128,CTR,GCM,OFB |
| TripleDES | 112,168 | OpenPGPCFB,ECB,CBC,CFB8,CFB64,OFB,CTR |
| Twofish | 129,192,256 | ECB,CBC,CCM,CFB8,CFB128,CTR,GCM,OFB |
某些模式需要初始化向量(IV) - 这是一个已知的随机字节串,在加密和解密时输入算法。IV长度通常与引擎的块大小相同。IV不是秘密,但需要通信双方都知道。
import BouncyCastle;
// 生成带IV的密钥参数
function generateKeyWithIV(keySize, IV) {
var keyGen = BouncyCastle.Crypto.CipherKeyGenerator();
keyGen.Init(BouncyCastle.Crypto.KeyGenerationParameters(
BouncyCastle.Security.SecureRandom(),
keySize
));
var keyParam = keyGen.GenerateKeyParameter();
return BouncyCastle.Crypto.Parameters.ParametersWithIV(keyParam, IV);
}
// 生成16字节IV
var iv = dotNet.buffer(16);
BouncyCastle.Security.SecureRandom().NextBytes(iv);
var keyWithIV = generateKeyWithIV(256, iv.Value);
import BouncyCastle;
import console;
// CBC 模式加密
function cbcEncrypt(keyParamWithIV, plainText) {
var cipher = BouncyCastle.Crypto.Engines.AesEngine();
var mode = BouncyCastle.Crypto.Modes.CbcBlockCipher(cipher);
var padding = BouncyCastle.Crypto.Paddings.Pkcs7Padding();
var cbcCipher = BouncyCastle.Crypto.Paddings.PaddedBufferedBlockCipher(mode, padding);
cbcCipher.Init(true, keyParamWithIV);
var cipherText = dotNet.buffer(cbcCipher.GetOutputSize(#plainText));
var len = cbcCipher.ProcessBytes(plainText, 0, #plainText, cipherText, 0);
cbcCipher.DoFinal(cipherText, len);
return cipherText.Value;
}
// CBC 模式解密
function cbcDecrypt(keyParamWithIV, cipherText) {
var cipher = BouncyCastle.Crypto.Engines.AesEngine();
var mode = BouncyCastle.Crypto.Modes.CbcBlockCipher(cipher);
var padding = BouncyCastle.Crypto.Paddings.Pkcs7Padding();
var cbcCipher = BouncyCastle.Crypto.Paddings.PaddedBufferedBlockCipher(mode, padding);
cbcCipher.Init(false, keyParamWithIV);
var plainText = dotNet.buffer(cbcCipher.GetOutputSize(#cipherText));
var len = cbcCipher.ProcessBytes(cipherText, 0, #cipherText, plainText, 0);
cbcCipher.DoFinal(plainText, len);
return plainText.Value;
}
// 测试
var iv = dotNet.buffer(16);
BouncyCastle.Security.SecureRandom().NextBytes(iv);
// 生成带IV的密钥参数
function generateKeyWithIV(keySize, IV) {
var keyGen = BouncyCastle.Crypto.CipherKeyGenerator();
keyGen.Init(BouncyCastle.Crypto.KeyGenerationParameters(
BouncyCastle.Security.SecureRandom(),
keySize
));
var keyParam = keyGen.GenerateKeyParameter();
return BouncyCastle.Crypto.Parameters.ParametersWithIV(keyParam, IV);
}
var key = generateKeyWithIV(256, iv.Value);
var data = raw.buffer("测试数据");
var encryptedBuffer = cbcEncrypt(key, data);
console.log("加密结果:", string.hex(encryptedBuffer));
var decryptedBuffer = cbcDecrypt(key, encryptedBuffer);
console.log("解密结果:", decryptedBuffer);
console.pause();
BouncyCastle支持以下填充类型:
import BouncyCastle;
import console;
// CFB 流模式加密
function cfbStreamEncrypt(keyParamWithIV, plainText) {
var cipher = BouncyCastle.Crypto.Engines.IdeaEngine();
var mode = BouncyCastle.Crypto.Modes.CfbBlockCipher(cipher, 8); // 8 位块大小
var streamCipher = BouncyCastle.Crypto.StreamBlockCipher(mode);
streamCipher.Init(true, keyParamWithIV);
var cipherText = raw.buffer(#plainText);
for(i=1; #plainText; 1) {
cipherText[i] = streamCipher.ReturnByte(plainText[i]);
}
return cipherText;
}
// CFB 流模式解密
function cfbStreamDecrypt(keyParamWithIV, cipherText) {
var cipher = BouncyCastle.Crypto.Engines.IdeaEngine();
var mode = BouncyCastle.Crypto.Modes.CfbBlockCipher(cipher, 8);
var streamCipher = BouncyCastle.Crypto.StreamBlockCipher(mode);
streamCipher.Init(false, keyParamWithIV);
var plainText = raw.buffer(#cipherText);
for(i=1; #cipherText; 1) {
plainText[i] = streamCipher.ReturnByte(cipherText[i]);
}
return plainText;
}
// 测试
var iv = dotNet.buffer(8);
BouncyCastle.Security.SecureRandom().NextBytes(iv);
// 生成带 IV 的密钥参数
function generateKeyWithIV(keySize, IV) {
var keyGen = BouncyCastle.Crypto.CipherKeyGenerator();
keyGen.Init(BouncyCastle.Crypto.KeyGenerationParameters(
BouncyCastle.Security.SecureRandom(),
keySize
));
var keyParam = keyGen.GenerateKeyParameter();
return BouncyCastle.Crypto.Parameters.ParametersWithIV(keyParam, IV);
}
var key = generateKeyWithIV(128, iv.Value);
var data = raw.buffer("流密码测试数据");
var encryptedBuffer = cfbStreamEncrypt(key, data);
console.log("加密结果:", string.hex(encryptedBuffer));
var decryptedBuffer = cfbStreamDecrypt(key, encryptedBuffer);
console.log("解密结果:", decryptedBuffer);
console.pause();
import BouncyCastle;
import console;
// CTR 模式加密(无填充)
function ctrEncrypt(keyParamWithIV, plainText) {
var cipher = BouncyCastle.Crypto.Engines.ThreefishEngine(256);
var mode = BouncyCastle.Crypto.Modes.KCtrBlockCipher(cipher);
var bufferedCipher = BouncyCastle.Crypto.BufferedBlockCipher(mode);
bufferedCipher.Init(true, keyParamWithIV);
var cipherText = dotNet.buffer(bufferedCipher.GetOutputSize(#plainText));
var len = bufferedCipher.ProcessBytes(plainText, 0, #plainText, cipherText, 0);
bufferedCipher.DoFinal(cipherText, len);
return cipherText.Value;
}
// CTR模式解密(无填充)
function ctrDecrypt(keyParamWithIV, cipherText) {
var cipher = BouncyCastle.Crypto.Engines.ThreefishEngine(256);
var mode = BouncyCastle.Crypto.Modes.KCtrBlockCipher(cipher);
var bufferedCipher = BouncyCastle.Crypto.BufferedBlockCipher(mode);
bufferedCipher.Init(false, keyParamWithIV);
var plainText = dotNet.buffer(bufferedCipher.GetOutputSize(#cipherText));
var len = bufferedCipher.ProcessBytes(cipherText, 0, #cipherText, plainText, 0);
bufferedCipher.DoFinal(plainText, len);
return plainText.Value;
}
// 测试
var iv = dotNet.buffer(28); // 28字节IV + 4字节计数器
BouncyCastle.Security.SecureRandom().NextBytes(iv);
var key = generateKeyWithIV(256, iv.Value);
var data = raw.buffer("CTR模式测试数据");
var encrypted = ctrEncrypt(key, data);
console.log("加密结果:", string.hex(encrypted));
var decrypted = ctrDecrypt(key, encrypted);
console.log("解密结果:", decrypted);
console.pause();
import console;
import BouncyCastle;
// CCM AEAD模式加密
function ccmEncrypt(keyParam, plainText) {
var cipher = BouncyCastle.Crypto.Engines.AesEngine();
var macSize = 8 * cipher.GetBlockSize();
var nonce = dotNet.buffer(12);
var associatedText = raw.buffer("附加认证数据");
var aeadParams = BouncyCastle.Crypto.Parameters.AeadParameters(
keyParam, macSize, nonce.Value, associatedText
);
var ccmCipher = BouncyCastle.Crypto.Modes.CcmBlockCipher(cipher);
ccmCipher.Init(true, aeadParams);
var cipherText = dotNet.buffer(ccmCipher.GetOutputSize(#plainText));
ccmCipher.ProcessBytes(plainText, 0, #plainText, cipherText, 0);
ccmCipher.DoFinal(cipherText, 0);
return cipherText.Value;
}
// CCM AEAD模式解密
function ccmDecrypt(keyParam, cipherText) {
var cipher = BouncyCastle.Crypto.Engines.AesEngine();
var macSize = 8 * cipher.GetBlockSize();
var nonce = dotNet.buffer(12);
var associatedText = raw.buffer("附加认证数据");
var aeadParams = BouncyCastle.Crypto.Parameters.AeadParameters(
keyParam, macSize, nonce.Value, associatedText
);
var ccmCipher = BouncyCastle.Crypto.Modes.CcmBlockCipher(cipher);
ccmCipher.Init(false, aeadParams);
var plainText = dotNet.buffer(ccmCipher.GetOutputSize(#cipherText));
ccmCipher.ProcessBytes(cipherText, 0, #cipherText, plainText, 0);
ccmCipher.DoFinal(plainText, 0);
return plainText.Value;
}
//调用例子:
var pwd = raw.buffer("1234567812345678");//必须是符合密钥要求的固定长度
var keyParam = BouncyCastle.Crypto.Parameters.KeyParameter(pwd);
var plainText = raw.buffer("待加密数据");
// 加密示例
var cipherText = ccmEncrypt(keyParam, plainText);
console.log("加密结果:", cipherText);
// 解密示例
var decryptedText = ccmDecrypt(keyParam, cipherText);
console.log("解密结果:", decryptedText);
console.pause();
在进入专用流密码算法前,我们先总结一下 BouncyCastle .NET API中分组密码的关键点:
下表列出了 BouncyCastle .NET API中最常见的流密码"引擎":
| 算法引擎 | 密钥长度 | 说明 |
|---|---|---|
| ChaCha | 128, 256 | eSTREAM认证算法。基于Salsa20,需要64位IV |
| HC128 | 128 | eSTREAM认证算法。需要128位IV |
| HC256 | 256 | 需要256位IV |
| Salsa20 | 128, 256 | eSTREAM认证算法。需要64位IV |
| XSalsa20 | 256 | 需要192位IV |
使用上表中的流密码时不需要考虑模式问题。下面的示例演示了这些密码的API用法。
import BouncyCastle;
import console;
// ChaCha流加密函数
function chachaStreamEncrypt(keyParamWithIV, plainTextData) {
var cipher = BouncyCastle.Crypto.Engines.ChaChaEngine();
cipher.Init(true, keyParamWithIV);
// 模拟流处理
var cipherTextData = raw.buffer(#plainTextData);
for(var j=1; j<=#plainTextData; j++) {
cipherTextData[j] = cipher.ReturnByte(plainTextData[j]);
}
return cipherTextData;
}
// ChaCha流解密函数(与加密相同)
function chachaStreamDecrypt(keyParamWithIV, cipherTextData) {
var cipher = BouncyCastle.Crypto.Engines.ChaChaEngine();
cipher.Init(false, keyParamWithIV);
// 模拟流处理
var plainTextData = raw.buffer(#cipherTextData);
for(var j=1; j<=#cipherTextData; j++) {
plainTextData[j] = cipher.ReturnByte(cipherTextData[j]);
}
return plainTextData;
}
// 测试代码
var keyParam = BouncyCastle.Crypto.Parameters.KeyParameter(dotNet.buffer(32).Value);
var iv = dotNet.buffer(8); // ChaCha需要8字节IV
BouncyCastle.Security.SecureRandom().NextBytes(iv);
var keyParamWithIV = BouncyCastle.Crypto.Parameters.ParametersWithIV(keyParam, iv.Value);
var plainText = raw.buffer("需要加密的流数据");
// 加密
var cipherText = chachaStreamEncrypt(keyParamWithIV, plainText);
console.log("加密结果:", string.hex(cipherText));
// 解密
var decrypted = chachaStreamDecrypt(keyParamWithIV, cipherText);
console.log("解密结果:", decrypted);
console.pause();
注意:由于流密码本质上是使用XOR运算,加密和解密方法完全相同。
import BouncyCastle;
import console;
// HC128流加密函数
function h128StreamEncrypt(keyParamWithIV, plainTextData) {
var cipher = BouncyCastle.Crypto.Engines.HC128Engine();
cipher.Init(true, keyParamWithIV);
// 模拟流处理
var cipherTextData = raw.buffer(#plainTextData);
for(var j=1; j<=#plainTextData; j++) {
cipherTextData[j] = cipher.ReturnByte(plainTextData[j]);
}
return cipherTextData;
}
// HC128流解密函数
function h128StreamDecrypt(keyParamWithIV, cipherTextData) {
var cipher = BouncyCastle.Crypto.Engines.HC128Engine();
cipher.Init(false, keyParamWithIV);
// 模拟流处理
var plainTextData = raw.buffer(#cipherTextData);
for(var j=1; j<=#cipherTextData; j++) {
plainTextData[j] = cipher.ReturnByte(cipherTextData[j]);
}
return plainTextData;
}
// 测试代码
var keyParam = BouncyCastle.Crypto.Parameters.KeyParameter(dotNet.buffer(16).Value); // HC128使用128位密钥
var iv = dotNet.buffer(16); // HC128需要16字节IV
BouncyCastle.Security.SecureRandom().NextBytes(iv);
var keyParamWithIV = BouncyCastle.Crypto.Parameters.ParametersWithIV(keyParam, iv.Value);
var plainText = raw.buffer("需要加密的流数据");
// 加密
var cipherText = h128StreamEncrypt(keyParamWithIV, plainText);
console.log("加密结果:", string.hex(cipherText));
// 解密
var decrypted = h128StreamDecrypt(keyParamWithIV, cipherText);
console.log("解密结果:", decrypted);
console.pause();
AEAD密码由两部分组成:
最常见的AEAD分组密码模式是GCM(伽罗瓦计数器模式),它结合了CTR模式加密和有限域上的多项式运算来生成MAC。
import BouncyCastle;
import console;
// GCM AEAD加密函数,返回密文和nonce
function gcmAEADEncrypt(keyParam, plainTextData) {
var cipher = BouncyCastle.Crypto.Engines.AesEngine();
var macSize = 8 * cipher.GetBlockSize(); // MAC大小(位)
var nonce = dotNet.buffer(12); // 推荐12字节nonce
BouncyCastle.Security.SecureRandom().NextBytes(nonce);
var associatedText = raw.buffer("附加认证数据");
var keyParamAead = BouncyCastle.Crypto.Parameters.AeadParameters(
keyParam, macSize, nonce.Value, associatedText
);
var cipherMode = BouncyCastle.Crypto.Modes.GcmBlockCipher(cipher);
cipherMode.Init(true, keyParamAead);
var cipherTextData = dotNet.buffer(cipherMode.GetOutputSize(#plainTextData));
var result = cipherMode.ProcessBytes(plainTextData, 0, #plainTextData, cipherTextData, 0);
cipherMode.DoFinal(cipherTextData, result);
// 返回密文和nonce
return cipherTextData, nonce;
}
// GCM AEAD解密函数,使用加密时生成的nonce
function gcmAEADDecrypt(keyParam, cipherTextData, nonce) {
var cipher = BouncyCastle.Crypto.Engines.AesEngine();
var macSize = 8 * cipher.GetBlockSize();
var associatedText = raw.buffer("附加认证数据");
var keyParamAead = BouncyCastle.Crypto.Parameters.AeadParameters(
keyParam, macSize, nonce.Value, associatedText
);
var cipherMode = BouncyCastle.Crypto.Modes.GcmBlockCipher(cipher);
cipherMode.Init(false, keyParamAead);
var plainTextData = dotNet.buffer(cipherMode.GetOutputSize(#cipherTextData.Value));
var result = cipherMode.ProcessBytes(cipherTextData.Value, 0, #cipherTextData.Value, plainTextData, 0);
cipherMode.DoFinal(plainTextData, result);
return plainTextData;
}
// 测试代码
var keyParam = BouncyCastle.Crypto.Parameters.KeyParameter(dotNet.buffer(32).Value);
var plainText = raw.buffer("需要加密的敏感数据");
// 加密
var cipherText, nonce = gcmAEADEncrypt(keyParam, plainText);
console.log("加密结果:", string.hex(cipherText.Value, ""));
// 解密
var decrypted = gcmAEADDecrypt(keyParam, cipherText, nonce);
console.log("解密结果:", decrypted.Value);
console.pause();
BouncyCastle .NET API还提供了专用的AEAD密码:ASCON和ChaCha20Poly1305。ASCON是NIST轻量级密码竞赛选定的标准算法。
import BouncyCastle;
import console;
// ASCON AEAD加密函数
function asconAEADEncrypt(keyParam, plainTextData) {
var cipher = BouncyCastle.Crypto.Engines.AsconEngine(
BouncyCastle.Crypto.Engines.AsconEngine.AsconParameters.ascon128a
);
var macSize = 8 * cipher.GetKeyBytesSize();
var nonce = dotNet.buffer(cipher.GetIVBytesSize());
BouncyCastle.Security.SecureRandom().NextBytes(nonce);
var associatedText = raw.buffer("附加认证数据");
var keyParamAead = BouncyCastle.Crypto.Parameters.AeadParameters(
keyParam, macSize, nonce.Value, associatedText
);
cipher.Init(true, keyParamAead);
var cipherTextData = dotNet.buffer(cipher.GetOutputSize(#plainTextData));
var result = cipher.ProcessBytes(plainTextData, 0, #plainTextData, cipherTextData, 0);
cipher.DoFinal(cipherTextData, result);
return { cipherText = cipherTextData, nonce = nonce };
}
// ASCON AEAD解密函数
function asconAEADDecrypt(keyParam, cipherTextData, nonce) {
var cipher = BouncyCastle.Crypto.Engines.AsconEngine(
BouncyCastle.Crypto.Engines.AsconEngine.AsconParameters.ascon128a
);
var macSize = 8 * cipher.GetKeyBytesSize();
var associatedText = raw.buffer("附加认证数据");
var keyParamAead = BouncyCastle.Crypto.Parameters.AeadParameters(
keyParam, macSize, nonce.Value, associatedText
);
cipher.Init(false, keyParamAead);
var plainTextData = dotNet.buffer(cipher.GetOutputSize(#cipherTextData.Value));
var result = cipher.ProcessBytes(cipherTextData.Value, 0, #cipherTextData.Value, plainTextData, 0);
cipher.DoFinal(plainTextData, result);
return plainTextData;
}
// 测试代码
var keyParam = BouncyCastle.Crypto.Parameters.KeyParameter(dotNet.buffer(16).Value); // ASCON使用128位密钥
var plainText = raw.buffer("需要加密的敏感数据");
// 加密
var encrypted = asconAEADEncrypt(keyParam, plainText);
console.log("加密结果:", string.hex(encrypted.cipherText.Value));
// 解密
var decrypted = asconAEADDecrypt(keyParam, encrypted.cipherText, encrypted.nonce);
console.log("解密结果:", decrypted.Value);
console.pause();
import BouncyCastle;
import console;
// ChaCha20Poly1305 AEAD加密函数
function chacha20poly1305AEADEncrypt(keyParam, plainTextData) {
var cipher = BouncyCastle.Crypto.Modes.ChaCha20Poly1305();
var macSize = 128; // 固定128位MAC
var nonce = dotNet.buffer(12); // 固定12字节nonce
BouncyCastle.Security.SecureRandom().NextBytes(nonce);
var associatedText = raw.buffer("附加认证数据");
var keyParamAead = BouncyCastle.Crypto.Parameters.AeadParameters(
keyParam, macSize, nonce.Value, associatedText
);
cipher.Init(true, keyParamAead);
var cipherTextData = dotNet.buffer(cipher.GetOutputSize(#plainTextData));
var result = cipher.ProcessBytes(plainTextData, 0, #plainTextData, cipherTextData, 0);
cipher.DoFinal(cipherTextData, result);
return { cipherText = cipherTextData, nonce = nonce, aad = associatedText };
}
// ChaCha20Poly1305 AEAD解密函数
function chacha20poly1305AEADDecrypt(keyParam, cipherTextData, nonce, aad) {
var cipher = BouncyCastle.Crypto.Modes.ChaCha20Poly1305();
var macSize = 128;
var keyParamAead = BouncyCastle.Crypto.Parameters.AeadParameters(
keyParam, macSize, nonce.Value, aad
);
cipher.Init(false, keyParamAead);
var plainTextData = dotNet.buffer(cipher.GetOutputSize(#cipherTextData.Value));
var result = cipher.ProcessBytes(cipherTextData.Value, 0, #cipherTextData.Value, plainTextData, 0);
cipher.DoFinal(plainTextData, result);
return plainTextData;
}
// 测试代码
var keyParam = BouncyCastle.Crypto.Parameters.KeyParameter(dotNet.buffer(32).Value); // 需要256位密钥
var plainText = raw.buffer("需要加密的敏感数据");
// 加密
var encryptedData = chacha20poly1305AEADEncrypt(keyParam, plainText);
console.log("加密结果:", string.hex(encryptedData.cipherText.Value, ""));
// 解密
var decrypted = chacha20poly1305AEADDecrypt(keyParam, encryptedData.cipherText, encryptedData.nonce, encryptedData.aad);
console.log("解密结果:", decrypted.Value);
console.pause();
在对称加密的最后部分,我们将介绍格式保留加密(FPE)算法系列。FPE 是一种尝试保持明文格式的加密方法,主要用于加密信用卡号和其他与个人相关的可识别数据。FPE 有三种不同的操作模式:FF1、FF2 和 FF3。需要注意的是,FF2 未被 NIST 批准为 FPE 的有效模式,因此不在 BouncyCastle .NET API 中。此外,FF3 被发现存在安全级别上的缺陷,因此已被 FF3-1 取代。BouncyCastle API 中实现了 FF1 和 FF3-1(FF3_1)两种模式。
FPE 基于输入字母表工作,并生成相同字母表中的加密字符串。从算法角度看,字母表只是一组从零开始的索引,索引范围与字母表中的字符数量相同。字母表的大小称为基数(radix)。算法还需要一个调整值(tweak)来提高安全性,这个值不需要保密。在下面的示例中,我们使用了 56 位的调整值——这是当前 NIST SP800-38r1 文档推荐的固定长度,也是唯一支持的调整值长度(同一 NIST 文档的附录 C 对调整值的使用有很好的总结)。
如前所述,FPE 加密引擎需要一个字母表。在我们的实现中,字母表就是一个字符数组。显然,要加密的明文必须由字母表中的字符组成。请注意,如果明文中包含字母表之外的字符,BouncyCastle .NET API 会抛出异常。
import BouncyCastle;
import console;
// FF3-1 FPE 加密函数
function ff3_1FPEEncrypt(tweak, keyParam, alphabet, plainTextData) {
// 创建字母表映射器
var alphabetMapper = Org.BouncyCastle.Crypto.Utilities.BasicAlphabetMapper(alphabet);
var fpeKeyParam = BouncyCastle.Crypto.Parameters.FpeParameters(
keyParam,
alphabetMapper.Radix,
tweak
);
// 创建加密引擎
var cipher = BouncyCastle.Crypto.Engines.AesEngine();
var cipherMode = Org.BouncyCastle.Crypto.Fpe.FpeFf3_1Engine(cipher);
cipherMode.Init(true, fpeKeyParam);
// 转换明文为索引
var convertedPlainTextData = alphabetMapper.ConvertToIndexes(plainTextData);
var cipherTextData = dotNet.buffer(#convertedPlainTextData);
// 执行加密
cipherMode.ProcessBlock(
convertedPlainTextData,
0,
#convertedPlainTextData,
cipherTextData,
0
);
// 转换加密结果为字符
return alphabetMapper.ConvertToChars(cipherTextData.Value);
}
// FF3-1 FPE 解密函数
function ff3_1FPEDecrypt(tweak, keyParam, alphabet, cipherTextData) {
// 创建字母表映射器
var alphabetMapper = Org.BouncyCastle.Crypto.Utilities.BasicAlphabetMapper(alphabet);
var fpeKeyParam = BouncyCastle.Crypto.Parameters.FpeParameters(
keyParam,
alphabetMapper.Radix,
tweak
);
// 创建解密引擎
var cipher = BouncyCastle.Crypto.Engines.AesEngine();
var cipherMode = Org.BouncyCastle.Crypto.Fpe.FpeFf3_1Engine(cipher);
cipherMode.Init(false, fpeKeyParam);
// 转换密文为索引
var convertedCipherTextData = alphabetMapper.ConvertToIndexes(cipherTextData);
var plainTextData = dotNet.buffer(#convertedCipherTextData);
// 执行解密
cipherMode.ProcessBlock(
convertedCipherTextData,
0,
#convertedCipherTextData,
plainTextData,
0
);
// 转换解密结果为字符
return alphabetMapper.ConvertToChars(plainTextData.Value);
}
// 示例用法
function testFPE() {
// 准备密钥
var keyParam = BouncyCastle.Crypto.Parameters.KeyParameter(dotNet.buffer(32).Value);
// 定义数字字母表
var alphabet = "0123456789";
// 测试数据,将字符串转换为 .NET System.Char[] 数组
var plainText = dotNet.char("1234567890");
// 创建 FPE 参数对象
var tweak = dotNet.buffer(7); // 56位调整值
BouncyCastle.Security.SecureRandom().NextBytes(tweak);
// 加密
var cipherText = ff3_1FPEEncrypt(tweak,keyParam, alphabet, plainText);
console.log("加密结果:",cipherText);// console.log 可查看纯数组的值
// 解密
var decryptedText = ff3_1FPEDecrypt(tweak,keyParam, alphabet, cipherText);
console.log("解密结果:", decryptedText);
console.log(string.fromCharCode( table.unpack(decryptedText) ))
}
testFPE();
console.pause();
字母表映射器:BasicAlphabetMapper 用于在字符和索引之间进行转换,确保加密/解密过程只处理字母表中的字符。
调整值(tweak):使用 7 字节(56 位)的随机值作为调整参数,这是 NIST 标准推荐的长度。
FPE 参数:FpeParameters 封装了密钥、基数和调整值,是加密/解密的核心参数。
加密引擎:使用 AesEngine 作为底层加密算法,FpeFf3_1Engine 实现 FF3-1 模式的格式保留加密。
索引转换:加密前需要将字符转换为索引,加密后再转换回字符,确保格式保持不变。
这个实现完整保留了原始 C# 版本的功能,同时适应了 aardio 的语法特性,特别是处理 .NET 对象交互时的类型转换。注意在 aardio 中处理 .NET 数组和缓冲区时确保正确的内存管理和类型匹配。