基于python的secp256k1椭圆曲线综合加密方案

eciesp的Python项目详细描述


eciespy

Codacy BadgeCICodecovPyPI - Python VersionPyPILicense

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

机制和实现细节

这个库结合了secp256k1AES-256-GCM(由^{}^{}提供支持)来提供用secp256k1公钥加密和用secp256k1私钥解密的api。它通常有两部分:

  1. 使用ECDH交换AES会话密钥;

    Notice that the sender public key is generated every time when ecies.encrypt is invoked, thus, the AES session key varies.

  2. 使用此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键

然而,作为一个像你这样有强烈学习欲望的黑客,你一定对地下的魔法很好奇。

在一句话中,k1k2secp256k1的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的图像描述。

EC public key format

If you want to convert the compressed format to uncompressed, basically, you need to calculate y from x by solving the equation using Cipolla's Algorithm:

y^2=(x^3 + 7) mod p, where p=2^{256}-2^{32}-2^{9}-2^{8}-2^{7}-2^{6}-2^{4}-1

You can check the bitcoin wiki and this thread on bitcointalk.org for more details.

然后,k1k2之间的共享密钥是compressed密钥的sha256散列。最好使用压缩格式,因为不需要任何计算就可以从x(x, y)获取x

>>>h=hashlib.sha256()>>>h.update(compressed_key)>>>h.hexdigest()'b1c9938f01121e159887ac2c8d393a22e4476ff8212de13fe1939de2a236f0a7'

你可能想问,如果没有杂碎怎么办?简而言之,hash可以:

  1. 使共享密钥的长度固定;
  2. 使其更安全,因为哈希函数可以删除原始计算密钥中的“弱位”。查看本paper的简介部分了解更多详细信息。

Warning: Accoring to some recent research, although widely used, the sha256 key derivation function is not secure enough.

aes

现在我们有了共享密钥,可以使用noncetag来解密。这是非常直接的,这个例子来自pycryptodomedocumentation

>>>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 in utils.py.

发行说明

0.3.0

  • api更改:使用HKDF来派生共享密钥,而不是sha256

0.2.0

  • api更改:ecies.encryptecies.decrypt现在可以同时使用十六进制str和原始字节
  • 凹凸依赖项版本
  • 更新文档

0.1.1~0.1.9

  • 凹凸依赖项版本
  • 更新文档
  • 切换到圆形ci
  • 将许可证更改为MIT

0.1.0

  • 首次测试版发布

欢迎加入QQ群-->: 979659372 Python中文网_新手群

推荐PyPI第三方库


热门话题
eclipse如何使用getter和setter调试java代码?   java如何使用HashMap作为LinkedHashSet   安卓 java。lang.RuntimeException:无法启动活动组件信息{..}JAVAlang.NullPointerException   使用二进制堆和比较器的Remove()存在循环问题   java在两个数组中查找匹配的数字   Maven exec可以工作,但java jar不能   java SQLite数据库未显示在文件资源管理器中   java Android Studio下一页按钮不工作   java为什么启用某些异常的调试要慢得多?   java如何在测试中注入bean的属性源   java如何预加载Android inapp计费购买   java使用CSS选择器在特定实例上单击按钮   java在Spring Boot中使用生成的ID持久化一个OneToMany实体   作用域如果我在java方法中声明一个变量,该变量是否也可以被从声明该变量的方法调用的任何方法访问?   本机JNIC代码中的java Openssl同步   java没有规则使目标“external/obpermissionjar/javalib/obpermissionjarPro20160628_1.0.9”为..所需。。?   java计算人口密度:初学者指南   java如何检查书签是否包含表?   java在Spring应用程序中设置时区