视频加载失败

从群到 ElGamal 加密算法

4154 字
21 分钟
从群到 ElGamal 加密算法
NOTE

原文由零音在 MoeCTF 2025 时完成。CrystaRin镜雨亭 迁移时,GPT-6.1-Sol 负责了对本文的审阅和部分修改。

ElGamal 加密的核心思路,是让发送方和接收方算出同一个秘密,再用它掩蔽消息。为什么接收方能撤销这层掩蔽?这与群中的 逆元 有关。至于旁观者为什么难以破解,则是另一个关于计算难度的问题。

我们先从群和模逆元出发,理解算法为什么能正确解密,再讨论它需要什么安全前提。

群#

群的定义和性质#

一个群是一个有序对 (G,∗)(G, \ast), 其中 GG 是一个非空集合,∗\ast 是定义在 GG 上的二元运算。我们要求该运算须满足以下四条公理:

  1. 封闭性(Closure):对于任意 a,b∈Ga, b \in G,都有 a∗b∈Ga * b \in G。一般地,当我们说 ∗* 是定义在 GG 上的二元运算时,封闭性通常已经隐含。
  2. 结合律(Associativity):对于任意的 a,b,c∈Ga, b, c \in G,都有 (a∗b)∗c=a∗(b∗c)(a * b) * c = a * (b * c)。
  3. 单位元(Identity element):存在某个元素 e∈Ge \in G,使得对于任意 a∈Ga \in G,都有 e∗a=a∗e=ae * a = a * e = a。
  4. 逆元(Inverse element):对于任意 a∈Ga \in G,存在某个元素 a−1∈Ga^{-1} \in G 使得 a∗a−1=a−1∗a=ea * a^{-1} = a^{-1} * a = e。

群不能只看作一个集合或一个运算,它必须是两者的结合,同时满足上面的公理。如果一个群满足交换律(即 a∗b=b∗aa * b = b * a 对于任意 a,b∈Ga, b \in G 成立),则我们称该群为 阿贝尔群(Abelian group)。

群可以分为有限群和无限群。有限群的集合 GG 中元素个数有限,记作 ∣G∣=n\lvert G \rvert = n,称为群的阶,即群的集合内元素的数量:例如 (Zn,+)(\mathbb{Z}_n, +),即模 nn 的整数加法群,其阶为 nn。无限群的集合元素个数无限:例如 (Z,+)(\mathbb{Z}, +),即整数加法群。

对于一个群 (G,∗)(G, \ast),其中 ∗\ast 是群的二元运算,如果 HH 是 GG 的一个非空子集,并且 HH 在运算 ∗\ast 下也构成一个群,则称 HH 为 GG 的一个 子群,记作 H≤GH \leq G。

我们判定一个群 GG 的非空子集 HH 是子群,当且仅当同时满足以下三个条件:

  1. 封闭性:对于任意 a,b∈Ha, b \in H,有 a∗b∈Ha * b \in H。
  2. 单位元:GG 的单位元 ee 也属于 HH。
  3. 逆元:对于任意 a∈Ha \in H,有 aa 的逆元 a−1∈Ha^{-1} \in H。

设 GG 是一个群,SS 是 GG 的一个非空子集。所有包含 SS 的 GG 的子群中最小的那个记作 ⟨S⟩\langle S \rangle,它由所有形如 s1ϵ1s2ϵ2⋯skϵks_1^{\epsilon_1} s_2^{\epsilon_2} \cdots s_k^{\epsilon_k} 的元素构成,其中 si∈Ss_i \in S,ϵi=±1\epsilon_i = \pm 1,kk 为任意非负整数;k=0k = 0 时的空乘积约定为单位元。如果 ⟨S⟩=G\langle S \rangle = G,我们就称 SS 是群 GG 的一个生成集。如果一个元素 gg 就能生成整个群,即 G=⟨g⟩G = \langle g \rangle,则称 GG 为循环群,gg 为这个群的一个生成元。一般生成集中的元素不一定能单独生成整个群。

ElGamal 可以定义在循环群上。本文从模素数乘法群 Zp∗\mathbb{Z}_p^\ast 入手:它的元素严格说是模 pp 的非零剩余类,我们用 1,2,⋯ ,p−11, 2, \cdots, p - 1 作为代表。群运算是模 pp 的乘法,即将两个代表数相乘后取模 pp。我们将在下文证明它满足群的四条公理;算法部分则会选用它的一个合适的子群。

