SM2椭圆曲线公钥密码算法中的密钥封装与解封装过程
字数 4597 2025-12-10 04:00:26

SM2椭圆曲线公钥密码算法中的密钥封装与解封装过程

今天我们来讲解SM2椭圆曲线公钥密码算法中的一个重要功能:密钥封装与解封装。这个过程是密钥交换协议(如SM2密钥交换协议)的核心部分,也常用于安全地传递对称密钥。

算法背景与目标

在非对称密码学中,直接使用公钥加密一个大消息(如一个文件)效率很低。通常的做法是:

  1. 随机生成一个对称密钥(如AES密钥)。
  2. 使用接收方的公钥,将这个对称密钥封装(加密)成一个密文
  3. 接收方收到密文后,用自己的私钥解封装(解密)出对称密钥。

这个过程保证了只有持有对应私钥的接收方才能恢复出对称密钥,进而用于后续的对称加密通信。SM2标准(GM/T 0003-2012)中定义了基于椭圆曲线的密钥封装机制(KEM)。

核心数学基础

SM2基于椭圆曲线密码学(ECC)。在详细介绍过程前,我们先复习几个关键点:

  1. 椭圆曲线参数:使用定义在有限域 \(GF(p)\) 上的椭圆曲线,由方程 \(y^2 = x^3 + ax + b \pmod{p}\) 确定。同时确定一个基点 \(G\),其阶为一个非常大的素数 \(n\)
  2. 密钥对
    • 私钥 \(d_A\):一个在区间 \([1, n-1]\) 内随机选择的整数。
    • 公钥 \(P_A\):由私钥计算得出,\(P_A = d_A \cdot G\)(椭圆曲线上的标量乘法)。
  3. 密钥派生函数(KDF):SM2使用一个特定的KDF(通常基于SM3哈希算法),可以将一个共享的秘密点坐标转换成一个或多个指定长度的密钥比特串。

密钥封装过程(发送方操作)

假设发送方(Bob)想要将一个对称密钥安全地发送给接收方(Alice)。Bob拥有Alice的公钥 \(P_A\)

步骤 1:产生临时密钥对

Bob首先生成一个临时的椭圆曲线密钥对。

  • 随机选择一个整数 \(k \in [1, n-1]\),作为临时私钥。
  • 计算临时公钥点 \(C_1 = k \cdot G\)。这个点将被作为密文的第一部分发送。

步骤 2:计算共享秘密点

Bob使用Alice的公钥和自己临时私钥计算一个共享秘密点。

  • 计算点 \(S = k \cdot P_A\)
    • 从数学上推导:\(S = k \cdot P_A = k \cdot (d_A \cdot G) = (k \cdot d_A) \cdot G\)
    • 注意,这个点 \(S\) 与后续Alice计算出的点是一致的。

步骤 3:将共享秘密点转换为比特串

共享秘密点 \(S\) 是一个椭圆曲线上的点,包含 \((x_S, y_S)\) 两个坐标。

  • 将坐标 \(x_S\)\(y_S\) 分别转换为大端序的字节串。
  • 将这两个字节串拼接起来,形成一个比特串(更准确地说是字节串),记作 \(\bar{S}\)

步骤 4:通过KDF派生密钥

这就是封装的核心。Bob要将目标对称密钥(假设是一个长度为 keylen 比特的密钥 \(K\))封装进密文。但实际过程中,Bob并不先有一个 \(K\),而是通过一个过程同时产生 \(K\) 和封装它的密文 \(C_2\)

  • 设定需要派生的密钥长度 keylen(例如,128比特对应AES-128)。
  • 将拼接后的共享秘密比特串 \(\bar{S}\) 输入到KDF函数中,派生出一个长度为 (keylen + 哈希输出长度) 的比特串。这里的“哈希输出长度”通常指SM3的输出长度(256比特)。我们记派生出的比特串为 KDF( \bar{S}, keylen + 256)
  • 将这个派生出的长比特串切成两部分
    • keylen 比特就是需要的对称密钥 \(K\)
    • 剩余的比特(假设为 \(t\))用于下一步的计算。

步骤 5:计算封装密文

