L 记事本

ECC椭圆曲线加密

   #ECC #加密 #模逆元 #素数 

ECDH密钥分发算法

满足结合律交换律,根据几何图像,交换律显而易见,结合律未找到证明。 下面大写表示曲线上的点,小写表示数. 表示 PQ连线和曲线另外一个交点$R^{‘}$的x轴对称点R

\(P + Q = R\) 表示,P点切线,与曲线交点的x轴对称点 \(P + P + P = 2 \cdot P + P = 3P\) 这里为了满足交换群的定义,加法需要一个单位元,需要定义一个无穷远点 $O$, $y = \infty$ \(O + P = P \\ P + (-P) = O\) 已知 $P$为公钥, $s$为私钥, $G$为公共曲线参数 由$s$ 和 $G$ 求 $P$ 很容易, 由$P$ 和 $G$ 求 $s$ 很困难, \(P_a =s_a \cdot G\) \(P_b = s_b \cdot G\) A 和 B 交换密钥流程 B 只用将 自己的私钥 $s_b$ 乘 A的公钥 获得 $K_1$ A 只用将 自己的私钥 $s_a$ 乘 B的公钥 获得 $K_2$ \(\begin{aligned} K_1 &=s_b \cdot P_a\\ &= s_b \cdot s_a \cdot G\\ &= s_a \cdot s_b \cdot G\\ &= s_a \cdot (s_b \cdot G)\\ &= s_a \cdot P_b \\ &= K_2 \end{aligned}\) 这样$K_1$ , $K_2$相等,就完成了交换.

两对密钥 公钥私钥交叉相乘结果相等 相乘是对应的椭圆曲线相乘法则

Ecc 加密过程

加密过程实际上是用的 ecdh 随机生成一个 密钥 $K_r$,然后在用对称加密算法 例如 AES 等使用此 $K_r$ 加密。 已知密钥对 $P = s \cdot G$ (P公钥,s私钥) 随机生成一个密钥对 $P_r$ $s_r$ 对应的加密 \(K_r= s_r \cdot P\)

  • 然后使用AES , $K_r$ 加密 明文M获得密文C,把临时公钥 ($P_r$ C)作为结果返回
  • 同时为了解密时候能校验是否解密成功,需要返回一个校验量 MAC, 可以是 HMAC($P_r$,$K_r$,C) 如果AES加密是非密码本模式,需要返回初始化向量IV. $K_r$ 是一个点,$(X,Y)$坐标编码成可加密的key,一般只需要 X坐标,因为Y坐标可以通过X坐标计算出来

    解密过程

    从加密方获得 已知密文 $C$,和临时公钥 $P_r$, 校验参数MAC 自己保密的私钥$s$

    1. 下面计算 对应的AES 密钥, \(K_{2r} = s \cdot P_r\) 参考 ECCDH密钥分发算法 ,两对ECC密钥的公钥私钥交叉相乘结果是一样的. $K_{2r} = K_r$ 证明 \(\begin{aligned} K_ {2r} &= s \cdot P_r \\ &= s \cdot s_r \cdot G \\&= s \cdot G \cdot s_r \\&= P \cdot s_r \\&= K_r\end{aligned}\)
  1. 验证是否是结果是否正确 计算$Mac2 = HAMC(P_r,K_{2r},C)$ $Mac2 == MAC$ 说明数据正确,反之报错,终止解密
  2. 通过 $K_2$ 进行对应的 AES 解密,进行解密获得明文$TEXT$

    如果需要初始化向量IV,也应该从加密方直接获取,加密方要把 iv mac 密文$C$, 临时公钥$P_r$ 一起返回

这里有一个在线加解密的网站实现了上面的ECC 加密过程, 采用 AES-256-CBC,返回值里面有初始化向量

网站的提供的默认公钥 BAjDe3ig3ZKh5xO0+aA5Zuakz2ukRfe0M3Jzg8nVFn/pnmD58qBX5Iwxk/IUNTm6TVZv7MZcXOcx0KzKORTMD4U=

私钥 fPtrf9iBkJTyEuCKbJuAxRoJEZJFOqPueQZ0mtsOC34=
曲线 secp256k1

椭圆方程的求解

\(y^2 = x^3 + ax + b \tag{1}\) \(y - y_p = k (x-x_p) \tag{2}\) 其中 \(\begin{cases} k = \frac{y_p-y_q}{x_p-x_q} & \text{P != Q;连线的斜率} \\[2ex] k = \frac{3x_p^2 + a}{2y_p} & \text{P = Q;表示在该点切线的斜率,也就是导数} \\[2ex] \end{cases}\) 将 2 带入 1中,根据得到 ,根据根与系数关系(韦达定理)

