Skip to content

Commit c7268ef

Browse files
committed
add chacha20-ietf-poly1305 c version
1 parent d1c8535 commit c7268ef

File tree

8 files changed

+185
-5
lines changed

8 files changed

+185
-5
lines changed

pproxy/__doc__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__title__ = "pproxy"
2-
__version__ = "2.0.0"
2+
__version__ = "2.0.1"
33
__license__ = "MIT"
44
__description__ = "Proxy server that can tunnel among remote servers by regex rules."
55
__keywords__ = "proxy socks http shadowsocks shadowsocksr ssr redirect pf tunnel cipher ssl udp"

pproxy/cipher.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,19 @@ class AES_192_GCM_Cipher(AES_256_GCM_Cipher):
164164
class AES_128_GCM_Cipher(AES_256_GCM_Cipher):
165165
KEY_LENGTH = IV_LENGTH = 16
166166

167+
class ChaCha20_IETF_POLY1305_Cipher(AEADCipher):
168+
KEY_LENGTH = 32
169+
IV_LENGTH = 32
170+
NONCE_LENGTH = 12
171+
TAG_LENGTH = 16
172+
def decrypt_and_verify(self, buffer, tag):
173+
return self.cipher_new(self.nonce).decrypt_and_verify(buffer, tag)
174+
def encrypt_and_digest(self, buffer):
175+
return self.cipher_new(self.nonce).encrypt_and_digest(buffer)
176+
def setup(self):
177+
from Crypto.Cipher import ChaCha20_Poly1305
178+
self.cipher_new = lambda nonce: ChaCha20_Poly1305.new(key=self.key, nonce=nonce)
179+
167180
class BF_CFB_Cipher(BaseCipher):
168181
KEY_LENGTH = 16
169182
IV_LENGTH = 8

pproxy/server.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,6 @@ async def test_url(url, rserver):
394394
port = int(port) if port else 80 if url.scheme == 'http' else 443
395395
initbuf = f'GET {url.path or "/"} HTTP/1.1\r\nHost: {host_name}\r\nUser-Agent: pproxy-{__version__}\r\nConnection: close\r\n\r\n'.encode()
396396
for roption in rserver:
397-
if roption.direct:
398-
continue
399397
print(f'============ {roption.bind} ============')
400398
try:
401399
reader, writer = await roption.open_connection(host_name, port, None, None)
@@ -438,7 +436,7 @@ def main():
438436
parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
439437
args = parser.parse_args()
440438
if args.test:
441-
asyncio.run(test_url(args.test, args.rserver))
439+
asyncio.get_event_loop().run_until_complete(test_url(args.test, args.rserver))
442440
return
443441
if not args.listen and not args.ulisten:
444442
args.listen.append(ProxyURI.compile_relay('http+socks4+socks5://:8080/'))

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def find_value(name):
3737
],
3838
extras_require = {
3939
'accelerated': [
40-
'pycryptodome',
40+
'pycryptodome >= 3.7.2',
4141
],
4242
},
4343
install_requires = [],

tests/api_client.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import asyncio
2+
import pproxy
3+
4+
async def test_tcp():
5+
conn = pproxy.Connection('ss://chacha20:123@127.0.0.1:12345')
6+
reader, writer = await conn.tcp_connect('google.com', 80)
7+
writer.write(b'GET / HTTP/1.1\r\n\r\n')
8+
data = await reader.read(1024*16)
9+
print(data.decode())
10+
11+
async def test_udp():
12+
conn = pproxy.Connection('ss://chacha20:123@127.0.0.1:12345')
13+
answer = asyncio.Future()
14+
await conn.udp_sendto('8.8.8.8', 53, b'hello', answer.set_result)
15+
await answer
16+
print(answer.result())
17+
18+
asyncio.run(test_tcp())
19+
asyncio.run(test_udp())

tests/cipher_compare.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import os, time
2+
from pproxy.cipher import AES_256_CFB_Cipher as A
3+
from pproxy.cipherpy import AES_256_CFB_Cipher as B
4+
from pproxy.cipher import ChaCha20_Cipher as C
5+
from pproxy.cipherpy import ChaCha20_Cipher as D
6+
from pproxy.cipherpy import Camellia_256_CFB_Cipher as E
7+
8+
TO_TEST = (A, B, C, D, E)
9+
10+
for X in TO_TEST:
11+
t = time.perf_counter()
12+
for i in range(10):
13+
c = X(os.urandom(X.KEY_LENGTH))
14+
c.setup_iv()
15+
for j in range(100):
16+
c.encrypt(os.urandom(1024))
17+
print(time.perf_counter()-t)