Bob现在需要构造密文的第二部分 \(C_2\),使得只有Alice才能恢复出 \(K\)

  • 计算 \(C_2 = K \oplus t\)
    • 这里的 \(t\) 是步骤4中派生出的后256比特。
    • 注意:在一些文献和实现中,步骤4派生的比特串可能为 KDF( \bar{S}, keylen),然后直接设 \(C_2 = K \oplus KDF( \bar{S}, keylen)\),但这存在一定的安全风险。标准做法是让KDF输出更长的比特,用一部分做 \(K\),另一部分(\(t\))做掩码与 \(K\) 异或。这个机制确保了解封装方必须能正确计算出 \(\bar{S}\)\(t\),才能通过异或还原 \(K\)
  • 更严谨的描述(基于标准):设 \( K = \text{KDF}(\bar{S}, \text{keylen})\),然后计算一个与明文相关的哈希值 \(H\)(例如,对 \(\bar{S}\) 和待加密的明文做哈希),最后计算 \(C_2 = K \oplus M\)(如果 \(M\) 是明文)或 \(C_2 = K \oplus t\)(在仅封装密钥时)。为了保证讲解清晰,我们采用派生长比特串并分割的方案。

步骤 6:生成完整密文

最后,Bob需要生成一个密文 \(C\),以便Alice能够验证解封装的正确性。

  • 密文 \(C\) 由三部分组成:\(C = C_1 \parallel C_2 \parallel C_3\)
  • \(C_1\) 是步骤1中得到的临时公钥点(通常编码为压缩或未压缩形式)。
  • \(C_2\) 是步骤5中计算出的密文比特串。
  • \(C_3\) 是一个消息认证码(MAC),用于确保数据完整性。\(C_3\) 通过对共享秘密 \(\bar{S}\) 和(在密钥封装场景下)可能包含的辅助信息(或直接对 \(C_2\) )进行哈希计算得到,例如 \(C_3 = \text{Hash}(\bar{S} \parallel C_2)\)。这能防止密文在传输中被篡改。

现在,Bob将 \(C\) 发送给Alice。

密钥解封装过程(接收方操作)

Alice收到密文 \(C\) 后,用自己的私钥 \(d_A\) 进行解封装,以恢复对称密钥 \(K\)

步骤 1:解析密文

Alice将接收到的 \(C\) 拆分成三部分:\(C_1\)\(C_2\)\(C_3\)

  • \(C_1\) 是一个椭圆曲线上的点(Bob的临时公钥)。
  • \(C_2\) 是封装了密钥的密文比特串。
  • \(C_3\) 是哈希值,用于验证。

步骤 2:计算共享秘密点

