aardio 文档

对称加密算法

对称加密算法可以分为两大类:

但实际上对称加密的分类更为复杂。分组密码可以通过不同的"模式"进行操作,某些模式下分组密码可以像流密码一样工作(但流密码不能转换为分组密码)。因此我们有以下分类:

分组密码操作固定大小的数据块(通常为 64、 128 或 256 位)。在不转换为流模式的场景下,如果明文长度不是块大小的整数倍,就需要进行填充。而流密码或作为流模式工作的分组密码则不需要填充。

BouncyCastle .NET API 主要由一系列"引擎"和"模式"组成:

基本使用流程

使用 BouncyCastle 进行对称加密的基本步骤:

  1. 选择密码引擎
  2. 选择兼容的加密模式
  3. 生成与所选引擎兼容的(随机)密钥
  4. 如果需要填充,选择与模式和引擎兼容的填充类型

示例5 - 随机对称密钥生成

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);

建议使用此方法生成密码学安全的随机密钥,而不是使用自定义字节作为密钥。

示例6 - 固定对称密钥生成

import BouncyCastle;

// 使用已有密钥生成 KeyParameter
function generateFixedKey(myKey) {
    return BouncyCastle.Crypto.Parameters.KeyParameter(myKey);
}

var myKey = raw.buffer(32); // 32 字节密钥
var keyParam = generateFixedKey(myKey);

示例 7 - ECB 模式对称加密(带填充)

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();

3.1 分组密码操作模式

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长度通常与引擎的块大小相同。IV不是秘密,但需要通信双方都知道。

示例8 - 带 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);

示例9 - CBC 模式加密/解密

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支持以下填充类型:

示例10 - CFB 流模式加密/解密

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();

示例11 - CTR模式 无填充加密/解密

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();

示例 12 - CCM AEAD 模式无填充加密/解密

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中分组密码的关键点:

3.2 流密码算法

下表列出了 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用法。

示例13 - ChaCha流密码加密/解密

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运算,加密和解密方法完全相同。

示例14 - HC128流密码加密/解密

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();

3.3 AEAD密码

AEAD密码由两部分组成:

最常见的AEAD分组密码模式是GCM(伽罗瓦计数器模式),它结合了CTR模式加密和有限域上的多项式运算来生成MAC。

示例15 - GCM AEAD模式加密/解密

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轻量级密码竞赛选定的标准算法。

示例16 - ASCON AEAD加密/解密

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();

示例17 - ChaCha20Poly1305 AEAD加密/解密

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();

3.4 使用 AES 的格式保留加密

在对称加密的最后部分,我们将介绍格式保留加密(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 会抛出异常。

示例 18 - FF3-1 FPE 模式加密/解密

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();

代码说明

  1. 字母表映射器BasicAlphabetMapper 用于在字符和索引之间进行转换,确保加密/解密过程只处理字母表中的字符。

  2. 调整值(tweak):使用 7 字节(56 位)的随机值作为调整参数,这是 NIST 标准推荐的长度。

  3. FPE 参数FpeParameters 封装了密钥、基数和调整值,是加密/解密的核心参数。

  4. 加密引擎:使用 AesEngine 作为底层加密算法,FpeFf3_1Engine 实现 FF3-1 模式的格式保留加密。

  5. 索引转换:加密前需要将字符转换为索引,加密后再转换回字符,确保格式保持不变。

这个实现完整保留了原始 C# 版本的功能,同时适应了 aardio 的语法特性,特别是处理 .NET 对象交互时的类型转换。注意在 aardio 中处理 .NET 数组和缓冲区时确保正确的内存管理和类型匹配。

Markdown 格式