基于python的secp256k1椭圆曲线综合加密方案
eciesp的Python项目详细描述
eciespy
python中secp256k1的椭圆曲线综合加密方案。
其他语言版本:
您还可以查看Flask Web后端演示here。
安装
使用python 3.5+下的pip install eciespy
安装。
快速启动
>>>fromecies.utilsimportgenerate_eth_key,generate_key>>>fromeciesimportencrypt,decrypt>>>eth_k=generate_eth_key()>>>prvhex=eth_k.to_hex()>>>pubhex=eth_k.public_key.to_hex()>>>data=b'this is a test'>>>decrypt(prvhex,encrypt(pubhex,data))b'this is a test'>>>secp_k=generate_key()>>>prvhex=secp_k.to_hex()>>>pubhex=secp_k.public_key.format(True).hex()>>>decrypt(prvhex,encrypt(pubhex,data))b'this is a test'
或者在您最喜欢的command line中使用内置命令eciespy
。
API
ecies.encrypt(receiver_pk: Union[str, bytes], msg: bytes) -> bytes
参数:
- receiver_pk-接收器的公钥(十六进制str或字节)
- msg-要加密的数据
返回:字节
ecies.decrypt(receiver_sk: Union[str, bytes], msg: bytes) -> bytes
参数:
- receiver磁盘-接收器的私钥(十六进制str或字节)
- msg-要解密的数据
返回:字节
命令行界面
显示帮助
$ eciespy -h usage: eciespy [-h] [-e] [-d] [-g] [-k KEY] [-D [DATA]] [-O [OUT]]Elliptic Curve Integrated Encryption Scheme for secp256k1 in Pythonoptional arguments: -h, --help show this help message and exit -e, --encrypt encrypt with public key, exclusive with -d -d, --decrypt decrypt with private key, exclusive with -e -g, --generate generate ethereum key pair -k KEY, --key KEY public or private key file -D [DATA], --data [DATA] file to encrypt or decrypt, if not specified, it will read from stdin -O [OUT], --out [OUT] encrypted or decrypted file, if not specified, it will write to stdout
生成eth密钥
$ eciespy -g Private: 0x95d3c5e483e9b1d4f5fc8e79b2deaf51362980de62dbb082a9a4257eef653d7dPublic: 0x98afe4f150642cd05cc9d2fa36458ce0a58567daeaf5fde7333ba9b403011140a4e28911fcf83ab1f457a30b4959efc4b9306f514a4c3711a16a80e3b47eb58bAddress: 0x47e801184B3a8ea8E6A4A7A4CFEfEcC76809Da72
用公钥加密,用私钥解密
$echo'0x95d3c5e483e9b1d4f5fc8e79b2deaf51362980de62dbb082a9a4257eef653d7d' > prv $echo'0x98afe4f150642cd05cc9d2fa36458ce0a58567daeaf5fde7333ba9b403011140a4e28911fcf83ab1f457a30b4959efc4b9306f514a4c3711a16a80e3b47eb58b' > pub $echo'helloworld'| eciespy -e -k pub | eciespy -d -k prv helloworld$echo'data to encrypt' > data $ eciespy -e -k pub -D data -O enc_data $ eciespy -d -k prv -D enc_data data to encrypt$ rm data enc_data
机制和实现细节
这个库结合了secp256k1
和AES-256-GCM
(由^{secp256k1
公钥加密和用secp256k1
私钥解密的api。它通常有两部分:
使用ECDH交换AES会话密钥;
Notice that the sender public key is generated every time when
ecies.encrypt
is invoked, thus, the AES session key varies.使用此AES会话密钥加密/解密
AES-256-GCM
下的数据。
基本上加密的数据如下:
+-------------------------------+----------+----------+-----------------+ | 65 Bytes | 16 Bytes | 16 Bytes | == data size | +-------------------------------+----------+----------+-----------------+ | Sender Public Key (ephemeral) | Nonce/IV | Tag/MAC | Encrypted data | +-------------------------------+----------+----------+-----------------+ | sender_pk | nonce | tag | encrypted_data | +-------------------------------+----------+----------+-----------------+ | Secp256k1 | AES-256-GCM | +-------------------------------+---------------------------------------+
秒256k1
浏览ECDH
那么,我们如何计算secp256k1
下的ecdh键?如果你使用像^{k1.ecdh(k2.public_key.format())
,然后,嗯哼,你得到了!让我们看看如何在简单的python代码片段中实现这一点:
>>>fromcoincurveimportPrivateKey>>>k1=PrivateKey(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01')>>>k2=PrivateKey(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02')>>>k1.public_key.format(False).hex()# 65 bytes, False means uncompressed key'0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8'>>>k2.public_key.format(False).hex()# 65 bytes'04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a'>>>k1.ecdh(k2.public_key.format()).hex()'b1c9938f01121e159887ac2c8d393a22e4476ff8212de13fe1939de2a236f0a7'>>>k2.ecdh(k1.public_key.format()).hex()'b1c9938f01121e159887ac2c8d393a22e4476ff8212de13fe1939de2a236f0a7'
手动计算ECDH键
然而,作为一个像你这样有强烈学习欲望的黑客,你一定对地下的魔法很好奇。
在一句话中,k1
和k2
的secp256k1
的ecdh键只是sha256(k2.public_key.multiply(k1))
。
>>>k1.to_int()1>>>shared_pub=k2.public_key.multiply(bytes.fromhex(k1.to_hex()))>>>shared_pub.point()(89565891926547004231252920425935692360644145829622209833684329913297188986597,12158399299693830322967808612713398636155367887041628176798871954788371653930)>>>importhashlib>>>h=hashlib.sha256()>>>h.update(shared_pub.format())>>>h.hexdigest()# here you got the ecdh key same as above!'b1c9938f01121e159887ac2c8d393a22e4476ff8212de13fe1939de2a236f0a7'
Warning: NEVER use small integers as private keys on any production systems or storing any valuable assets.
Warning: ALWAYS use safe methods like
os.urandom
to generate private keys.
关于ecdh的数学
让我们详细讨论一下。这里的单词multiply表示将椭圆曲线(如(x, y)
)上公钥的point与^{str1}$scalar相乘(如k
)。这里的k
是私钥的整数格式,例如,这里的k1
可以是1
,这里的(x, y)
是一个非常大的数对,如(89565891926547004231252920425935692360644145829622209833684329913297188986597, 12158399299693830322967808612713398636155367887041628176798871954788371653930)
。
Warning: 1 * (x, y) == (x, y) is always true, since 1 is the identity element for multiplication.
从数学上讲,椭圆曲线密码是基于这样一个事实:你可以很容易地将点A
(也就是ecdh中的公钥)和标量k
(也就是私钥)相乘,得到另一个点B
(也就是公钥),但是几乎不可能从B
反向计算A
(很容易乘法,难以分割)。
压缩和未压缩的键
与标量相乘的点可以被视为该点自身多次相加,并且点B
可以被转换为压缩或未压缩格式的可读公钥。
- 压缩格式(
x
仅坐标)
>>>point=(89565891926547004231252920425935692360644145829622209833684329913297188986597,12158399299693830322967808612713398636155367887041628176798871954788371653930)>>>prefix='02'ifpoint[1]%2==0else'03'>>>compressed_key_hex=prefix+hex(point[0])[2:]>>>compressed_key=bytes.fromhex(compressed_key_hex)>>>compressed_key.hex()'02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5'
- 未压缩格式(
(x, y)
坐标)
>>>uncompressed_key_hex='04'+hex(point[0])[2:]+hex(point[1])[2:]>>>uncompressed_key=bytes.fromhex(uncompressed_key_hex)>>>uncompressed_key.hex()'04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a'
格式由下面来自bitcoin book的图像描述。
If you want to convert the compressed format to uncompressed, basically, you need to calculate
y
fromx
by solving the equation using Cipolla's Algorithm:You can check the bitcoin wiki and this thread on bitcointalk.org for more details.
然后,k1
和k2
之间的共享密钥是compressed密钥的sha256
散列。最好使用压缩格式,因为不需要任何计算就可以从x
或(x, y)
获取x
。
>>>h=hashlib.sha256()>>>h.update(compressed_key)>>>h.hexdigest()'b1c9938f01121e159887ac2c8d393a22e4476ff8212de13fe1939de2a236f0a7'
你可能想问,如果没有杂碎怎么办?简而言之,hash可以:
- 使共享密钥的长度固定;
- 使其更安全,因为哈希函数可以删除原始计算密钥中的“弱位”。查看本paper的简介部分了解更多详细信息。
Warning: Accoring to some recent research, although widely used, the
sha256
key derivation function is not secure enough.
aes
现在我们有了共享密钥,可以使用nonce
和tag
来解密。这是非常直接的,这个例子来自pycryptodome
的documentation。
>>>fromCryptodome.CipherimportAES>>>key=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'>>>iv=b'\xf3\xe1\xba\x81\r,\x89\x00\xb1\x13\x12\xb7\xc7%V_'>>>tag=b'\xec;q\xe1|\x11\xdb\xe3\x14\x84\xda\x94P\xed\xcfl'>>>data=b'\x02\xd2\xff\xed\x93\xb8V\xf1H\xb9'>>>decipher=AES.new(key,AES.MODE_GCM,nonce=iv)>>>decipher.decrypt_and_verify(data,tag)b'helloworld'
Strictly speaking,
nonce
!=iv
, but this is a little bit off topic, if you are curious, you can check the comment inutils.py
.
发行说明
0.3.0
- api更改:使用
HKDF
来派生共享密钥,而不是sha256
0.2.0
- api更改:
ecies.encrypt
和ecies.decrypt
现在可以同时使用十六进制str和原始字节 - 凹凸依赖项版本
- 更新文档
0.1.1~0.1.9
- 凹凸依赖项版本
- 更新文档
- 切换到圆形ci
- 将许可证更改为MIT
0.1.0
- 首次测试版发布