这是最关键的一步,与封装过程对应。

  • 使用自己的私钥 \(d_A\) 和收到的 \(C_1\) 计算共享秘密点:\(S' = d_A \cdot C_1\)
    • 从数学上推导:\(S' = d_A \cdot C_1 = d_A \cdot (k \cdot G) = (d_A \cdot k) \cdot G\)
    • 这与Bob在步骤2计算的 \(S = (k \cdot d_A) \cdot G\)同一个点。这是椭圆曲线离散对数问题的性质保证了 \(k \cdot d_A = d_A \cdot k\)

步骤 3:将共享秘密点转换为比特串

与发送方步骤3完全相同。

  • 获取 \(S'\) 的坐标 \((x_{S'}, y_{S'})\)
  • 将它们转换成字节串并拼接,得到比特串 \(\bar{S}'\)。如果传输和计算无误, \(\bar{S}'\) 应该等于Bob那边的 \(\bar{S}\)

步骤 4:验证消息认证码(可选但重要)

在尝试解封装前,先验证 \(C_3\) 以确保密文完整性。

  • 使用计算出的 \(\bar{S}'\) 和收到的 \(C_2\),按照与发送方相同的规则计算哈希值:\(C_3' = \text{Hash}(\bar{S}' \parallel C_2)\)
  • 比较计算出的 \(C_3'\) 与收到的 \(C_3\)
  • 如果不相等,说明密文可能在传输中被篡改,或者发送方身份有误。应立即中止操作,不进行后续解封装。

步骤 5:通过KDF派生掩码并还原密钥

这是解封装的核心。

  • \(\bar{S}'\) 输入KDF函数,派生出一个与发送方步骤4长度完全相同的比特串:KDF( \bar{S}', keylen + 256)
  • 同样,将这个派生出的长比特串切成两部分:
    • keylen 比特记为 \(K'\)(这是尝试恢复的对称密钥)。
    • 剩余的256比特记为 \(t'\)
  • 还原密钥:计算 \(K'' = C_2 \oplus t'\)
    • 因为发送方计算的是 \(C_2 = K \oplus t\),而如果一切正确,我们有 \(t' = t\)
    • 所以 \(K'' = (K \oplus t) \oplus t = K\)

步骤 6:输出密钥

至此,Alice成功恢复了对称密钥 \(K\)。她可以将 \(K\) 用于后续的对称加解密操作。

安全性总结与要点

  1. 机密性:只有拥有私钥 \(d_A\) 的Alice才能计算出正确的共享秘密点 \(S'\),从而派生出正确的 \(t'\) 来解出 \(K\)。基于椭圆曲线离散对数问题(ECDLP)的困难性保证了这一点。
  2. 完整性\(C_3\) 哈希值(MAC)的引入,确保了密文 \(C_1\)\(C_2\) 在传输过程中未被篡改。这防止了主动攻击者篡改 \(C_1\) 导致密钥协商错误或篡改 \(C_2\) 导致解出错误密钥。
  3. 前向安全性:如果Bob每次封装都使用一个随机的新临时私钥 \(k\),那么即使Alice的长期私钥 \(d_A\) 在未来某一天泄露,攻击者也无法根据存储的旧密文 \(C\) 恢复出历史会话密钥 \(K\)。因为从 \(C_1 = k \cdot G\)\(P_A = d_A \cdot G\) 计算 \(S = k \cdot d_A \cdot G\) 仍然需要解决ECDLP。
  4. KDF的作用:KDF不仅将椭圆曲线点映射成比特串,更重要的是,它保证了派生出的密钥比特具有较好的随机性,并且通过派生长比特串并分割使用的方式,将密钥 \(K\) 的恢复与共享秘密 \(\bar{S}\) 紧密绑定。

通过以上循序渐进的步骤,我们完成了对SM2密钥封装与解封装过程的详细讲解。这个过程巧妙地结合了椭圆曲线的数学特性、KDF和哈希函数,构建了一个安全高效的对称密钥传递机制。

SM2椭圆曲线公钥密码算法中的密钥封装与解封装过程 今天我们来讲解SM2椭圆曲线公钥密码算法中的一个重要功能: 密钥封装与解封装 。这个过程是密钥交换协议(如SM2密钥交换协议)的核心部分,也常用于安全地传递对称密钥。 算法背景与目标 在非对称密码学中,直接使用公钥加密一个大消息(如一个文件)效率很低。通常的做法是: 随机生成一个对称密钥(如AES密钥)。 使用接收方的公钥,将这个对称密钥 封装 (加密)成一个 密文 。 接收方收到密文后,用自己的私钥 解封装 (解密)出对称密钥。 这个过程保证了只有持有对应私钥的接收方才能恢复出对称密钥,进而用于后续的对称加密通信。SM2标准(GM/T 0003-2012)中定义了基于椭圆曲线的密钥封装机制(KEM)。 核心数学基础 SM2基于椭圆曲线密码学(ECC)。在详细介绍过程前,我们先复习几个关键点: 椭圆曲线参数 :使用定义在有限域 \( GF(p) \) 上的椭圆曲线,由方程 \( y^2 = x^3 + ax + b \pmod{p} \) 确定。同时确定一个基点 \( G \),其阶为一个非常大的素数 \( n \)。 密钥对 : 私钥 \( d_ A \):一个在区间 \( [ 1, n-1 ] \) 内随机选择的整数。 公钥 \( P_ A \):由私钥计算得出,\( P_ A = d_ A \cdot G \)(椭圆曲线上的标量乘法)。 密钥派生函数(KDF) :SM2使用一个特定的KDF(通常基于SM3哈希算法),可以将一个共享的秘密点坐标转换成一个或多个指定长度的密钥比特串。 密钥封装过程(发送方操作) 假设发送方(Bob)想要将一个对称密钥安全地发送给接收方(Alice)。Bob拥有Alice的公钥 \( P_ A \)。 步骤 1:产生临时密钥对 Bob首先生成一个临时的椭圆曲线密钥对。 随机选择一个整数 \( k \in [ 1, n-1 ] \),作为临时私钥。 计算临时公钥点 \( C_ 1 = k \cdot G \)。这个点将被作为密文的第一部分发送。 步骤 2:计算共享秘密点 Bob使用Alice的公钥和自己临时私钥计算一个共享秘密点。 计算点 \( S = k \cdot P_ A \)。 从数学上推导:\( S = k \cdot P_ A = k \cdot (d_ A \cdot G) = (k \cdot d_ A) \cdot G \)。 注意,这个点 \( S \) 与后续Alice计算出的点是一致的。 步骤 3:将共享秘密点转换为比特串 共享秘密点 \( S \) 是一个椭圆曲线上的点,包含 \( (x_ S, y_ S) \) 两个坐标。 将坐标 \( x_ S \) 和 \( y_ S \) 分别转换为大端序的字节串。 将这两个字节串拼接起来,形成一个比特串(更准确地说是字节串),记作 \( \bar{S} \)。 步骤 4:通过KDF派生密钥 这就是 封装 的核心。Bob要将目标对称密钥(假设是一个长度为 keylen 比特的密钥 \( K \))封装进密文。但实际过程中,Bob并不先有一个 \( K \),而是通过一个过程 同时产生 \( K \) 和封装它的密文 \( C_ 2 \)。 设定需要派生的密钥长度 keylen (例如,128比特对应AES-128)。 将拼接后的共享秘密比特串 \( \bar{S} \) 输入到KDF函数中,派生出一个长度为 (keylen + 哈希输出长度) 的比特串。这里的“哈希输出长度”通常指SM3的输出长度(256比特)。我们记派生出的比特串为 KDF( \bar{S}, keylen + 256) 。 将这个派生出的长比特串 切成两部分 : 前 keylen 比特就是需要的对称密钥 \( K \)。 剩余的比特(假设为 \( t \))用于下一步的计算。 步骤 5:计算封装密文 Bob现在需要构造密文的第二部分 \( C_ 2 \),使得只有Alice才能恢复出 \( K \)。 计算 \( C_ 2 = K \oplus t \)。 这里的 \( t \) 是步骤4中派生出的后256比特。 注意 :在一些文献和实现中,步骤4派生的比特串可能为 KDF( \bar{S}, keylen) ,然后直接设 \( C_ 2 = K \oplus KDF( \bar{S}, keylen) \),但这存在一定的安全风险。标准做法是让KDF输出更长的比特,用一部分做 \( K \),另一部分(\( t \))做掩码与 \( K \) 异或。这个机制确保了解封装方必须能正确计算出 \( \bar{S} \) 和 \( t \),才能通过异或还原 \( K \)。 更严谨的描述(基于标准):设 \( K = \text{KDF}(\bar{S}, \text{keylen})\),然后计算一个与明文相关的哈希值 \( H \)(例如,对 \( \bar{S} \) 和待加密的明文做哈希),最后计算 \( C_ 2 = K \oplus M \)(如果 \( M \) 是明文)或 \( C_ 2 = K \oplus t \)(在仅封装密钥时)。为了保证讲解清晰,我们采用派生长比特串并分割的方案。 步骤 6:生成完整密文 最后,Bob需要生成一个密文 \( C \),以便Alice能够验证解封装的正确性。 密文 \( C \) 由三部分组成:\( C = C_ 1 \parallel C_ 2 \parallel C_ 3 \)。 \( C_ 1 \) 是步骤1中得到的临时公钥点(通常编码为压缩或未压缩形式)。 \( C_ 2 \) 是步骤5中计算出的密文比特串。 \( C_ 3 \) 是一个 消息认证码(MAC) ,用于确保数据完整性。\( C_ 3 \) 通过对共享秘密 \( \bar{S} \) 和(在密钥封装场景下)可能包含的辅助信息(或直接对 \( C_ 2 \) )进行哈希计算得到,例如 \( C_ 3 = \text{Hash}(\bar{S} \parallel C_ 2) \)。这能防止密文在传输中被篡改。 现在,Bob将 \( C \) 发送给Alice。 密钥解封装过程(接收方操作) Alice收到密文 \( C \) 后,用自己的私钥 \( d_ A \) 进行解封装,以恢复对称密钥 \( K \)。 步骤 1:解析密文 Alice将接收到的 \( C \) 拆分成三部分:\( C_ 1 \), \( C_ 2 \), \( C_ 3 \)。 \( C_ 1 \) 是一个椭圆曲线上的点(Bob的临时公钥)。 \( C_ 2 \) 是封装了密钥的密文比特串。 \( C_ 3 \) 是哈希值,用于验证。 步骤 2:计算共享秘密点 这是最关键的一步,与封装过程对应。 使用自己的私钥 \( d_ A \) 和收到的 \( C_ 1 \) 计算共享秘密点:\( S' = d_ A \cdot C_ 1 \)。 从数学上推导:\( S' = d_ A \cdot C_ 1 = d_ A \cdot (k \cdot G) = (d_ A \cdot k) \cdot G \)。 这与Bob在步骤2计算的 \( S = (k \cdot d_ A) \cdot G \) 是 同一个点 。这是椭圆曲线离散对数问题的性质保证了 \( k \cdot d_ A = d_ A \cdot k \)。 步骤 3:将共享秘密点转换为比特串 与发送方步骤3完全相同。 获取 \( S' \) 的坐标 \( (x_ {S'}, y_ {S'}) \)。 将它们转换成字节串并拼接,得到比特串 \( \bar{S}' \)。如果传输和计算无误, \( \bar{S}' \) 应该等于Bob那边的 \( \bar{S} \)。 步骤 4:验证消息认证码(可选但重要) 在尝试解封装前,先验证 \( C_ 3 \) 以确保密文完整性。 使用计算出的 \( \bar{S}' \) 和收到的 \( C_ 2 \),按照与发送方相同的规则计算哈希值:\( C_ 3' = \text{Hash}(\bar{S}' \parallel C_ 2) \)。 比较计算出的 \( C_ 3' \) 与收到的 \( C_ 3 \)。 如果不相等 ,说明密文可能在传输中被篡改,或者发送方身份有误。应立即中止操作,不进行后续解封装。 步骤 5:通过KDF派生掩码并还原密钥 这是 解封装 的核心。 将 \( \bar{S}' \) 输入KDF函数,派生出一个与发送方步骤4 长度完全相同 的比特串: KDF( \bar{S}', keylen + 256) 。 同样,将这个派生出的长比特串切成两部分: 前 keylen 比特记为 \( K' \)(这是尝试恢复的对称密钥)。 剩余的256比特记为 \( t' \)。 还原密钥 :计算 \( K'' = C_ 2 \oplus t' \)。 因为发送方计算的是 \( C_ 2 = K \oplus t \),而如果一切正确,我们有 \( t' = t \)。 所以 \( K'' = (K \oplus t) \oplus t = K \)。 步骤 6:输出密钥 至此,Alice成功恢复了对称密钥 \( K \)。她可以将 \( K \) 用于后续的对称加解密操作。 安全性总结与要点 机密性 :只有拥有私钥 \( d_ A \) 的Alice才能计算出正确的共享秘密点 \( S' \),从而派生出正确的 \( t' \) 来解出 \( K \)。基于椭圆曲线离散对数问题(ECDLP)的困难性保证了这一点。 完整性 :\( C_ 3 \) 哈希值(MAC)的引入,确保了密文 \( C_ 1 \) 和 \( C_ 2 \) 在传输过程中未被篡改。这防止了主动攻击者篡改 \( C_ 1 \) 导致密钥协商错误或篡改 \( C_ 2 \) 导致解出错误密钥。 前向安全性 :如果Bob每次封装都使用一个随机的新临时私钥 \( k \),那么即使Alice的长期私钥 \( d_ A \) 在未来某一天泄露,攻击者也无法根据存储的旧密文 \( C \) 恢复出历史会话密钥 \( K \)。因为从 \( C_ 1 = k \cdot G \) 和 \( P_ A = d_ A \cdot G \) 计算 \( S = k \cdot d_ A \cdot G \) 仍然需要解决ECDLP。 KDF的作用 :KDF不仅将椭圆曲线点映射成比特串,更重要的是,它保证了派生出的密钥比特具有较好的随机性,并且通过派生长比特串并分割使用的方式,将密钥 \( K \) 的恢复与共享秘密 \( \bar{S} \) 紧密绑定。 通过以上循序渐进的步骤,我们完成了对SM2密钥封装与解封装过程的详细讲解。这个过程巧妙地结合了椭圆曲线的数学特性、KDF和哈希函数,构建了一个安全高效的对称密钥传递机制。