# 对称加密算法

对称加密算法可以分为两大类：

- **分组密码(Block Ciphers)**：每次加密固定大小的明文数据块（称为块）。如果明文长度不是块的整数倍，则需要使用填充(padding)补全最后一个块。
- **流密码(Stream Ciphers)**：直接逐字节加密明文数据，不需要填充。

但实际上对称加密的分类更为复杂。分组密码可以通过不同的"模式"进行操作，某些模式下分组密码可以像流密码一样工作（但流密码不能转换为分组密码）。因此我们有以下分类：

- 工作在特定模式下的分组密码（某些模式允许分组密码作为流密码工作）
- 工作在专用模式下的流密码

分组密码操作固定大小的数据块（通常为 64、 128 或 256 位）。在不转换为流模式的场景下，如果明文长度不是块大小的整数倍，就需要进行填充。而流密码或作为流模式工作的分组密码则不需要填充。

BouncyCastle .NET API 主要由一系列"引擎"和"模式"组成：
- 引擎(Engine)：底层数学置换算法，用于生成密码（技术上来说，一个 k 位密钥的引擎是从 `2^k` 位应用到 n 位字符串的双射）
- 操作模式(Mode)：指定如何应用 n 位分组密码的过程或算法

## 基本使用流程

使用 BouncyCastle 进行对称加密的基本步骤：
1. 选择密码引擎
2. 选择兼容的加密模式
3. 生成与所选引擎兼容的（随机）密钥
4. 如果需要填充，选择与模式和引擎兼容的填充类型

### 示例5 - 随机对称密钥生成

```aardio
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 - 固定对称密钥生成

```aardio
import BouncyCastle;

// 使用已有密钥生成 KeyParameter
function generateFixedKey(myKey) {
    return BouncyCastle.Crypto.Parameters.KeyParameter(myKey);
}

var myKey = raw.buffer(32); // 32 字节密钥
var keyParam = generateFixedKey(myKey);
```

### 示例 7 - ECB 模式对称加密（带填充）

```aardio
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 的密钥生成

```aardio
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 模式加密/解密

```aardio
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支持以下填充类型：
- ZERO - 用零字节填充
- PKCS7(PKCS5) - 每个填充字节的值等于填充字节数
- ISO10126-2 - 最后一个填充字节是填充字节数，其他字节可以是随机的
- X9.23 - 最后一个填充字节是填充字节数，其他填充字节为零
- ISO7816-4(ISO9797-1) - 第一个填充字节是0x80，其他填充字节是0x00
- TBC(尾位补码) - 如果明文以0位结束，所有填充位为1，否则为0

### 示例10 - CFB 流模式加密/解密

```aardio
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模式 无填充加密/解密

```aardio
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 模式无填充加密/解密

```aardio
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中分组密码的关键点：

- 分组密码由加密引擎和模式组成
- 某些模式需要填充，而其他模式可作为流密码使用
- 某些模式需要初始化向量(IV/nonce)，有些模式还支持关联数据用于认证


## 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流密码加密/解密

```aardio
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流密码加密/解密

```aardio
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密码由两部分组成：
- _AE_ (认证加密)：保护消息数据不被篡改或注入
- _AD_ (关联数据)：防止重放攻击

最常见的AEAD分组密码模式是GCM(伽罗瓦计数器模式)，它结合了CTR模式加密和有限域上的多项式运算来生成MAC。

#### 示例15 - GCM AEAD模式加密/解密

```aardio
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加密/解密

```aardio
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加密/解密

```aardio
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 模式加密/解密

```aardio
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 数组和缓冲区时确保正确的内存管理和类型匹配。