\(\begin{aligned} & a_nX^n + b_{n-1}X^{n-1} + ... + a_0 = 0 \\ & x_0 + x_1+ ... + x_n = \frac{a_{n-1} }{a_n} ; &\text{*}\\ & x_0 \cdot x_1 \cdot ... \cdot x_n = (-1)^n \frac{a_0}{a_n}\\[2ex] \end{aligned}\) 得到 \(\begin{cases} x_R = k^2 -x_P - x_Q\\ y_R = -y_P +k(x_P - x_R) \end{cases}\)

模素数P点运算

上面的说明是实数上的运算, 会有小数的问题,计算机处理起来有困难;采用有限域(模素数)上加减乘除,可以很好的解决这个问题,所有的$x,y$都是整数,并且根据公共点$G$生成足够多的点, 上面的公式进一步变成 \(\begin{cases} x_R = k^2 -x_P - x_Q \pmod p \\ y_R = -y_P +k(x_P - x_R) \pmod p \end{cases}\) \(\begin{cases} k = (y_p-y_q)\cdot(x_p-x_q)^{-1} \pmod p \\[2ex] k =(3x_p^2 + a)\cdot(2y_p)^{-1} \pmod p \\[2ex] \end{cases}\) 模素数P算法获取到的点已经绝大部分不再曲线了,全部分布在 [0 ,p-1] 这个正方形区间内.

模逆元

\(ab = 1 \pmod p\) ab 互为模逆元, 这里 由于p 取的素数, a,b都小于p,那么a,b和p都互质数, 模逆元必定存在(充要条件.a 和p 互质,那么,b存在); 快速求模逆元,可以用扩展欧几里得算法,具体方式就是辗转相除,细节可以看百科

后续博文更新:

判断一个公钥是否合法

实现代码

  1. 判断是否满足方程
  2. 判断该公钥 $P$ 乘以生成元的阶$n$ 由于$n$是一个素数,任意元素的阶都是 $n$ ,根据这个性质可以判断

更多详见此问答

大结局

这样,ECC加密过程基本清楚. 实际上,很多ECC文章写的ecc加密过程是这样 已知公钥 P,私钥s 消息 M,(也就是X,坐标,y坐标根据方程算出来); 加密过程 $(C_m,C_p)$ 为加密结果 $r$为随机数.作为临时私钥 $C_p$临时公钥,和加密结果一起返回 \(C_m = M + r \cdot P \\ C_p = r \cdot G\\\) 解密过程,知道 C 和 S \(\begin{aligned} M &= C - s \cdot C_p \\ &= M + r \cdot P - s \cdot C_p \\ &= M + (r\cdot s \cdot G - s \cdot r \cdot G)\\ &= M \end{aligned}\) 减法表示逆元,对于任意 点$P + (-P) = O$ 椭圆曲线零元是 无穷远处的点.作图可知$P$点逆元表示P的x轴对称点 $P^{‘}$ 这种方式有一个缺点,就是计算过程比较麻烦,而且也是用到了dh秘钥交换的方式。 所以很多加密实现都是直接用 ecdh方式商定一个共同的随机私钥,再通过AES等对称加密方式进行加密,从而加速运算。

推荐资料

零知识证明 - 椭圆曲线基础 ## Menezes-Vanstone 密码体制 利用ecdh密钥交换,生成交换的密钥 $P$ 对于明文分拆,加密过程 \(m = (m1,m2) \\ c1 = m1 * P.x \pmod P \\ c2 = m1 * P.x \pmod P \\\) 解密过程 \(d1 = c1 * P.x^{-1} \pmod P \\ d2 = c2 * P.x^{-1} \pmod P \\\) 利用扩展欧几里得算法能快速求模p逆元.



const exGcd = require("./exGcd.js");
const isPrime = require("./miller-rabin.js");
const eulerJudge = require("./euler-judge").eulerJudge
const cipolla_sqrt = require("./cipolla").cipolla_sqrt;
const ZeroPoint = { x: 0, y: -1 };
const a = 0n;
const  b = 7n 
const MaxCofactor = 8;
function exGcd_Num(a, b) {
  return Number(exGcd(BigInt(a), BigInt(b)));
}
function isSamePoint(A,B){
  return A.x == B.x && A.y == B.y;
}
function getRandomPrime(){
  while(1){
    var m = 65536000 + Math.floor(Math.random() * 100000000);
    if (m > 2 && isPrime(m) ) {
      console.log('random prime:',m);
      return m ;
    }
  }   
}
class ECC{
  
