# 密钥协商与交换算法

## 4.1 非对称加密的现状

非对称加密（公钥加密）由密钥对（K_priv, K_pub）组成，其中 K_priv 表示密钥对的私密部分，K_pub 表示公开可用的部分。需要注意的是，使用哪部分密钥（K_priv 或 K_pub）来加密明文消息并不是预先确定的。这只是非对称加密用于大数据块加密时面临的问题之一。

假设 K_enc 是用于加密的密钥（可能是 K_priv 或 K_pub），K_dec 是解密密钥。简单来说，给定明文 M 和加密/解密函数 E/D，我们可以得到密文 C = E(K_enc, M) 和 M = D(K_dec, C)。这里我们注意到另一个问题：虽然 D(K_dec, E(K_enc, M)) = M，但通常我们并不满足 E(K_enc, D(K_dec, M)) = M（RSA 非对称加密满足此性质，但基于椭圆曲线的加密不一定满足）。

其他问题包括：
- 如果使用 K_pub 加密，可以提供隐私性但无法保证真实性
- 如果使用 K_priv 加密，可以保证真实性但无法提供隐私性（这是数字签名的基础）

使用非对称加密大数据时最大的问题是：
1. 底层数学原理要求明文数据必须映射到密钥大小。例如椭圆曲线本身没有加密方案，需要额外添加双射函数。
2. 非对称加密比对称加密慢得多。例如 RSA 加密（理论上可用于 ECB 模式）比相同模式下的 AES 慢数千倍。

这些限制可以通过混合加密方案克服。混合加密通常使用非对称密钥对生成和交换对称密钥，然后使用对称密钥在非安全通道上高效加密数据。这类对称密钥通常是短期的（临时密钥）。

以下是后续示例中会用到的术语：

- **密钥交换**：允许两方或多方商定密钥的方法、过程或协议，使这些方能够发送和接收加密安全数据。
- **密钥协商**：选择的密钥交换方法来共享生成的密钥。例如最常见的 Diffie-Hellman 密钥协商。
- **密钥封装机制(KEM)**：使用非对称加密的特定密钥协商，简单来说就是用公钥加密对称密钥，用私钥解密对称密钥用于数据交换。
- **密钥派生函数(KDF)**：将不一定加密随机的字符串输入转换为加密强度高的密钥的函数或算法。
- **密钥加密密钥(KEK)**：使用 KDF 生成的对称密钥。
- **伪随机函数(PRF)**：根据 NIST 定义，PRF 是可以从随机种子和数据变量生成输出的函数，其输出在计算上与真正的随机输出无法区分。实际上 KDF 和 PRF 密切相关，KDF 会使用 PRF。

## 4.2 Diffie-Hellman 密钥协商

Diffie-Hellman(DH)密钥协商算法需要生成域参数集 (p, q, g)，其中 p 是足够大的素数，q 是 p-1 的大素数因子，g 是有限域乘法群中阶为 q 的循环子群的生成元。这个集合是公开信息。

生成 DH 域参数非常耗时（取决于硬件和 p 或 q 的大小，可能需要几分钟到几小时）。密码学安全对 p 值有各种限制，这类密码学素数称为强素数。其中安全素数（形式为 p=2q+1，q 也是素数）是强素数的子集，这类素数称为 Sophie Germain 素数。RFC7919 和 RFC3526 提供了预定义的安全素数列表。

### 示例19 - 生成 DH 有限域参数（aardio 实现）

```aardio
import BouncyCastle;
import console;

// 使用预定义的 DH 参数组（RFC7919 3072位）
var dhParams = BouncyCastle.Crypto.Agreement.DHStandardGroups.rfc7919_ffdhe3072;

console.dump(dhParams);
console.pause();
```

### 示例20 - Diffie-Hellman 密钥对生成（aardio 实现）

```aardio
import BouncyCastle;
import console;

// 使用预定义的 DH 参数
var dhParams = BouncyCastle.Crypto.Agreement.DHStandardGroups.rfc7919_ffdhe3072;

// 创建密钥生成参数
var dhKeyGenParams = BouncyCastle.Crypto.Parameters.DHKeyGenerationParameters(
    BouncyCastle.Security.SecureRandom(),
    dhParams
);

// 创建密钥对生成器
var dhKeyPairGen = BouncyCastle.Crypto.Generators.DHKeyPairGenerator();
dhKeyPairGen.Init(dhKeyGenParams);

// 生成密钥对
var dhKeyPair = dhKeyPairGen.GenerateKeyPair();

console.dump(dhKeyPair);
console.pause();
```