一个经典结论是:对于任意素数 pp,群 Zp∗\mathbb{Z}_p^\ast 都是循环群。本文引用这一结论,不展开证明。这意味着,存在一个原根,也就是整个群的生成元 g∈Zp∗g \in \mathbb{Z}_p^\ast,使得

g1,g2,g3,⋯ ,gp−1(modp)g^1, g^2, g^3, \cdots, g^{p - 1} \pmod{p}

恰好覆盖 Zp∗\mathbb{Z}^*_p 集合中的所有元素。

模 p 乘法群的运算满足四条公理#

以下固定素数 pp,记 G=Zp∗G = \mathbb{Z}_p^\ast,并继续用 1,2,⋯ ,p−11, 2, \cdots, p - 1 表示其中的元素。

封闭性#

群的封闭性要求一个集合中的两个数经过运算后,其结果一定还在这个集合里。

对于任意 a,b∈Ga, b \in G,都有 p∤ap \nmid a 且 p∤bp \nmid b。根据素数的性质,若 p∣abp \mid ab,就必有 p∣ap \mid a 或 p∣bp \mid b,因此这里 p∤abp \nmid ab。所以 ab mod pab \bmod p 不为 00,仍在 1,2,⋯ ,p−11, 2, \cdots, p - 1 中,即

a⋅b mod p∈G.\boxed{a \cdot b \bmod p \in G.}

结合律#

形式上,一个在集合 GG 上的二元运算 ∗* 满足结合律,是指

(a∗b)∗c=a∗(b∗c)∀a,b,c∈G.(a\ast b)\ast c = a\ast(b\ast c) \quad \forall a, b, c \in G.

模乘法继承了整数的结合律 (a⋅b)⋅c=a⋅(b⋅c)(a \cdot b) \cdot c = a \cdot (b \cdot c),而模运算和乘法操作是良好兼容的,也就是说,在乘法运算中无论何时取模,最终的计算结果都是一致的。即,我们有

(a⋅b) mod p=((a mod p)⋅(b mod p)) mod p.\boxed{(a \cdot b) \bmod p = ((a \bmod p) \cdot (b \bmod p)) \bmod p.}

下面给出一个证明:我们设

a=qap+ra,0≤ra<p,b=qbp+rb,0≤rb<pa = q_a p + r_a, \quad 0 \leq r_a < p, \quad b = q_b p + r_b, \quad 0 \leq r_b < p

其中 ra=a mod p, rb=b mod pr_a = a \bmod p, \ r_b = b \bmod p。

注意到

ab=(qap+ra)(qbp+rb)=p(pqaqb+qarb+qbra)+rarbab = (q_a p + r_a)(q_b p + r_b) = p(pq_aq_b + q_ar_b + q_br_a) + r_ar_b

因此 ab≡rarb(modp)ab \equiv r_ar_b \pmod p,从而

(ab) mod p=(rarb) mod p=((a mod p)(b mod p)) mod p.(ab)\bmod p = (r_ar_b)\bmod p = ((a\bmod p)(b\bmod p))\bmod p.

注意,rar_a 和 rbr_b 虽然都小于 pp,它们的乘积却未必小于 pp,所以不能省略最后一次取模。由此结合整数乘法的结合律,可得

((ab mod p)c) mod p=abc mod p=(a(bc mod p)) mod p.\boxed{((ab\bmod p)c)\bmod p = abc\bmod p = (a(bc\bmod p))\bmod p.}

这就证明了模 pp 乘法的结合律。

单位元#

因为 1∈G1 \in G,且对任意 a∈Ga \in G 都有

1⋅a mod p=a⋅1 mod p=a,\boxed{1 \cdot a \bmod p = a \cdot 1 \bmod p = a,}

所以 11 就是单位元。

逆元#

类比通常的乘法中 3×13=13 \times \frac{1}{3} = 1,模乘法中的逆元也用来撤销一次乘法。区别在于,模 pp 的逆元不是通常意义下的分数,而是群中的另一个元素。

我们要证明:对于任意 a∈Ga \in G,存在 b∈Gb \in G,使得 ab≡1(modp)ab \equiv 1 \pmod p。这里可以使用 裴蜀定理(Bézout’s lemma):