tests/cipher_speed.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import os, time, sys, os
2+
from pproxy.cipher import MAP
3+
from pproxy.cipherpy import MAP as MAP_PY
4+
5+
def test_cipher(A, size=32*1024, repeat=128):
6+
for i in range(repeat):
7+
key = os.urandom(A.KEY_LENGTH)
8+
iv = os.urandom(A.IV_LENGTH)
9+
a = A(key)
10+
a.setup_iv(iv)
11+
s = os.urandom(size)
12+
s2 = a.encrypt(s)
13+
a = A(key, True)
14+
a.setup_iv(iv)
15+
s4 = a.decrypt(s2)
16+
assert s == s4
17+
18+
cipher = sys.argv[1] if len(sys.argv) > 1 else None
19+
20+
if cipher and cipher.endswith('-py'):
21+
A = MAP_PY.get(cipher[:-3])
22+
else:
23+
A = MAP.get(cipher)
24+
if A:
25+
t = time.perf_counter()
26+
test_cipher(A)
27+
print(cipher, time.perf_counter()-t)
28+
else:
29+
print('unknown cipher', cipher)
30+

tests/cipher_verify.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import os, time, sys, pickle, os
2+
from pproxy.cipher import MAP
3+
from pproxy.cipherpy import MAP as MAP_PY
4+
5+
def test_both_cipher(A, B, size=4*1024, repeat=16):
6+
print('Testing', B.__name__, '...')
7+
t1 = t2 = 0
8+
for i in range(repeat):
9+
assert A.KEY_LENGTH == B.KEY_LENGTH and A.IV_LENGTH == B.IV_LENGTH
10+
key = os.urandom(A.KEY_LENGTH)
11+
iv = os.urandom(A.IV_LENGTH)
12+
t = time.perf_counter()
13+
a = A(key)
14+
a.setup_iv(iv)
15+
t1 += time.perf_counter() - t
16+
t = time.perf_counter()
17+
b = B(key)
18+
b.setup_iv(iv)
19+
t2 += time.perf_counter() - t
20+
s = os.urandom(size)
21+
t = time.perf_counter()
22+
s2 = a.encrypt(s)
23+
t1 += time.perf_counter() - t
24+
t = time.perf_counter()
25+
s3 = b.encrypt(s)
26+
t2 += time.perf_counter() - t
27+
assert s2 == s3
28+
29+
t = time.perf_counter()
30+
a = A(key, True)
31+
a.setup_iv(iv)
32+
t1 += time.perf_counter() - t
33+
t = time.perf_counter()
34+
b = B(key, True)
35+
b.setup_iv(iv)
36+
t2 += time.perf_counter() - t
37+
t = time.perf_counter()
38+
s4 = a.decrypt(s2)
39+
t1 += time.perf_counter() - t
40+
t = time.perf_counter()
41+
s5 = b.decrypt(s2)
42+
t2 += time.perf_counter() - t
43+
assert s4 == s5 == s
44+
45+
print('Passed', t1, t2)
46+
47+
def test_cipher(A, data, size=4*1024, repeat=16):
48+
if A.__name__ not in data:
49+
if input('Correct now? (Y/n)').upper() != 'Y':
50+
return
51+
d = []
52+
for i in range(repeat):
53+
key = os.urandom(A.KEY_LENGTH)
54+
iv = os.urandom(A.IV_LENGTH)
55+
a = A(key)
56+
a.setup_iv(iv)
57+
s = os.urandom(size)
58+
s2 = a.encrypt(s)
59+
a = A(key, True)
60+
a.setup_iv(iv)
61+
s4 = a.decrypt(s2)
62+
assert s == s4
63+
d.append((key, iv, s, s2))
64+
data[A.__name__] = d
65+
print('Saved correct data')
66+
else:
67+
t = time.perf_counter()
68+
print('Testing', A.__name__, '...')
69+
for key, iv, s, s2 in data[A.__name__]:
70+
a = A(key)
71+
a.setup_iv(iv)
72+
s3 = a.encrypt(s)
73+
assert s2 == s3
74+
a = A(key, True)
75+
a.setup_iv(iv)
76+
s4 = a.decrypt(s2)
77+
assert s == s4
78+
print('Passed', time.perf_counter()-t)
79+
80+
81+
cipher = sys.argv[1] if len(sys.argv) > 1 else None
82+
data = pickle.load(open('.cipherdata', 'rb')) if os.path.exists('.cipherdata') else {}
83+
84+
if cipher is None:
85+
print('Testing all ciphers')
86+
87+
for cipher, B in sorted(MAP_PY.items()):
88+
A = MAP.get(cipher)
89+
if A:
90+
test_both_cipher(A, B)
91+
elif B.__name__ in data:
92+
test_cipher(B, data)
93+
else:
94+
B = MAP_PY[cipher]
95+
A = MAP.get(cipher)
96+
if A:
97+
test_both_cipher(A, B)
98+
else:
99+
test_cipher(B, data)
100+
101+
102+
pickle.dump(data, open('.cipherdata', 'wb'))
103+

0 commit comments

Comments
 (0)