  constructor(){
    /*
    this.Prime = 9319;
    // this.h = 1;
    // this.CurveOrder = 9127;
    /// order = curveorder / h
    this.Order = 9127;
    this.G = { x: 5762, y: 1167 };
    */
    this.Prime = 6741313;
    // this.h = 1;
    // this.CurveOrder = 6742804;
    /// order = curveorder / h
    this.Order = 1685701;
    this.G = { x: 2331692, y: 6255336 };

    this.Prime = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2Fn

    this.Order =  0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n
    var x = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798n;
    var y = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n
    this.G = {x,y};

  }


  logN(a,msg){
    console.log('' + msg,a.toString(16))
  }
  PointAdd(P, Q) {
    const Prime = this.Prime;
    if (P.y < 0) {
      return Q;
    }
    if (Q.y < 0) {
      return P;
    }

    var k = 0;
    if (P.x == Q.x && P.y == Q.y) {
      /// 特殊点,斜率无穷大
      if (P.y == 0) {
        return ZeroPoint;
      }

      this.logN(0,'----------------------------')
  
      var tmp = exGcd(2n * P.y, Prime);
      k = ((3n * ((P.x * P.x) % Prime) + a) * tmp) % Prime;

      if (k < 0) {
        k += Prime;
      }
     
    } else {
      if (P.x == Q.x) {
        return ZeroPoint;
      }

      var tmpz = (P.x - Q.x) % Prime;
      if (tmpz < 0) {
        tmpz += Prime;
      }
      var tmp = exGcd(BigInt(tmpz), BigInt(Prime));

      var tmpDy = ((P.y - Q.y) % Prime) + Prime;
      k = (tmpDy * tmp) % Prime;
      if (k < 0) {
        k += Prime;
      }

      // console.log("px",P.x.toString(16));
      // console.log("py",P.y.toString(16));
      // console.log("qx",Q.x.toString(16));
      // console.log("qy",Q.y.toString(16));

      // console.log("k",k.toString(16));
    }

    const x = (((k * k - P.x - Q.x) % Prime) + Prime) % Prime;
    const y = ((((-P.y + k * (P.x - x)) % Prime) + Prime) % Prime);

    // this.logN(k * k - P.x - Q.x,"MMM")
    // this.logN('-------------')
    // this.logN(P.x,"P.x")
    // this.logN(Q.x,"Q.x")
    // this.logN(k * k ,"kk")
    // console.log("k",k.toString(16));
    // console.log("xx",x.toString(16));
    // console.log("yy",y.toString(16));
    return { x, y };
  }

  PointTimes(P, s){
    var s1 = s % (this.Order );
    return this._PointTimes(P,s1);
  }
  _PointTimes(P, s) {
    if (s == 0) {
      return ZeroPoint;
    }
    if (s == 1) {
      return P;
    }
    if (s == 2) {
      return this.PointAdd(P, P);
    }

    var s_0 = s % 2n;
    var s_2 = (s - s_0) / 2n;
    var P2 = this._PointTimes(P, s_2);
    var result = this.PointAdd(P2, P2);
    if (s_0) {
      result = this.PointAdd(result, P);
    }
    // console.log('times',P,result,s)
    return result;
  }

  genKeyPair(key){
    if(key == 0 || key >= this.Order){
      throw `key must  be  between [0,  ${this.Order}]`
      return
    }
    var keyout = key ? key : Math.floor(Math.random() * this.Order)
    while(keyout == 0){
      keyout =  Math.floor(Math.random() * this.Order)
    }
    return {
      private:keyout,
      public:this.PointTimes(this.G,keyout),
      curve:{
        P:this.Prime,
        Order:this.Order,
        G:this.G
      }
    }
  }

  ecdh(privatekeyA,publicKeyOfB){
    return this.PointTimes(publicKeyOfB,privatekeyA);
  }


  // return <R.x, S>
  SingMsg(msgHash,PriKey){
    const Order = this.Order;
    const G = this.G;
    if (PriKey == 0) {
      throw "privateKey cant  be 0"
    }

    var S = 0;
    do{
      var randK = Math.floor(Math.random() * Order);
      if (randK == 0) {
        randK = 1;
      }


      var R = this.PointTimes(G, randK);
      var k1 = exGcd_Num(randK, Order );
      S = k1 * ((msgHash + (PriKey * R.x % Order)) % Order) % Order ;
    }while(  S == 0);

    
    return [R.x,S];
  }