裴蜀定理(Bézout’s lemma)

设 a,ba, b 为不全为 00 的整数。对于任意 x,y∈Zx, y \in \mathbb{Z},都有 gcd⁡(a,b)∣(ax+by)\gcd(a,b) \mid (ax+by);而且,存在整数 x,yx, y,使得 ax+by=gcd⁡(a,b)ax+by=\gcd(a,b)。完整证明见文末附录。

由于 pp 是素数,任意 a∈{1,2,⋯ ,p−1}a \in \{1, 2, \cdots, p - 1\} 都满足 gcd⁡(a,p)=1\gcd(a, p) = 1,因此存在整数 x,yx, y,使得

ax+py=1ax + py = 1

对上式两侧取模 pp,可得

ax≡1(modp)ax \equiv 1 \pmod{p}

令 b=x mod pb = x \bmod p,则 ab≡1(modp)ab \equiv 1 \pmod p。这里 bb 不可能为 00,否则 abab 将与 00 同余,因此 b∈Gb \in G。模乘法满足交换律,所以也有 ba≡1(modp)ba \equiv 1 \pmod p,即 bb 是 aa 的逆元。

∀a∈G, ∃b∈G,ab≡ba≡1(modp).\boxed{\forall a \in G,\ \exists b \in G,\quad ab \equiv ba \equiv 1 \pmod p.}

例如,2⋅12−23=12 \cdot 12 - 23 = 1,因此 2⋅12≡1(mod23)2 \cdot 12 \equiv 1 \pmod{23},所以 22 在模 2323 下的逆元是 1212。上面的存在性证明,也能通过附录中的扩展欧几里得算法转化为求逆元的方法。

ElGamal 算法浅析#

从共享秘密到消息掩蔽#

设 Bob 的私钥为 xx,公钥中的一个元素为 y=gxy = g^x。Alice 加密时另外选取随机数 kk,并发送 c1=gkc_1 = g^k。于是,两人可以分别算出

yk=(gx)k⏟Alice 计算=gxk=(gk)x=c1x⏟Bob 计算.\underbrace{y^k = (g^x)^k}_{\text{Alice 计算}} = g^{xk} = \underbrace{(g^k)^x = c_1^x}_{\text{Bob 计算}}.

这里先用群中的幂运算表示;在下文选取的模乘法群中,所有结果都要取模 pp。

两人算出的同一个群元素记为 ss。Alice 用乘法把它与消息结合,得到 c2=msc_2 = ms;Bob 则乘上 s−1s^{-1},撤销这层掩蔽。这说明了逆元为什么能保证解密的正确性。但旁观者从 g,gx,gkg,g^x,g^k 中能否得到 ss,以及密文是否泄露消息的信息,需要另行讨论。

选取群与参数#

为了把算法和后面的安全讨论统一起来,我们选用模素数乘法群中的一个素数阶子群:

G=⟨g⟩≤Zp∗,∣G∣=q,q∣(p−1),G = \langle g\rangle \leq \mathbb{Z}_p^\ast,\qquad |G| = q,\qquad q\mid(p-1),

其中 p,qp,q 都是素数,qq 足够大,gg 的阶恰好为 qq,即 gq≡1(modp)g^q \equiv 1 \pmod p 且 g≠1g \ne 1。此处的 gg 只需要生成子群 GG,不再要求它是整个 Zp∗\mathbb{Z}_p^\ast 的原根。

具体参数不能仅凭「p,qp,q 都很大」就认定安全,还需要所选群中的相关计算问题足够困难。本文先在这样的群中说明算法,再解释安全假设。

消息必须是一个群元素 m∈Gm \in G,而不是任意整数、文本或文件。如何把一般消息编码到群中并无歧义地恢复,是另外需要处理的问题,不能简单地把任意数据取模 pp。实际应用通常采用经过安全分析的混合加密方案。

密钥生成#

Bob 使用合适的公开群参数 (p,q,g)(p,q,g),然后生成密钥:

  1. 使用密码学安全的随机数生成器,均匀选取私钥 x∈{1,2,⋯ ,q−1}x \in \{1,2,\cdots,q-1\};
  2. 计算 y=gx mod py = g^x \bmod p。

Bob 公开公钥 (p,q,g,y)(p,q,g,y),并将私钥 xx 保密。群参数可以由协议统一规定,不必由每个接收方重新生成。

