本文分析了一种基于Cubic Pell曲线的RSA变体加密方法。作者尝试使用ChatGPT生成代码,但结果不理想,随后作者分享了从Sage代码转换而来的Python代码,并提供了在线测试链接,证明了该方案的可行性。

我一直在阅读这篇论文 [2]:

它是一种相对较新的将三次 Pell 曲线与 RSA 方法结合使用的方法。使用 RSA(Rivest、Shamir 和 Adleman)方法,我们生成两个素数($p$ 和 $q$),并创建一个模数 $N = p \cdot q$。 因此,该方法的安全性基于将模数分解为其原始素数的难度。在 2018 年,Murru 和 Saettone 提出了一种新的 RSA 方法,该方法基于具有 $N = p.q$ 形式的模数的三次 Pell 曲线 [1]。 通过这种方法,我们基于三次 Pell 曲线执行算术运算,而这只有消息的发送者和接收者知道。在 2024 年的一篇论文中,Seck 和 Nitaj [2] 通过 $N = p^r q^s$ 的模数增强了这一点。
在 RSA 中,我们有一个公钥 (e,N) 和一个私钥 (d,N)。 Pell 曲线增强了基本的加密和解密过程,而不是使用指数运算,例如:
C = M^e (mod N)
并且解密:
D = C^d (mod N)
我们使用三次 Pell 曲线运算。 Pell 曲线的基本方程是:
x³+ay³+a²z³-(3axyz) (mod N)
其中 N=p.q [1](或 N=p^r q^s [2])。该方法的核心是找到以下方程的解:
x³+ay³+a²z³-(3axyz) = 1 (mod N)
在理解了论文中的主要方法之后:

我决定尝试一下 ChatGPT 的代码解决方案:
编写一个基于三次
Pell 方程实现 RSA 变体的 Python 程序
不幸的是,它给了我垃圾代码,其中包含太多错误:
##!/usr/bin/env python3
"""
Cubic Pell RSA (核心算术的参考实现)
实现:
- 环 R = Z_N[α]/(α^3 - a)
- 元素表示为三元组 (x, y, z),表示 x + y*α + z*α^2
- 由 α^3 = a 引起的乘法
- “单位群”/范数-1 子群算术是 cubic-Pell RSA 变体使用的
- 类似 RSA 的幂运算:C = P^e, P = C^d
此文件有意将消息编码/解码保留为可插入函数,
因为不同的论文使用不同的编码和参数约束。
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Tuple, Optional
import secrets
import math
## -------------------------
## 基本数论实用程序
## -------------------------
def egcd(a: int, b: int) -> Tuple[int, int, int]:
"""扩展 GCD:返回 (g, x, y),其中 ax + by = g = gcd(a,b)。"""
x0, y0, x1, y1 = 1, 0, 0, 1
while b != 0:
q, a, b = a // b, b, a % b
x0, x1 = x1, x0 - q * x1
y0, y1 = y1, y0 - q * y1
return a, x0, y0
def modinv(a: int, n: int) -> int:
"""a 模 n 的模逆。"""
a %= n
g, x, _ = egcd(a, n)
if g != 1:
raise ValueError(f"no inverse: gcd({a},{n})={g}")
return x % n
def is_probable_prime(n: int, rounds: int = 40) -> bool:
"""Miller-Rabin 概率素性测试。"""
if n < 2:
return False
small_primes = [2,3,5,7,11,13,17,19,23,29,31,37]
for p in small_primes:
if n == p:
return True
if n % p == 0:
return False
# 写成 n-1 = d * 2^s,其中 d 为奇数
d = n - 1
s = 0
while d % 2 == 0:
s += 1
d //= 2
def check(a: int) -> bool:
x = pow(a, d, n)
if x == 1 or x == n - 1:
return True
for _ in range(s - 1):
x = (x * x) % n
if x == n - 1:
return True
return False
for _ in range(rounds):
a = secrets.randbelow(n - 3) + 2
if not check(a):
return False
return True
def random_prime(bits: int, *, mod: Optional[int] = None, rem: int = 0) -> int:
"""生成一个 `bits` 位的随机素数;可以选择强制执行 p % mod == rem。"""
if bits < 2:
raise ValueError("bits must be >= 2")
while True:
x = secrets.randbits(bits)
x |= (1 << (bits - 1)) # 确保最高位
x |= 1 # 确保为奇数
if mod is not None and x % mod != rem:
continue
if is_probable_prime(x):
return x
## -------------------------
## 三次 Pell 元素
## -------------------------
@dataclass(frozen=True)
class CubicPell:
"""
表示 Z_N[α]/(α^3 - a) 中的一个元素 u = x + y*α + z*α^2。
"""
x: int
y: int
z: int
N: int
a: int
def __post_init__(self):
object.__setattr__(self, "x", self.x % self.N)
object.__setattr__(self, "y", self.y % self.N)
object.__setattr__(self, "z", self.z % self.N)
object.__setattr__(self, "a", self.a % self.N)
@staticmethod
def one(N: int, a: int) -> "CubicPell":
return CubicPell(1, 0, 0, N, a)
def mul(self, other: "CubicPell") -> "CubicPell":
"""
使用 α^3=a 乘 (x1+y1α+z1α^2)(x2+y2α+z2α^2)。
使用:
α^3 = a
α^4 = aα
乘积系数:
x' = x1x2 + a(y1z2 + z1y2)
y' = x1y2 + y1x2 + a(z1z2)
z' = x1z2 + z1x2 + (y1y2)
"""
if self.N != other.N or self.a != other.a:
raise ValueError("mismatched modulus/parameter")
N = self.N
a = self.a
x1, y1, z1 = self.x, self.y, self.z
x2, y2, z2 = other.x, other.y, other.z
x = (x1 * x2 + a * (y1 * z2 + z1 * y2)) % N
y = (x1 * y2 + y1 * x2 + a * (z1 * z2)) % N
z = (x1 * z2 + z1 * x2 + (y1 * y2)) % N
return CubicPell(x, y, z, N, a)
def pow(self, e: int) -> "CubicPell":
"""通过平方和乘法进行快速求幂。"""
if e < 0:
raise ValueError("negative exponent not supported in this reference")
result = CubicPell.one(self.N, self.a)
base = self
k = e
while k:
if k & 1:
result = result.mul(base)
base = base.mul(base)
k >>= 1
return result
def norm(self) -> int:
"""
范数 N(x+yα+zα^2) = x^3 + a y^3 + a^2 z^3 - 3 a x y z (mod N)。
"""
N = self.N
a = self.a
x, y, z = self.x, self.y, self.z
return (pow(x, 3, N) + a * pow(y, 3, N) + (a * a % N) * pow(z, 3, N) - (3 * a * x * y * z)) % N
def is_norm_one(self) -> bool:
return self.norm() % self.N == 1 % self.N
## -------------------------
## 类似 Cubic Pell RSA 的密钥
## -------------------------
@dataclass
class PublicKey:
N: int
a: int
e: int
@dataclass
class PrivateKey:
N: int
a: int
d: int
# 可选地保留因子(方便测试;在实际使用中省略)
p: Optional[int] = None
q: Optional[int] = None
def psi_murru_saettone(p: int, q: int) -> int:
"""ψ(N) = (p^2 + p + 1)(q^2 + q + 1)。"""
return (p*p + p + 1) * (q*q + q + 1)
def keygen(bits: int = 512, *, a: Optional[int] = None, e: int = 65537) -> Tuple[PublicKey, PrivateKey]:
"""
使用 N=pq 和
ed ≡ 1 (mod (p^2+p+1)(q^2+q+1)) 为经典 cubic-Pell RSA 变体生成密钥。
注意:许多构造还需要对 a 进行条件限制(三次非剩余等)。
如果未提供,我们在此简单地随机选择一个,并强制执行 gcd(e, ψ)=1。
"""
if bits < 64:
raise ValueError("use >= 64 bits even for toy demos")
while True:
p = random_prime(bits, mod=3, rem=2) # 在某些三次剩余设置中很常见
q = random_prime(bits, mod=3, rem=2)
if p == q:
continue
N = p * q
psi = psi_murru_saettone(p, q)
if math.gcd(e, psi) != 1:
continue
d = modinv(e, psi)
if a is None:
# 在 Z_N^* 中均匀随机选择 a(避免 0 和小 gcd)
while True:
aa = secrets.randbelow(N - 2) + 2
if math.gcd(aa, N) == 1:
a_val = aa
break
else:
a_val = a % N
if a_val == 0 or math.gcd(a_val, N) != 1:
raise ValueError("a must be nonzero and coprime to N")
pk = PublicKey(N=N, a=a_val, e=e)
sk = PrivateKey(N=N, a=a_val, d=d, p=p, q=q)
return pk, sk
## -------------------------
## 加密/解密
## -------------------------
def encrypt_point(P: CubicPell, pk: PublicKey) -> CubicPell:
"""通过幂运算加密*曲线/环元素* P:C = P^e。"""
if P.N != pk.N or P.a != pk.a:
raise ValueError("point parameters mismatch")
return P.pow(pk.e)
def decrypt_point(C: CubicPell, sk: PrivateKey) -> CubicPell:
"""通过幂运算解密:P = C^d。"""
if C.N != sk.N or C.a != sk.a:
raise ValueError("cipher parameters mismatch")
return C.pow(sk.d)
## -------------------------
## 示例/玩具编码
## -------------------------
## 实际方案使用复杂的 Encode2Curve/DecodeFromCurve 风格的映射。
## 为了实现最小的可运行演示,我们将整数 m 编码为范数 1 元素
## 通过暴力破解一个小调整 t,直到 norm(x,y,z)=1(仅适用于玩具大小)。
## 请勿对实际大小使用暴力编码。
def encode_int_toy(m: int, N: int, a: int, max_tries: int = 100000) -> CubicPell:
"""
玩具编码:搜索 t,使得元素 (m, t, 1) 具有范数 1 mod N。
这纯粹是为了演示小 N(否则不可行)。
"""
m %= N
for _ in range(max_tries):
t = secrets.randbelow(N)
P = CubicPell(m, t, 1, N, a)
if P.is_norm_one():
return P
raise RuntimeError("toy encoding failed (try increasing max_tries or use smaller N)")
def decode_int_toy(P: CubicPell) -> int:
"""玩具解码:仅返回 x 坐标(仅适用于玩具编码器)。"""
return P.x
def demo():
# 使用微小的大小,以便玩具演示编码能够快速成功。
pk, sk = keygen(bits=80, e=65537)
print("N =", pk.N)
print("a =", pk.a)
print("e =", pk.e)
print("d =", sk.d)
message = 123456789
P = encode_int_toy(message, pk.N, pk.a)
assert P.is_norm_one()
C = encrypt_point(P, pk)
P2 = decrypt_point(C, sk)
assert P2.is_norm_one()
m2 = decode_int_toy(P2)
print("message =", message)
print("decoded =", m2)
print("OK =", (m2 == message))
if __name__ == "__main__":
demo()
我真的无法开始概述此代码的问题。 即使代码看起来很权威,但方法论是错误的。 即使你修复了所有基本的代码问题(例如缺少类的初始化),你也不会让它工作。 我花了几个小时修复数学和语法上的众多问题,但最终放弃了,因为它完全是垃圾!
因此,我从论文中找到了一些 Sage 代码 [ here ],并将其转换为纯 Python [ here ]:
## 基于此处的 Sage 代码:https://github.com/mseckept/schemesecknitaj/tree/main
from Crypto.Util.number import *
from Crypto import Random
import Crypto
from random import randint
from math import gcd
import libnum
def is_cube(a, p):
## 测试 a 是否为立方剩余 mod p,p 为素数
return power_mod(a, (p-1)//gcd(3, p-1), p) == 1
def is_square(x, p):
## 测试 x 是否为二次剩余 mod p,p 为素数
if (libnum.has_sqrtmod(x,{p: 1})):
return True
else:
return False
def rand_prime_congru_1mod3_and_3mod4(size):
## 随机生成一个素数 p,2 <= p < 2^size,其中 p = 1 mod 3 且 p = 3 mod 4
p = Crypto.Util.number.getPrime(size, randfunc=Crypto.Random.get_random_bytes)
while (p%3 != 1) or (p%4 != 3):
p = Crypto.Util.number.getPrime(size, randfunc=Crypto.Random.get_random_bytes)
return p
def rand_pq(size):
## 随机生成两个不同的素数 p, q = 1 mod 3 且 p, q = 3 mod 4
p = rand_prime_congru_1mod3_and_3mod4(size)
q = p
while q == p:
q = rand_prime_congru_1mod3_and_3mod4(size)
return (p, q)
## 使用 Hensel 引理解二次方程 ax^2+bx+c = 0 mod p**r 的解
def extend_solution_quadratic_equation_mod_pr(coeffs, sol, p, r):
## 使用 Hensel 引理将二次方程 ax^2+bx+c = 0 的解 *sol* 从 (mod p) 扩展到 (mod p^r)
a, b, c = coeffs; x0 = int(sol)
f = lambda x : a *x**2 + b*x + c
fprime = lambda x : 2*a*x + b
# 我们应用 Hensel 引理
x0 = int(sol)
for _ in range(2, r+1):
x0 = (x0 - f(x0)*inverse_mod(fprime(x0), p**r )) % p**r
return x0
def solver_quadratic_equation_mod_pr(coeffs, p, r):
## 使用 Hensel 引理解二次方程 ax^2+bx+c = 0 (mod p^r) 的解
a, b, c = coeffs;
if p%4 == 3:
x1 = Mod((-b- power_mod(Mod(b**2-4*a*c, p), (p+1)//4, p)) * inverse_mod(2*a, p), p)
x2 = Mod((-b + power_mod(Mod(b**2-4*a*c, p), (p+1)//4, p)) * inverse_mod(2*a, p), p)
else:
x1 = Mod((-b - Mod(b**2-4*a*c, p).sqrt()) * inverse_mod(2*a, p), p)
x2 = Mod((-b + Mod(b**2-4*a*c, p).sqrt()) * inverse_mod(2*a, p), p)
x1r = extend_solution_quadratic_equation_mod_pr(coeffs, x1, p, r)
x2r = extend_solution_quadratic_equation_mod_pr(coeffs, x2, p, r)
return (x1r, x2r)
def get_sol_with_cubic_pell_point_mod_pr_qs(C, p, q, r, s):
## 给定三次 Pell 方程上的一个点,确定 mod p^r 和 mod q^s 的解
xc, yc, zc = C; (a, b, c) = zc**3, yc**3-3*xc*yc*zc, xc**3 - 1
sol_mod_pr = solver_quadratic_equation_mod_pr((a, b, c), p, r)
sol_mod_qs = solver_quadratic_equation_mod_pr((a, b, c), q, s)
return (sol_mod_pr, sol_mod_qs)
def is_solution_mod_pr(C, sol, p, r):
## 测试 *sol* 是否为 xc^3 + x*yc^3 + x^2 * zc^3 - 3x*xc*yc*zc = 1 mod p^r 的解
xc, yc, zc = C; (a, b, c) = zc**3, yc**3-3*xc*yc*zc, xc**3 - 1
f = lambda x : a *x**2 + b*x + c
return (f(sol) % p**r ) == 0
def get_sols_with_ctheo(xp, yq, p, q, r, s):
## 确定 N=p^rq^s 的解 Y mod N,其中 Y = xp mod p^r 且 Y = yq mod q^s
N = p**r * q**s
X = (xp*q**s *inverse_mod(q**s , p**r ) + yq*p**r *inverse_mod(p**r , q**s ))
return X % N
def get_possible_a_values(C, p, q, r, s):
## 确定 a 的所有值,使得 xc^3 + a*yc^3 + a^2 * zc^3 - 3a*xc*yc*zc = 1 mod N,其中 N = p^rq^s
N = p**r * q**s; (xc, yc, zc) = C
sol_mod_pr, sol_mod_qs = get_sol_with_cubic_pell_point_mod_pr_qs(C, p, q, r, s)
a1 = get_sols_with_ctheo(sol_mod_pr[0], sol_mod_qs[0], p, q, r, s)
a2 = get_sols_with_ctheo(sol_mod_pr[1], sol_mod_qs[1], p, q, r, s)
a3 = get_sols_with_ctheo(sol_mod_pr[0], sol_mod_qs[1], p, q, r, s)
a4 = get_sols_with_ctheo(sol_mod_pr[1], sol_mod_qs[0], p, q, r, s)
possible_a = (a1, a2, a3, a4)
return possible_a
def inverse_mod(a,b):
return(pow(a,-1,b))
def Mod(a,b):
return(a%b)
def power_mod(a,b,N):
return(pow(a,b,N))
## 三次 Pell 曲线的算术运算
def add(pt1, pt2, a, N):
## 在 Z/NZ 中,将三次 Pell 曲线 C_a(N) = x^3+ay^3+a^2z^3-3axyz = 1 上的两个点 pt1 和 pt2 相加
(x1, y1, z1) = pt1; (x2, y2, z2) = pt2
x3 = (x1*x2 + a*(y2*z1 + y1*z2)) % N
y3 = (x2*y1 + x1*y2 + a*z1*z2) % N
z3 = (y1*y2 + x2*z1 + x1*z2) % N
return (x3, y3, z3)
def exponent(pt, e, a, N):
## 在三次 Pell 曲线 C_a(N) = x^3+ay^3+a^2z^3-3axyz = 1 中,从左到右进行标量乘法
(x1, y1, z1) = pt; (x2, y2, z2) = (1, 0, 0)
for bit in bin(e)[2:]:
(x2, y2, z2) = add((x2, y2, z2), (x2, y2, z2), a, N)
if bit == '1':
(x2, y2, z2) = add((x2, y2, z2), (x1, y1, z1), a, N)
return (x2, y2, z2)
def keygen(size, r, s):
p, q = rand_pq(size)
N = p**r * q**s
ordn = p*q*(p**2+p+1)*(q**2+q+1)*(p - 1)**2*(q - 1)**2
e = randint(1, N)
while gcd(e, ordn) != 1:
e = randint(1, N)
d1 = inverse_mod(e, p**(2*(r-1))*q**(2*(s-1))*(p**2+p+1)*(q**2+q+1))
d2 = inverse_mod(e, p**(2*(r-1))*q**(2*(s-1))*(p - 1)**2*(q - 1)**2)
d3 = inverse_mod(e, p**(2*(r-1))*q**(2*(s-1))*(p**2+p+1)*(q - 1)**2)
d4 = inverse_mod(e, p**(2*(r-1))*q**(2*(s-1))*(p - 1)**2*(q**1+q+1))
return (N, e), (p, q, (r, s), (d1, d2, d3, d4))
def encipher(m, pk):
(N, e) = pk; (xm, ym) = m
a = ((1 - xm**3) * inverse_mod(ym**3, N)) % N
C = exponent((xm, ym, 0), e, a, N)
return C
def decipher(ctx, sk):
(p, q, (r, s), (d1, d2, d3, d4)) = sk
N = p**r *q**s
possible_a = get_possible_a_values(ctx, p, q, r, s)
for a in possible_a:
if (not is_cube(a, p)) and (not is_cube(a, q)):
d = d1
elif is_cube(a, p) and is_cube(a, q):
d = d2
elif (not is_cube(a, p)) and is_cube(a, q):
d = d3
else:
d = d4
(xm, ym, zm) = exponent(ctx, d, a, N)
if zm == 0:
return (xm, ym)
r, s = 2, 3
msg="Hello"
bits=60
if (len(sys.argv)>1):
msg=str(sys.argv[1])
if (len(sys.argv)>2):
bits=int(sys.argv[2])
pk, sk = keygen(bits, r, s)
N = pk[0]
print(f"pk = {pk}\nsk = {sk}\n")
print(f"Bits = {bits}\nMessage = {msg}\n")
m= bytes_to_long(msg.encode('utf-8'))
m = (m, m);
print(f"\nMessage: {m}\n")
ctx = encipher(m, pk);
print(f"Cipher = {ctx}\n")
ptx = decipher(ctx, sk=sk);
print(f"Decipher= {ptx}\n")
print ("\n==解密后的消息===\nmsg=%s" %long_to_bytes(ptx[0]) )
当我使用 “Hello” 和 60 位消息运行时,我们得到了正确的结果 [ here ]:
pk = (1080928831331135013779446457169419532527431370087255033221341860232168568083200088298968271, 839318919357785876635889999468890161415510066915672848798116175735363773849202484382355727)
sk = (848652422799117451, 1144930513775975191, (2, 3), (300195589744604718162001536858972385437891337444414027571033863024674871239379084432567260146654173021529193784230753898560163461195579737766556175825451362007554735939673785303383, 984829125046219908975956622365077885396089251496387714601240798419375876355803485567553293047859329797113199413345859391395499614544656634154761849589025472160237605937157390439663, 106890682378044000985457050811691152002571563022688174143901212098399223677253105864658595329691625764223465951446088797750147128019601693436000919243413417127766262895098848537563, 1241964101001002944870754602380875718664425735833398559540944784530123751044201115593823554376019993620191152959498221676597950534828743348260213445237809115267163))
Bits = 60
Message = Hello
Message: (310939249775, 310939249775)
Cipher = (469485677348104705702147635846339994152100534842645981803425124585832145208328242075114880, 881871471207622001832864371562012465373436593613927667095664977168039698476294479248910587, 628516305167444377466415415147842177803171283952167154498329097616649062567788677885702272)
Decipher= (310939249775, 310939249775)
==解密后的消息===
msg=b'Hello'
你可以在这里尝试代码:
https://asecuritysite.com/rsa/rsa_pell
[1] Murru, N., & Saettone, F. M. (2017, September). A novel
- 原文链接: medium.com/asecuritysite...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!