  VerifySign(msgHash,SignInfo,PubKey){
    const Order = this.Order;
    const G = this.G;
    var Rx = SignInfo[0];
    var S = SignInfo[1];
      
    var S1 = exGcd_Num(S, Order );
    var CheckR = this.PointAdd(
      this.PointTimes(G, (msgHash * S1) ),
      this.PointTimes(PubKey, (Rx * S1) )
    );
    return CheckR.x == Rx;
  }
  
  _findGeneratorG(Prime){
   // 找到素数阶生成元
   if(1){
    /**
     * 计算曲线的阶应该使用 schoof 算法,以后再更新..
     */
    var OrderOfCurve =  1;
    for (let x = 0; x < Prime; x++) {

      var a = ((x * x % Prime)* x + b) % Prime ;
      var exist =  eulerJudge(a,Prime);
      if (exist ) {
        /// a== 0 ,那么 y = 0,只有一个点
        OrderOfCurve += (a == 0 ? 1 : 2);
      }
    }
  
      
      
    /// 分解 曲线的阶 n = r * p ^k, p 是素数,那么肯定存在 阶为p ^k的子群, 西罗定理 , 这里k 一般是 1 ,(r,p)互质

    var h =  1;
    var tmpOrder = -1;
    var isSucc = 0;;
    /// h 很小,保证 order 很大
    for (let r = 1; r < MaxCofactor ; r++) {
      if (OrderOfCurve % r == 0) {
        var order0 = OrderOfCurve/r;
        if (isPrime(order0)) {
          tmpOrder = order0;
          h  = r;
          isSucc = 1;
          break;
        }
      }
    }


    if(h == tmpOrder){
      tmpOrder *= h;
      h = 1;
    }

    
      
    console.log('\n\n\n-----------');
    console.log("Prime ",Prime);
    console.log("curve Order ",OrderOfCurve)
    console.log("group Order ",tmpOrder)
    console.log("h ",h)
      

    if(isSucc == 0 ){
      return 0
    }

    this.Order = tmpOrder;
    this.Prime = Prime;
    var G ;
    do{

      var tmpG = {x:0,y:-1}
      do {
        tmpG.x = Math.floor(Math.random() * Prime);
        // -1 表示无解

        var tmp = Number(BigInt(tmpG.x) * BigInt(tmpG.x)  % BigInt(Prime) * BigInt(tmpG.x) % BigInt(Prime)) + b
        tmpG.y  = cipolla_sqrt(tmp, Prime );
        console.log(tmpG);
      } while (tmpG.y < 0);

      G = this.PointTimes(tmpG,h);
    }while(G.y < 0 )
  
    console.log("generator of G ",tmpG);
    console.log("G ",G);
    var checkG = this._PointTimes(G,tmpOrder)
    console.log('check G',checkG.y < 0 ? '✅' : '❌' )
    console.log('-----------\n\n\n');
    if(checkG.y >=0){
      throw "order of curve is wrong"
    }
    
    this.G = G ;
    return 1;

  }
  }

  findNewGroup(){
    var result = 0;
    do {
      var Prime =  getRandomPrime();
      result = this._findGeneratorG(Prime);
    } while (result == 0);
  }

  /**
   * Menezes-Vanstone 密码体制
   * msg 这里限制 4字节.数字,仅原理展示
   */
  encyptMsg(msg,PubKey){
    var randK =  Math.floor(Math.random() * this.Order);
    while(randK == 0){
      randK =  Math.floor(Math.random() * this.Order);
    }


    var PubRnd = this.PointTimes(this.G,randK);
    var dh = this.PointTimes(PubKey,randK);
    // 可以把msg 按字节平分,
    // msg -> (msg0 msg1);
    
    let bf =   Buffer.alloc(4,0);

    bf.writeUInt32BE(msg);
    var msg0 = bf.readUInt16BE(0);
    var msg1 = bf.readUInt16BE(2);
    if (msg0 > this.Prime || msg1 > this.Prime) {
      throw 'msg too large'
    }
 
    var out0 = Number(BigInt(msg0) * BigInt(dh.x) % BigInt(this.Prime));
    var out1 = Number(BigInt(msg1) * BigInt(dh.y) % BigInt(this.Prime));
 
    return [PubRnd,out0,out1];
  }