加密#

Alice 要向 Bob 发送消息 m∈Gm \in G:

  1. 获取 Bob 的公钥 (p,q,g,y)(p,q,g,y),并通过可信方式确认它确实属于 Bob;
  2. 使用密码学安全的随机数生成器,独立、均匀地选取临时随机指数 k∈{1,2,⋯ ,q−1}k \in \{1,2,\cdots,q-1\};
  3. 计算第一部分密文 c1=gk mod pc_1 = g^k \bmod p;
  4. 计算共享秘密 s=yk mod ps = y^k \bmod p,以及第二部分密文 c2=ms mod pc_2 = ms \bmod p;
  5. 将密文 (c1,c2)(c_1,c_2) 发送给 Bob。

每次加密都要重新采样 kk,不能复用以前的值。独立随机采样理论上仍可能碰撞,因此随机空间也必须足够大。kk 通常不随密文发送,密文中公开的是 gk mod pg^k \bmod p。

解密与正确性#

对于按上述方式生成的合法密文,Bob 使用私钥 xx 解密:

  1. 计算共享秘密 s=c1x mod ps = c_1^x \bmod p;
  2. 计算 ss 的模 pp 逆元 s−1s^{-1},使得 ss−1≡1(modp)ss^{-1} \equiv 1 \pmod p;
  3. 恢复消息 m=c2s−1 mod pm = c_2s^{-1} \bmod p。

为什么双方能得到同一个 ss?因为

c1x≡(gk)x=gkx=(gx)k≡yk(modp).c_1^x \equiv (g^k)^x = g^{kx} = (g^x)^k \equiv y^k \pmod p.

而 s∈Gs \in G,所以逆元一定存在。于是

c2s−1≡m yk(c1x)−1≡m gxk(gkx)−1≡m(modp).\begin{aligned} c_2s^{-1} &\equiv m\,y^k(c_1^x)^{-1} \\ &\equiv m\,g^{xk}(g^{kx})^{-1} \\ &\equiv m \pmod p. \end{aligned}

由于我们用 1,2,⋯ ,p−11,2,\cdots,p-1 中的数代表群元素,c2s−1 mod pc_2s^{-1}\bmod p 就恰好等于原消息 mm。

一个小例子#

为了便于手算,取 p=23,q=11,g=2p=23,q=11,g=2。因为 211≡1(mod23)2^{11}\equiv1\pmod{23},2≠12\ne1,而 1111 是素数,所以 22 的阶为 1111。

Bob 选取私钥 x=6x=6,得到 y=26 mod 23=18y=2^6\bmod23=18。Alice 要发送 m=9m=9,它确实属于 GG,因为 9=25 mod 239=2^5\bmod23。选取 k=3k=3 后,

c1=23 mod 23=8,s=183 mod 23=13,c2=9⋅13 mod 23=2.\begin{aligned} c_1 &= 2^3\bmod23=8,\\ s &= 18^3\bmod23=13,\\ c_2 &= 9\cdot13\bmod23=2. \end{aligned}

Bob 收到 (8,2)(8,2) 后,算出 s=86 mod 23=13s=8^6\bmod23=13。由于 13⋅16≡1(mod23)13\cdot16\equiv1\pmod{23},所以 s−1=16s^{-1}=16,最终恢复

m=2⋅16 mod 23=9.m=2\cdot16\bmod23=9.

能正确解密,不等于已经安全#

安全性依赖什么#

如果旁观者能从 gg 和 gxg^x 求出 xx,也就是解决 离散对数问题,就能得到 Bob 的私钥。但「离散对数难求」还不足以直接推出完整的消息保密结论。

这里需要区分两个相关问题:

  • 计算 Diffie–Hellman 问题(CDH):给定 g,ga,gbg,g^a,g^b,计算 gabg^{ab}。这与旁观者能否直接算出共享秘密有关。
  • 判定 Diffie–Hellman 问题(DDH):给定 g,ga,gbg,g^a,g^b 和一个候选元素 TT,判断 TT 是 gabg^{ab},还是独立均匀选取的群元素。

在合适的群中,若 DDH 假设成立,即对适当随机选取的指数,上述两种情况在计算上难以区分,基础 ElGamal 可以达到 选择明文攻击下的语义安全(IND-CPA)。直观地说,即使攻击者能选择两条候选消息,也难以从挑战密文判断加密的是哪一条。这里不展开形式化安全实验与证明。

