一、椭圆曲线的简单介绍
首先介绍下一个著名的方程,魏尔施特拉斯方程(Weierstrass equation),方程形式如下:
椭圆曲线一个重要的应用是已知两个点,求出第三个点,我们以下面的例子为例。
例1:已知椭圆曲线E的方程 ,显然的,点P=(7,16)、Q=(1,2)是位于该椭圆曲线上的。那么如何求得第三个点R使得该点既在PQ直线上,也在E上呢?
传统的做法:将直线方程求出来,用X表示Y,并且代入曲线E的方程上,则可以求出解。得出解R=(-23/9,170/27)。
现在我们先定义一个加法运算。
通过例1,可以知道,已知两个点P、Q,我们可以求得第三个点R的坐标,我们现在定义运算,P
Q=R’,R‘和R是关于x轴对称的。可能有人会好奇?为什么会这样定义?而不是直接定义P
Q=R。对这个问题感兴趣的朋友可以去学习下抽象代数的知识(Mordell-Weil定理:椭圆曲线上有理点构成的群是有限生成的。)通过下面的例2,我们可以很快的了解该定理。
例2:椭圆曲线上,仅有16个整 点:(-2,3),(-1,4),(2,5),(4,9),(8,23),(43,282),(52,375),(5234,378661)。以及它们关于x轴的对称点,而其上所有的有理点可以由(-2,3),(2,5)通过群上的加法生成。
例2应该很明显的告诉了我们,为什么要这样定义椭圆曲线上的加法了。
二、椭圆曲线上的运算规则
(为了方便,我们将上面的符号简写成+)对于椭圆曲线E上加法运算,其性质有如下:
1、(单位元)P+O=O+P,对于任意的P属于E;
2、(可逆)P+(-P)=O,对于任意P属于E;
3、(结合律)(P+Q)+R=P+(Q+R),对于任意的P、Q和R属于E;
4、(交换律)P+Q=Q+R,对于任意的P、Q属于E。
换句话说,(E,+)构成一个阿贝尔群(Abelian group)。
现在给出加法的具体计算的方法。
假设E上有两点,P=(,
),Q=(
,
)。那么如何计算P+Q。
1、如果P=O,则P+Q=Q;
2、如果=
,且
,则P+Q=O;
3、
三、在有限域上的椭圆曲线
前面我们说过椭圆曲线的计算,但是是基于实数域上的,要想将其引用到密码学中,那么我们考虑的将是有限域上的曲线了。将通过一个例子来引入。
例3:考虑椭圆曲线(在有限域F13上)。从0到12,我们依次代入,可以求得所有在椭圆曲线E上的点{O,(1,5),(1,8),(2,3),(2,10),(9,6),(9,7),(12,2),(12,11)}。下面我们来计算一个例子,P=(9,7),Q=(1,8)。
1、计算P+Q。
解:在有限域中的公式依旧适用,就是运算略有不同(对抽象代数中的运算不清楚的,可以去学习下抽象代数中的相关知识)。计算 (最后一步可能部分读者不能理解?Why?一句话概括就是5的逆元是8,
),然后计算出
,
。所以P+Q=(2,10)。
2、计算2P
计算可得,从而可得2P=(9,6),步骤与上面几乎一致,有兴趣的读者可以验证下。下面我们给出一个计算有限域问题的JAVA代码。
/**
* 该代码只是一个示例
* */
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.*;
public class PplusQOverFiniteFields {
private BigInteger A = null;
private BigInteger B = null;
private BigInteger Px = null;
private BigInteger Py = null;
private BigInteger Qx = null;
private BigInteger Qy = null;
private BigInteger Zx = null;
private BigInteger Zy = null;
private static BigInteger x = null;
private static BigInteger y = null;
private BigInteger P = null;// 需要设置
private boolean isOutputProcedure = true;
public void setisOutputProcedure(boolean t) {
this.isOutputProcedure = false;
}
public PplusQOverFiniteFields(BigInteger A, BigInteger B, BigInteger p) {
this.A = A;
this.B = B;
this.P = p;
}
public void setP(BigInteger x, BigInteger y) {
this.Px = x;
this.Py = y;
}
public void setQ(BigInteger x, BigInteger y) {
this.Qx = x;
this.Qy = y;
}
public BigInteger[] caclu2P() {
BigInteger[] temp = new BigInteger[2];
this.Qx = this.Px;
this.Qy = this.Py;
this.caculZ();
temp[0] = Zx;
temp[1] = Zy;
return temp;
}
public void input() {
System.out.println("请输入Weierstrass方程的系数A,B:");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
A = new BigInteger(br.readLine());
B = new BigInteger(br.readLine());
System.out.println("请输入P点的坐标:");
Px = new BigInteger(br.readLine());
Py = new BigInteger(br.readLine());
System.out.println("请输入Q点的坐标:");
Qx = new BigInteger(br.readLine());
Qy = new BigInteger(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
public BigInteger[] caculNP(int N) {
this.Qx = Px;
this.Qy = Py;
BigInteger[] res = new BigInteger[2];
BigInteger[] temp = new BigInteger[2];
res[0] = BigInteger.ZERO;
res[1] = BigInteger.ZERO;
while (N != 0) {
temp = caclu2P();
if (N % 2 == 1) {
PplusQOverFiniteFields pqoff = new PplusQOverFiniteFields(A, B,
P);
pqoff.setisOutputProcedure(false);
pqoff.setP(res[0], res[1]);
pqoff.setQ(Qx, Qy);
res = pqoff.caculZ();
}
N = N / 2;
setP(temp[0], temp[1]);
}
return res;
}
public BigInteger[] caculZ() {
BigInteger[] temp = new BigInteger[2];
BigInteger lanbuta = null;
if ((Qx.signum() == 0 && Qy.signum() == 0)) {
temp[0] = Px;
temp[1] = Py;
return temp;
}
if (Px.signum() == 0 && Py.signum() == 0) {
temp[0] = Qx;
temp[1] = Qy;
return temp;
}
if (!(Qx.equals(Px) && (Qy.equals(Py)))) {
lanbuta = (Qy.subtract(Py).mod(P)).multiply(
inverses_modulo(Qx.subtract(Px), P)).mod(P);
} else {
lanbuta = ((new BigInteger("3").multiply(Px).multiply(Px)).add(A)
.mod(P)).multiply(
inverses_modulo(new BigInteger("2").multiply(Py), P))
.mod(P);
}
Zx = (lanbuta.multiply(lanbuta)).subtract(Px).subtract(Qx).mod(P);
Zy = (lanbuta.multiply((Qx.subtract(Zx)))).subtract(Qy).mod(P);
if (isOutputProcedure) {
System.out.println("Q" + "(" + Qx + "," + Qy + ")" + "+" + "P"
+ "(" + Px + "," + Py + ")" + "=" + "Z" + "(" + Zx + ","
+ Zy + ")");
}
temp[0] = Zx;
temp[1] = Zy;
return temp;
}
private static BigInteger exgcd(BigInteger a, BigInteger b) {
if (b.equals(new BigInteger("0"))) {
x = new BigInteger("1");
y = new BigInteger("0");
return a;
}
BigInteger r = exgcd(b, a.mod(b));
BigInteger t = x;
x = y;
y = t.subtract(a.divide(b).multiply(y));
return r;
}
public BigInteger inverses_modulo(BigInteger a, BigInteger b) {
exgcd(a, b);
while (x.signum() == -1)
x = x.add(b);
return x;
}
public void clear() {
Px = null;
Py = null;
Qx = null;
Qy = null;
Zx = null;
Zy = null;
x = null;
y = null;
}
public static void main(String args[]) {
System.out.println("有限域13上的运算");
PplusQOverFiniteFields test_1 = new PplusQOverFiniteFields(
new BigInteger("3"), new BigInteger("8"), new BigInteger("13"));
test_1.setP(new BigInteger("9"), new BigInteger("7"));
test_1.setQ(new BigInteger("1"), new BigInteger("8"));
test_1.caculZ();
test_1.caclu2P();
test_1.clear();
}
}
四、倍加算法(The Double-and-Add Algorithm)
上面我们提到了Q=2P的计算,但是当计算Q=nP的时候,如果是在一个大素数下有限域且n的值较大,那么Q=nP的计算将会十分复杂。这里我们介绍一种算法。以例4来介绍该算法。
例4:考虑椭圆曲线E:,在有限域F(3623)下,P=(6,730),下面我们计算Q=947P。
传统的做法是不停的+P,这样的话,需要执行947-1次加法,但是这样的算法我们是不提倡的,如果n足够大,那么该算法的执行时间将十分长。因此才出现了The Double-and-Add算法。
首先将947写成二进制的表达形式。这样只需要9次双倍运算和6次加法运算即可得出结果。
具体计算结果可参见下表:
表1:计算947*(6,730) 在椭圆曲线E:在F(3623)上
下面给出该算法的JAVA测试代码(里面用到的类在上面的代码中)。
import java.math.BigInteger;
public class NPcacultest {
public static void main(String args[]) {
PplusQOverFiniteFields test_2 = new PplusQOverFiniteFields(
new BigInteger("14"), new BigInteger("19"), new BigInteger(
"3623"));
test_2.setP(new BigInteger("6"), new BigInteger("730"));
BigInteger[] res = new BigInteger[2];
res = test_2.caculNP(947);
System.out.println(res[0] + " " + res[1]);
}
}
五、其它应用
有限域上椭圆曲线可以应用于Diffie-Hellman密钥交换和ElGamal公钥体制(椭圆曲线版本,与一般的ElGamal略有不同)。
有兴趣的读者可以参考《An Introduction to Mathematical Cryptography》第五章,编者Jeffrey Hoffstein 等。
下面给出这两个的体制的JAVA代码。
(1)Diffie-Hellman密钥交换
/**
* Diffie-Hellman密钥交换的一种简单实现
* */
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
public class Diffie_Hellman {
static BigInteger P, A, B;
public static void main(String args[]) throws NumberFormatException,
IOException {
PplusQOverFiniteFields ppp = new PplusQOverFiniteFields(new BigInteger(
"324"), new BigInteger("1287"), new BigInteger("3851"));
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int Alice = Integer.parseInt(br.readLine());
int Bob = Integer.parseInt(br.readLine());
BigInteger[] res1 = new BigInteger[2];
BigInteger[] res2 = new BigInteger[2];
BigInteger[] res3 = new BigInteger[2];
ppp.setP(new BigInteger("920"), new BigInteger("303"));
ppp.setisOutputProcedure(false);//不输出中间过程
res1=ppp.caculNP(Alice);
System.out.println("Alice计算得到的结果:("+res1[0]+","+res1[1]+")");
ppp.setP(new BigInteger("920"), new BigInteger("303"));
res2=ppp.caculNP(Bob);
System.out.println("Bob计算得到的结果:("+res2[0]+","+res2[1]+")");
//他们分享的密钥如下
ppp.setP(res2[0],res2[1]);
res3=ppp.caculNP(Alice);
System.out.println("Alice计算得到的秘密:("+res3[0]+","+res3[1]+")");
ppp.setP(res1[0],res1[1]);
res3=ppp.caculNP(Bob);
System.out.println("Bob计算得到的秘密:("+res3[0]+","+res3[1]+")");
System.out.println("分享秘密成功!");
}
}
下面为上述代码的执行结果:
(2)ElGamal椭圆曲线版本的实现
/**
* Elgamal椭圆曲线版本的实现
* */
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.util.Random;
public class ElGamalTest {
static BigInteger P, A, B,g;
public static void getRandomP() {
int alpha=50;
Random r = new Random();
BigInteger q = null;
while (true) {
q = BigInteger.probablePrime(alpha, r);
if (q.bitLength() != alpha)
continue;
if (q.isProbablePrime(10)) // if q is prime ,contiune
{
P = q.multiply(new BigInteger("2")).add(BigInteger.ONE);
if (P.isProbablePrime(10)) // if p is prime,then break
break;
}
}
while (true) {
g = BigInteger.probablePrime(P.bitLength() - 1, r);
if (!g.modPow(BigInteger.ONE, P).equals(BigInteger.ONE)
&& !g.modPow(q, P).equals(BigInteger.ONE)) {
break;
}
}
}
public static void main(String args[]) throws NumberFormatException,
IOException {
getRandomP();
System.out.println("可信方随机得到的大素数P:"+P);
PplusQOverFiniteFields ppp = new PplusQOverFiniteFields(new BigInteger(
"324"), new BigInteger("1287"), P);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入Alice的私钥:");
int Alice = Integer.parseInt(br.readLine());
ppp.setP(new BigInteger("920"), new BigInteger("303"));
ppp.setisOutputProcedure(false);// 不输出中间过程
BigInteger[] res1 = new BigInteger[2];
res1 = ppp.caculNP(Alice);
System.out
.println("Alice计算得到的结果(公钥):(" + res1[0] + "," + res1[1] + ")");
ppp.setP(new BigInteger("920"), new BigInteger("303"));
int BobEphemeral = (int) (Math.random() * 10000);
BigInteger[] C1 = ppp.caculNP(BobEphemeral);
System.out.println("请输入明文:");
BigInteger PlainText = new BigInteger(br.readLine().getBytes());
BigInteger CopyOfPlainText = PlainText;
ppp.setP(res1[0], res1[1]);
BigInteger[] CTemp = ppp.caculNP(BobEphemeral);
ppp.setP(CTemp[0], CTemp[1]);
ppp.setQ(PlainText, CopyOfPlainText);
BigInteger[] C2 = ppp.caculZ();
System.out.println("密文:((" + C1[0] + "," + C1[1] + "),(" + C2[0] + ","
+ C2[1] + "))");
ppp.setP(C1[0], C1[1]);
CTemp=ppp.caculNP(Alice);
ppp.setP(C2[0], C2[1]);
ppp.setQ(CTemp[0], CTemp[1].negate());
BigInteger[] res=ppp.caculZ();
String mingwen =new String(res[0].toByteArray());
System.out.println(mingwen);
}
}
执行结果如下:
笔者水平有限,难免有错误,欢迎交流!