最简单的 DH 密钥交换是双方交换公钥。A 方生成密钥对 (a, g^a mod p)，B 方生成 (b, g^b mod p)。基本密钥交换就是：

A 方计算：(g^b)^a mod p = g^(ab) mod p  
B 方计算：(g^a)^b mod p = g^(ab) mod p

### 示例21 - 基本 DH 密钥协商（aardio 实现）

```aardio
import BouncyCastle;
import console;

// 使用预定义的 DH 参数
var dhParams = BouncyCastle.Crypto.Agreement.DHStandardGroups.rfc7919_ffdhe3072;

// 生成双方密钥对
var generator = BouncyCastle.Crypto.Generators.DHKeyPairGenerator();
var params = BouncyCastle.Crypto.Parameters.DHKeyGenerationParameters(
    BouncyCastle.Security.SecureRandom(),
    dhParams
);
generator.Init(params);

var keyPairA = generator.GenerateKeyPair();
var keyPairB = generator.GenerateKeyPair();

// 进行密钥协商
var agreementA = BouncyCastle.Crypto.Agreement.DHBasicAgreement();
agreementA.Init(keyPairA.Private);
var secretA = agreementA.CalculateAgreement(keyPairB.Public);

var agreementB = BouncyCastle.Crypto.Agreement.DHBasicAgreement();
agreementB.Init(keyPairB.Private);
var secretB = agreementB.CalculateAgreement(keyPairA.Public);

console.assert(secretA.Equals(secretB), "协商的密钥应该相同");
console.log("协商的共享密钥:", secretA.ToString(16));
console.pause();
```

更安全的 DH 密钥协商方法是 Matsumoto-Takashima-Imai MTI/A0 密钥协商方案（ BouncyCastle .NET API 仅提供 MTI/A0 变体）。这种方案本质上是基本密钥协商的两阶段版本，主要安全性在于第一次公钥交换应该以某种方式进行认证。

### 示例22 - MTI/A0 DH 密钥协商（aardio 实现）

```aardio
import BouncyCastle;
import console;

// 使用预定义的 DH 参数
var dhParams = BouncyCastle.Crypto.Agreement.DHStandardGroups.rfc7919_ffdhe3072;

// 生成静态密钥对
var generator = BouncyCastle.Crypto.Generators.DHKeyPairGenerator();
var params = BouncyCastle.Crypto.Parameters.DHKeyGenerationParameters(
    BouncyCastle.Security.SecureRandom(),
    dhParams
);
generator.Init(params);

var staticKeyPairA = generator.GenerateKeyPair();
var staticKeyPairB = generator.GenerateKeyPair();

// A 方生成临时密钥对发送给 B 方
var agreementA = BouncyCastle.Crypto.Agreement.DHAgreement();
agreementA.Init(staticKeyPairA.Private);
var ephemeralPublicKeyA = agreementA.CalculateMessage();

// B 方生成临时密钥对发送给 A 方
var agreementB = BouncyCastle.Crypto.Agreement.DHAgreement();
agreementB.Init(staticKeyPairB.Private);
var ephemeralPublicKeyB = agreementB.CalculateMessage();

// 计算共享密钥
var secretA = agreementA.CalculateAgreement(staticKeyPairB.Public, ephemeralPublicKeyB);
var secretB = agreementB.CalculateAgreement(staticKeyPairA.Public, ephemeralPublicKeyA);

console.assert(secretA.Equals(secretB), "协商的密钥应该相同");
console.log("A 方计算的共享密钥:", secretA.ToString(16));
console.log("B 方计算的共享密钥:", secretB.ToString(16));
console.pause();
```

## 4.3 椭圆曲线 Diffie-Hellman 密钥协商

Diffie-Hellman 协议也可用于椭圆曲线。定义在有限域上的椭圆曲线点形成阿贝尔群。如果这个群的阶是素数，那么它是循环群，所有满足曲线方程的点都在群中（此时协因子 h=1）。

椭圆曲线密码学主要关注两种类型：
1. 素数域上的椭圆曲线（F_p 或 GF(p)，p 是素数）
2. 二进制有限域上的椭圆曲线（F_{2^m}）

### 示例23 - 直接生成 EC GF(p) 参数（aardio 实现）

```aardio
import BouncyCastle;
import console;

// 获取预定义的椭圆曲线参数
var ecParams = BouncyCastle.Asn1.X9.ECNamedCurveTable.GetByName("brainpoolp160r1");

// 创建域参数
var domainParams = BouncyCastle.Crypto.Parameters.ECDomainParameters(
    ecParams.Curve,
    ecParams.G,
    ecParams.N,
    ecParams.H,
    ecParams.GetSeed()
);

console.dump(domainParams);
console.pause();
```