这也是不能随便选群的原因。对于奇素数 pp,整个 Zp∗\mathbb{Z}_p^\ast 上的 DDH 可以利用二次剩余性质来区分。一个非零元素如果能写成某个非零元素的平方,就叫作 二次剩余;这一性质可以高效判定。若 gg 是整个群的原根,则 gtg^t 是二次剩余,当且仅当 tt 为偶数。于是,公开的 y=gxy=g^x 和 c1=gkc_1=g^k 会暴露 x,kx,k 的奇偶性,进而暴露掩码 gxkg^{xk} 的二次剩余性质,再结合 c2=mgxkc_2=mg^{xk},就能判断 mm 是否为二次剩余。

因此,直接在整个 Zp∗\mathbb{Z}_p^\ast 中掩蔽任意非零消息,虽然可以正确解密,却会泄露消息的一项性质,不能满足上述安全要求。选用合适的素数阶子群可以避开这里的二次剩余区分,但 素数阶本身并不自动保证 DDH 困难,仍需使用经过分析的群和参数。

为什么不能复用加密随机数#

若同一公钥下两次加密使用了相同的 kk,就会使用相同的掩码 s=yks=y^k。设两条消息分别为 m,m′m,m',则

c2≡ms,c2′≡m′s(modp),c_2 \equiv ms,\qquad c_2' \equiv m's \pmod p,

所以

c2′(c2)−1≡m′m−1(modp).c_2'(c_2)^{-1}\equiv m'm^{-1}\pmod p.

这会暴露两条明文之间的关系。一旦其中一条明文 mm 已知,就可以直接恢复另一条:

m′≡m c2′(c2)−1(modp).m'\equiv m\,c_2'(c_2)^{-1}\pmod p.

基础 ElGamal 不提供什么#

基础 ElGamal 本身不提供消息完整性或发送者身份认证,也不具备选择密文安全性。例如,攻击者可以选取 t∈Gt\in G,把密文 (c1,c2)(c_1,c_2) 改为

(c1,  tc2 mod p),(c_1,\;tc_2\bmod p),

接收方解密得到的就会是 tm mod ptm\bmod p。这种可以有规律地改变明文的性质,称为 可塑性。而且公钥是公开的,任何人都可以向 Bob 加密,解密成功并不能证明消息来自 Alice。

实际实现还必须处理公钥和密文的群成员检查、参数验证、侧信道等问题。对于任意文本或文件,通常使用经过安全分析的混合加密方案,将公钥机制与密钥派生、认证加密等组件正确组合,而不是直接套用这里的乘法公式,也不能假定随意增加一个组件就能补齐安全性。

总结起来:群和逆元解释了 ElGamal 为什么能正确解密;计算困难性假设、随机数和协议设计,才决定它能提供怎样的安全保障。

附录:裴蜀定理的证明#

欧几里得算法 / 辗转相除法#

先设 a,ba,b 为正整数,不妨设 a≥ba\geq b。对 aa 除以 bb,写成

a=bk+c,0≤c<b.a=bk+c,\qquad 0\leq c<b.

我们要证明

gcd⁡(a,b)=gcd⁡(b,c)=gcd⁡(b,a mod b).\gcd(a,b)=\gcd(b,c)=\gcd(b,a\bmod b).

设 dd 为正整数。若 d∣ad\mid a 且 d∣bd\mid b,则 d∣(a−bk)d\mid(a-bk),即 d∣cd\mid c。反过来,若 d∣bd\mid b 且 d∣cd\mid c,则 d∣(bk+c)d\mid(bk+c),即 d∣ad\mid a。因此,(a,b)(a,b) 和 (b,c)(b,c) 拥有完全相同的正公约数,最大公约数自然相同。

反复应用这个等式,就得到欧几里得算法。非零余数构成严格递减的正整数序列,所以算法必然在有限步后结束,最后一个非零余数就是最大公约数。如果一开始 b∣ab\mid a,则第一步余数为 00,直接得到 gcd⁡(a,b)=b\gcd(a,b)=b。

裴蜀定理的正式证明#

设 a,ba,b 为不全为 00 的整数,记 d=gcd⁡(a,b)>0d=\gcd(a,b)>0。因为 a=du,b=dva=du,b=dv,其中 u,vu,v 为整数,所以对任意整数 x,yx,y,都有

