楕円曲線暗号のPythonによる実装その2(楕円曲線とECDSA)
お断り
この記事は『Software Design2022年3月号』の「第4章:電子署名のプロセスを体験 Pythonによる楕円曲線暗号の実装」の入稿記事を技術評論社のご好意で公開したものです。
元はLaTeXだったのをマークダウンに修正し、二つに分けています。
前半は楕円曲線暗号のPythonによる実装その1(有限体とECDH鍵共有)です。
記事中のサンプルコードはサポートページからダウンロードできます。
楕円曲線クラス
楕円曲線の点
有限体クラスを実装できたので次は楕円曲線クラス
楕円曲線は有限体
楕円曲線クラスは楕円曲線の節で紹介したように
secp256k1はTLSやビットコインで使われる楕円曲線のパラメータで、
となっています(SEC 2: Recommended Elliptic Curve Domain Parameters)。
それらの値をクラスメソッド
class Ec:
@classmethod
def init(cls, a, b, r):
cls.a = a
cls.b = b
cls.r = r
Ec::init
楕円曲線の点は2個の
そのためメンバ変数として
値が正しいか否かを確認する
def isValid(self):
if self.isZero:
return True
a = self.a
b = self.b
x = self.x
y = self.y
return y*y == (x*x+a)*x+b
Ec::isValid
楕円曲線の点の足し算
楕円曲線の点の足し算、引き算の規則を紹介します。
まず点0と任意の点
また
そして一般の点
加法公式
式は複雑ですが、四則計算だけなので有限体の節で実装した有限体を使って計算できます。
なぜこんな式が現れるのかについてはたとえば拙著『クラウドを支えるこれからの暗号技術』などを参照ください。
この式をそのままPythonで実装します。
def __add__(self, rhs):
if self.isZero:
return rhs
if rhs.isZero:
return self
x1 = self.x
y1 = self.y
x2 = rhs.x
y2 = rhs.y
if x1 == x2:
# P + (-P) = 0
if y1 == -y2:
return Ec()
# dbl
L = x1 * x1
L = (L + L + L + self.a) / (y1 + y1)
else:
L = (y1 - y2) / (x1 - x2)
x3 = L * L - (x1 + x2)
y3 = L * (x1 - x3) - y1
return Ec(x3, y3, False)
Ec::add
楕円曲線の点の整数倍
最後に楕円曲線の点の整数倍を実装します。
そこで効率のよい方法を紹介します。
と表せます。次に
つまり
その値を2倍して次のビットが1なら
この方法では、ループ回数は
Pythonで実装するには、まず
def __mul__(self, rhs):
if rhs == 0:
return Ec()
bs = bin(rhs)[2:]
ret = Ec()
for b in bs:
ret += ret
if b == '1':
ret += self
return ret
Ec::mul
以上でECDH鍵共有に必要なメソッドの実装が完了しました。
ECDSAの実装
最後に楕円曲線を用いた署名の一つであるECDSAを実装しましょう。
署名の概要
署名は鍵生成、署名(sign)、検証(verify)の3個のアルゴリズムからなります。
鍵生成ではアリスが署名鍵sと検証鍵Sを生成します。署名鍵sは自分だけの秘密の値なので秘密鍵、検証鍵Sは他人に渡して使ってもらう鍵なので公開鍵ともいいます。
signは署名したいデータmに対して署名鍵sで署名と呼ばれるデータσを作ります。
secp256k1曲線の場合は2個の256ビット整数からなる512ビットの固定長データです。
署名
データmと署名σのペアを他人(ボブ)に渡します。
ボブは検証鍵Sを使って(m,σ)の正しさを確認し、受理か拒否します。
ECDSAの鍵生成
ECDSAの鍵生成アルゴリズムは次の通りです。
まずアリスが
そして点
ECDSA
P = initSecp256k1()
s = Fr()
s.setRand() # 署名鍵
S = P * s # 検証鍵
鍵生成
ECDSAの署名
署名と検証にはハッシュ関数を使います。ハッシュ関数自体の説明は他の書籍に譲ります。
PythonでSHA-256を使うにはhashlibをimportします。
ハッシュ値は256ビットでPythonでは32個のバイト配列です。
それを有限体
たとえば
msgToFr(図中のh)はメッセージmから
import hashlib
def byteToFr(b):
v = 0
for x in b:
v = v * 256 + x
return Fr(v)
def msgToFr(msg):
H = hashlib.sha256()
H.update(msg)
return byteToFr(H.digest())
ハッシュ値の整数化
データmの署名を作成するにはまず乱数
そして楕円曲線の点
楕円曲線の点を計算するときは素数
どう実装するか悩んだのですが、ここでは安直に
def sign(P, s, msg):
z = msgToFr(msg)
k = Fr()
k.setRand()
Q = P * k
r = Fr(Q.x.v)
return (r, (r * s + z) / k)
署名
署名に使う乱数
2010年にPlayStation 3の署名実装が同じ乱数を使っていたため署名鍵が漏洩する脆弱性が見つかりました(Console Hacking 2010 PS3 Epic Fail)。
乱数を生成するのはなかなかやっかいなため、その代わりに署名鍵やデータなどから
興味ある方はご覧ください。
ECDSAの検証
最後にverifyを実装します。
verifyは、データmと署名
そして楕円曲線の点
楕円曲線の点を
楕円曲線の整数倍は有限体の掛け算に比べてずっと重たい処理なので
def verify(P, sig, S, msg):
(r, t) = sig
if r == 0 || t == 0:
return False
z = msgToFr(msg)
w = Fr(1) / t
u1 = z * w
u2 = r * w
Q = P * u1 + S * u2
if Q.isZero:
return False
x = Fr(Q.x.v)
return r == x
検証
最後に求めた楕円曲線の点
以上がECDSAの実装です。
実際に使うにはより厳密なチェックや、検証鍵や署名データを相互運用するためのフォーマットなど実装すべき箇所がありますが、手元で数値を確認しながら動作させるにはこれで十分です。
この記事が楕円曲線や鍵共有、署名の理解に少しでも役立てば幸いです。
Discussion