在密码学中,环签名是一种数字签名,其可以由每个具有密钥的一组用户的任何成员完成。因此,使用环签名签名的消息由特定人群中的某一人签署。环签名的一个安全属性是若要确定哪个组成员的密钥用于产生签名在计算上是不可行的。环签名与群签名类似,但在两个关键方面有所不同:
1. 无法撤销单个签名的匿名性;
2. 任何用户组都可以作为一个组使用,无需额外设置。
环签名技术由Ron Rivest, Adi Shamir, 和 Yael Tauman发明的,于2001发表出来的。环签名得名于其环状结构签名算法。
定义
假设有一组用户,每个人都有公钥和私钥对,(P1, S1), (P2, S2), …, (Pn, Sn)。用户i可以基于消息m计算一个环签名σ ,输入参数为(m, Si, P1, …, Pn)。如果给定参数σ, m以及所有的公钥(P1, …, Pn),任何人都可以检查该环签名的合法性。如果环签名是正确的,那么它应该可以通过刚才的验证。另外,如果没有当前用户组中任何一个用户的私钥,任何人应该是不太可能基于任何组消息来创建一个合法的环签名。
环签名满足的性质
无条件匿名性:攻-击-者者无法确定签名是由环中哪个成员生成,即使在获得环成员私钥的情况下,概率也不超过1/n。
正确性:签名必需能被所有其他人验证。
不可伪造性:环中其他成员不能伪造真实签名者签名,外部攻-击-者者即使在获得某个有效环签名的基础上,也不能为消息m伪造一个签名。
环签名实现:
(1)密钥生成。为环中每个成员产生一个密钥对(公钥PKi,私钥SKi)。
(2)签名。签名者用自己的私钥和任意n个环成员(包括自己)的公钥为消息m生成签名a。
(3)签名验证。验证者根据环签名和消息m,验证签名是否为环中成员所签,如果有效就接收,否则丢弃。
Python实现样例
import os, hashlib, random, Crypto.PublicKey.RSA
class ring:
def __init__(self, k, L=1024):
self.k = k
self.l = L
self.n = len(k)
self.q = 1 << (L - 1)
def sign(self, m, z):
self.permut(m)
s = [None] * self.n
u = random.randint(0, self.q)
c = v = self.E(u)
for i in (range(z+1, self.n) + range(z)):
s[i] = random.randint(0, self.q)
e = self.g(s[i], self.k[i].e, self.k[i].n)
v = self.E(v^e)
if (i+1) % self.n == 0:
c = v
s[z] = self.g(v^u, self.k[z].d, self.k[z].n)
return [c] + s
def verify(self, m, X):
self.permut(m)
def _f(i):
return self.g(X[i+1], self.k[i].e, self.k[i].n)
y = map(_f, range(len(X)-1))
def _g(x, i):
return self.E(x^y[i])
r = reduce(_g, range(self.n), X[0])
return r == X[0]
def permut(self, m):
self.p = int(hashlib.sha1('%s' % m).hexdigest(),16)
def E(self, x):
msg = '%s%s' % (x, self.p)
return int(hashlib.sha1(msg).hexdigest(), 16)
def g(self, x, e, n):
q, r = divmod(x, n)
if ((q + 1) * n) <= ((1 << self.l) - 1):
rslt = q * n + pow(r, e, n)
else:
rslt = x
return rslt
签名并验证两个由4个用户组成的环签名消息:
size = 4
msg1, msg2 = 'hello', 'world!'
def _rn(_):
return Crypto.PublicKey.RSA.generate(1024, os.urandom)
key = map(_rn, range(size))
r = ring(key)
for i in range(size):
s1 = r.sign(msg1, i)
s2 = r.sign(msg2, i)
assert r.verify(msg1, s1) and r.verify(msg2, s2) and not r.verify(msg1, s2)
go实现样例
import (
"bytes"
"crypto/elliptic"
"crypto/sha256"
"fmt"
"io"
"math/big"
"sync"
)
一些包,bytes是二进制处理数组(对于哈希函数就很有效了),crypto是密码学库,这里我们用的是椭圆曲线密码学,又用到了SHA256哈希函数。math/big是大整数包,因为密码通常都是很长的,超出了整数范围。sync是并发,具体的后面解释。
首先我们来看看源码库elliptic.curve的内容
type Curve interface {
// Params returns the parameters for the curve.
Params() *CurveParams
// IsOnCurve reports whether the given (x,y) lies on the curve.
IsOnCurve(x, y *big.Int) bool
// Add returns the sum of (x1,y1) and (x2,y2)
Add(x1, y1, x2, y2 *big.Int) (x, y *big.Int)
// Double returns 2*(x,y)
Double(x1, y1 *big.Int) (x, y *big.Int)
// ScalarMult returns k*(Bx,By) where k is a number in big-endian form.
ScalarMult(x1, y1 *big.Int, k []byte) (x, y *big.Int)
// ScalarBaseMult returns k*G, where G is the base point of the group
// and k is an integer in big-endian form.
ScalarBaseMult(k []byte) (x, y *big.Int)
}
type CurveParams struct {
P *big.Int // the order of the underlying field
N *big.Int // the order of the base point
B *big.Int // the constant of the curve equation
Gx, Gy *big.Int // (x,y) of the base point
BitSize int // the size of the underlying field
Name string // the canonical name of the curve
}
注释很详细,解释一下,先看曲线参数,P是椭圆曲线的阶,N是基点的阶,它一定是P的一个质因子,GX,GY是基点坐标,Bitsize是基点阶的字节大小。
再看曲线函数,Add是曲线上点的加法,几何上为连线求另一点,公式可以用韦达定理推导,需要注意,我们用椭圆曲线代入到离散对数时,点的加法就是数的乘法,椭圆曲线密码的安全性高就是因为没有多项式复杂度方法求倍数。
Double函数是自加,几何上为切线求另一交点。
ScalarMult是点的数乘,注意就是离散对数里面的幂。
ScalarBaseMult是基点的数乘,供方便用。
下面看密钥结构体:
type PublicKey struct {
elliptic.Curve
X, Y *big.Int
}
type PrivateKey struct {
PublicKey
D *big.Int
}
定义公钥和私钥,公钥里的X,Y就是公钥坐标,公钥的产生公式为,其中x是私钥,也就是PrivateKey结构体中的D。
type PublicKeyRing struct {
Ring []PublicKey
}
定义环,环就是公钥集合。
func NewPublicKeyRing(cap uint) *PublicKeyRing {
return &PublicKeyRing{make([]PublicKey, 0, cap)}
}
生成指定长度的环,0表示初始元素个数为0,预留cap个元素的空间。make是开数组的函数。
func (r *PublicKeyRing) Add(pub PublicKey) {
r.Ring = append(r.Ring, pub)
}
func (r *PublicKeyRing) Len() int {
return len(r.Ring)
}
func (r *PublicKeyRing) Bytes() (b []byte) {
for _, pub := range r.Ring {
b = append(b, pub.X.Bytes()...)
b = append(b, pub.Y.Bytes()...)
}
return
}
var one = new(big.Int).SetInt64(1)
一些常用操作,append是追加元素,range是遍历关键词。
func randFieldElement(c elliptic.Curve, rand io.Reader) (k *big.Int, err error) {
params := c.Params()
b := make([]byte, params.BitSize/8+8)
_, err = io.ReadFull(rand, b)
if err != nil {
return
}
k = new(big.Int).SetBytes(b)
n := new(big.Int).Sub(params.N, one)
k.Mod(k, n)
k.Add(k, one)
return
}
随机数产生,下半段是避免出现0的处理,因为椭圆曲线的子群是循环群,所以出现零点是可能的。
func GenerateKey(c elliptic.Curve, rand io.Reader) (priv *PrivateKey, err error) {
k, err := randFieldElement(c, rand)
if err != nil {
return
}
priv = new(PrivateKey)
priv.PublicKey.Curve = c
priv.D = k
priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes())
return
}
产生私钥、公钥。注意,是先有私钥,后有公钥,私钥仅仅是基点的那个幂指数,其他都是公钥。 err是go语言的异常处理关键字。
type RingSign struct {
X, Y *big.Int
C, T []*big.Int
}