ax+by=d(ux+vy).ax+by=d(ux+vy).

因此 d∣(ax+by)d\mid(ax+by)。下面证明存在整数 x,yx,y,使得 ax+by=dax+by=d。

如果 b=0b=0,则 a≠0a\ne0 且 d=∣a∣d=|a|。当 a>0a>0 时取 (x,y)=(1,0)(x,y)=(1,0);当 a<0a<0 时取 (x,y)=(−1,0)(x,y)=(-1,0)。a=0a=0 的情形类似。

如果 a,ba,b 都非零,可以先对 ∣a∣,∣b∣|a|,|b| 证明:因为 gcd⁡(a,b)=gcd⁡(∣a∣,∣b∣)\gcd(a,b)=\gcd(|a|,|b|),对绝对值得到的系数可以通过调整符号转化为原整数对应的系数。因此以下只需考虑 a,ba,b 都为正数的情况,交换两者后不妨设 a≥ba\geq b。

若 b∣ab\mid a,直接取 (x,y)=(0,1)(x,y)=(0,1) 即可。否则,令 r−1=a,r0=br_{-1}=a,r_0=b,将欧几里得算法统一写为

ri−2=qiri−1+ri,0≤ri<ri−1.r_{i-2}=q_i r_{i-1}+r_i,\qquad 0\leq r_i<r_{i-1}.

记最后一个非零余数为 rnr_n,下一步余数为 rn+1=0r_{n+1}=0。根据前面证明的最大公约数不变性,

d=gcd⁡(a,b)=gcd⁡(rn,0)=rn.d=\gcd(a,b)=\gcd(r_n,0)=r_n.

现在要说明,每一个余数都能表示成 a,ba,b 的整数线性组合。先为初始两项选取系数:

r−1=1⋅a+0⋅b,(s−1,t−1)=(1,0),r0=0⋅a+1⋅b,(s0,t0)=(0,1).\begin{aligned} r_{-1}&=1\cdot a+0\cdot b, & (s_{-1},t_{-1})&=(1,0),\\ r_0&=0\cdot a+1\cdot b, & (s_0,t_0)&=(0,1). \end{aligned}

假设前两项已写成 ri−2=si−2a+ti−2br_{i-2}=s_{i-2}a+t_{i-2}b 和 ri−1=si−1a+ti−1br_{i-1}=s_{i-1}a+t_{i-1}b,则

ri=ri−2−qiri−1=(si−2−qisi−1)a+(ti−2−qiti−1)b.\begin{aligned} r_i &=r_{i-2}-q_i r_{i-1}\\ &=(s_{i-2}-q_i s_{i-1})a +(t_{i-2}-q_i t_{i-1})b. \end{aligned}

因此可以递推定义整数系数

si=si−2−qisi−1,ti=ti−2−qiti−1.s_i=s_{i-2}-q_i s_{i-1},\qquad t_i=t_{i-2}-q_i t_{i-1}.

从上述初值出发,归纳可得每个 rir_i 都满足 ri=sia+tibr_i=s_i a+t_i b,特别地,

d=rn=sna+tnb.d=r_n=s_n a+t_n b.

取 x=sn,y=tnx=s_n,y=t_n 就得到了所需的整数解,裴蜀定理得证。沿着欧几里得算法同时递推这些系数,就是 扩展欧几里得算法。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
从群到 ElGamal 加密算法
https://crystal.stellalyr.ink/posts/from-group-theory-to-elgamal-encryption/
作者
LyCecilion
发布于
2025-10-16
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
LyCecilion
Backward, go backward, turn back to the antemundane realm, go back to the -
CrystaRin镜雨亭 运营结束
零音已完成对 Hazelita 服务器的运营,CrystaRin镜雨亭 今日重新开放。本次维护中,零音将该站点由 Hexo (Stellar) 切换到了 Astro (Firefly)。一些破坏性变更包括:将评论插件更换到了 Giscus,没有保留 Waline 的数据;部分文章的 slug 变更。友链和 RSS 链接不受影响。
分类
标签
最新动态
站点统计
文章
10
分类
6
标签
37
总字数
81,593
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
ESA Pages
博客版本
Firefly v6.16.9
文章许可
CC BY-NC-SA 4.0
文章目录