### 示例24 - 使用内置 EC GF(p) 参数（aardio 实现）

```aardio
import BouncyCastle;
import console;

// 更简单的方式获取内置曲线参数
var ecParams = BouncyCastle.Asn1.X9.ECNamedCurveTable.GetByName("brainpoolp160r1");
var domainParams = BouncyCastle.Crypto.Parameters.ECDomainParameters(
    ecParams.Curve,
    ecParams.G,
    ecParams.N,
    ecParams.H,
    ecParams.GetSeed()
);

console.dump(domainParams);
console.pause();
```

### 示例25 - 直接生成二进制 EC 参数（aardio 实现）

```aardio
import BouncyCastle;
import console;

// 获取预定义的二进制椭圆曲线参数
var ecParams = BouncyCastle.Asn1.X9.ECNamedCurveTable.GetByName("K-283");

// 创建域参数
var domainParams = BouncyCastle.Crypto.Parameters.ECDomainParameters(
    ecParams.Curve,
    ecParams.G,
    ecParams.N,
    ecParams.H,
    ecParams.GetSeed()
);

console.dump(domainParams);
console.pause();
```

### 示例26 - 使用内置二进制 EC 参数（aardio 实现）

```aardio
import BouncyCastle;
import console;

// 更简单的方式获取内置二进制曲线参数
var ecParams = BouncyCastle.Asn1.X9.ECNamedCurveTable.GetByName("K-283");
var domainParams = BouncyCastle.Crypto.Parameters.ECDomainParameters(
    ecParams.Curve,
    ecParams.G,
    ecParams.N,
    ecParams.H,
    ecParams.GetSeed()
);

console.dump(domainParams);
console.pause();
```

### 示例27 - EC Diffie-Hellman 密钥对生成（aardio 实现）

```aardio
import BouncyCastle;
import console;

// 获取椭圆曲线参数
var ecParams = BouncyCastle.Asn1.X9.ECNamedCurveTable.GetByName("secp256r1");
var domainParams = BouncyCastle.Crypto.Parameters.ECDomainParameters(
    ecParams.Curve,
    ecParams.G,
    ecParams.N,
    ecParams.H,
    ecParams.GetSeed()
);

// 创建密钥生成参数
var ecKeyGenParams = BouncyCastle.Crypto.Parameters.ECKeyGenerationParameters(
    domainParams,
    BouncyCastle.Security.SecureRandom()
);

// 生成密钥对
var ecKeyPairGen = BouncyCastle.Crypto.Generators.ECKeyPairGenerator();
ecKeyPairGen.Init(ecKeyGenParams);
var ecKeyPair = ecKeyPairGen.GenerateKeyPair();

console.dump(ecKeyPair);
console.pause();
```

### 示例28 - EC Diffie-Hellman 密钥协商（aardio 实现）

```aardio
import BouncyCastle;
import console;

// 获取椭圆曲线参数
var ecParams = BouncyCastle.Asn1.X9.ECNamedCurveTable.GetByName("secp256r1");
var domainParams = BouncyCastle.Crypto.Parameters.ECDomainParameters(
    ecParams.Curve,
    ecParams.G,
    ecParams.N,
    ecParams.H,
    ecParams.GetSeed()
);

// 生成双方密钥对
var generator = BouncyCastle.Crypto.Generators.ECKeyPairGenerator();
var params = BouncyCastle.Crypto.Parameters.ECKeyGenerationParameters(
    domainParams,
    BouncyCastle.Security.SecureRandom()
);
generator.Init(params);

var keyPairA = generator.GenerateKeyPair();
var keyPairB = generator.GenerateKeyPair();

// 进行密钥协商
var agreementA = BouncyCastle.Crypto.Agreement.ECDHBasicAgreement();
agreementA.Init(keyPairA.Private);
var secretA = agreementA.CalculateAgreement(keyPairB.Public);

var agreementB = BouncyCastle.Crypto.Agreement.ECDHBasicAgreement();
agreementB.Init(keyPairB.Private);
var secretB = agreementB.CalculateAgreement(keyPairA.Public);

console.assert(secretA.Equals(secretB), "协商的密钥应该相同");
console.log("协商的共享密钥:", secretA.ToString(16));
console.pause();
```

注意：在示例27和28中，我们使用了 sect283k1 椭圆曲线（协因子 h=4），因此需要使用 ECDHCBasicAgreement 对象来考虑协因子。如果协因子 h=1，则应使用 ECDHBasicAgreement 对象。