  decryptMsg(cipher,privatekey){
    var tmpPub = cipher[0];
    var dh  = this.PointTimes(tmpPub,privatekey);
 
    var x1 = exGcd(BigInt(dh.x),BigInt(this.Prime));
    var y1 = exGcd(BigInt(dh.y),BigInt(this.Prime));
    
    var msg0 = Number(BigInt(cipher[1]) * x1 % BigInt(this.Prime));
    var msg1 = Number(BigInt(cipher[2]) * y1 % BigInt(this.Prime));


    /// 避免溢出,实际上,溢出了肯定就是解密失败了
    msg0 = msg0 & 0xffff;
    msg1 = msg1 & 0xffff;

    let bf = Buffer.alloc(4,0);
    bf.writeUInt16BE(msg0);
    bf.writeUInt16BE(msg1,2);
    var outMsg = bf.readUInt32BE(0);
    return outMsg;
  }

}

exports.EC = new ECC;
let ec =  new ECC;

// for (var i = 100 ; i < 120 ; ++ i ){
//   var s = ec.PointTimes(ec.G,i);
//   console.log(i,"---",s);
// }

var s = ec.PointTimes(ec.G,3n);
console.log("---Finalx",s.x.toString(16));
console.log("---Finaly",s.y.toString(16));




if(0){
  var p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2Fn
  var x = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798n
  var n = ((x * x % p) * x )% p + 7n

  // y^2 = x^3 + ax + b mod Prime

  var y = cipolla_sqrt(n,p);
 
  console.log("Y",y.toString(16))
  console.log("Y2",((p - y ) % p).toString(16) )


  // 32670510020758816978083085130507043184471273380659243275938904335757337482424n
}



!function(){
const EC = require('./ecc').EC
var a = EC.genKeyPair(Math.floor(Math.random() * EC.Order ));
var b = EC.genKeyPair();
console.log('keypair A',a );
console.log('keypair B',b );
if(1){
    var ecdhA = EC.ecdh(a.private,b.public);
    var ecdhB = EC.ecdh(b.private,a.public);
    console.log('ECDH of A', ecdhA);
    console.log('ECDH of B', ecdhB);
    console.log('ECDH A == B ?', ecdhB.x == ecdhA.x && ecdhB.y == ecdhA.y);
}


if(1){
    console.log('\n\n-----------Sign-------')
    var msgHash = Math.floor(Math.random() * EC.Order);
    console.log('msg to be sign:',msgHash);
    var signinfo = EC.SingMsg(msgHash,a.private);
    console.log('sign with key A.private result',signinfo);

    var ra = EC.VerifySign(msgHash,signinfo, a.public);
    var rb = EC.VerifySign(msgHash,signinfo, b.public);

    console.log('verify with A.public:',ra ? '✅' : '❌');
    console.log('verify with B.public:',rb ? '✅' : '❌');
}


if(1){
    console.log('\n\n--------------Encryption (Menezes-Vanstone)--------')
    var msg = Math.floor(Math.random() * EC.Prime) & 0xffffffff;

    console.log('message :',msg);

    var cipher = EC.encyptMsg(msg,a.public);

    console.log("encrypt (by A.public):",cipher);
    var msg2 = EC.decryptMsg(cipher,a.private);
    console.log('decrypt (by A.private)',msg2,msg2 == msg ? '✅' : '❌');

    msg2 = EC.decryptMsg(cipher,b.private);
    console.log('decrypt (by B.private)',msg2,msg2 == msg ? '✅' : '❌');
}
 

// // // 重新生成素数P,生成点
// EC.findNewGroup()
// var c = EC.genKeyPair()
// console.log(c); 

}();


输出

keypair A {
  private: 338741,
  public: { x: 1237765, y: 721891 },
  curve: { P: 1271293, Order: 423013, G: { x: 336180, y: 636912 } }
}
keypair B {
  private: 270816,
  public: { x: 541275, y: 574997 },
  curve: { P: 1271293, Order: 423013, G: { x: 336180, y: 636912 } }
}
ECDH of A { x: 145062, y: 1113104 }
ECDH of B { x: 145062, y: 1113104 }
ECDH A == B ? true


-----------Sign-------
msg to be sign: 257748
sign with key A.private result [ 120209, 278454 ]
verify with A.public: ✅
verify with B.public: ❌


--------------Encryption (Menezes-Vanstone)--------
message : 90572
encrypt (by A.public): [ { x: 995290, y: 1257659 }, 129839, 955553 ]
decrypt (by A.private) 90572 ✅
decrypt (by B.private) 3740080411 ❌