Skip to content

Commit 3ff912a

Browse files
authored
v1.5
add aead ciphers
1 parent 6d6fd4a commit 3ff912a

File tree

3 files changed

+163
-19
lines changed

3 files changed

+163
-19
lines changed

pproxy/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from pproxy import proto
33

44
__title__ = 'pproxy'
5-
__version__ = "1.4.2"
5+
__version__ = "1.5"
66
__description__ = "Proxy server that can tunnel among remote servers by regex rules."
77
__author__ = "Qian Wenjie"
88
__license__ = "MIT License"
@@ -152,7 +152,7 @@ def uri_compile(uri):
152152
return types.SimpleNamespace(protos=protos, rproto=protos[0], cipher=cipher, auth=url.fragment.encode(), match=match, server=server, connect=connect, bind=loc or urlpath, unix=not loc, lbind=lbind, sslclient=sslclient, sslserver=sslserver, alive=True)
153153

154154
def main():
155-
parser = argparse.ArgumentParser(description=__description__+'\nSupported protocols: http,socks,shadowsocks,redirect', epilog='Online help: <https://github.com/qwj/python-proxy>')
155+
parser = argparse.ArgumentParser(description=__description__+'\nSupported protocols: http,socks,shadowsocks,shadowsocksr,redirect', epilog='Online help: <https://github.com/qwj/python-proxy>')
156156
parser.add_argument('-i', dest='listen', default=[], action='append', type=uri_compile, help='proxy server setting uri (default: http+socks://:8080/)')
157157
parser.add_argument('-r', dest='rserver', default=[], action='append', type=uri_compile, help='remote server setting uri (default: direct)')
158158
parser.add_argument('-b', dest='block', type=pattern_compile, help='block regex rules')

pproxy/cipher.py

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import os, hashlib
1+
import os, hashlib, hmac
22

33
class BaseCipher(object):
44
PYTHON = False
55
CACHE = {}
6-
def __init__(self, key, ota=False):
7-
if self.KEY_LENGTH > 0:
6+
def __init__(self, key, ota=False, setup_key=True):
7+
if self.KEY_LENGTH > 0 and setup_key:
88
self.key = self.CACHE.get(b'key'+key)
99
if self.key is None:
1010
keybuf = []
@@ -18,6 +18,7 @@ def __init__(self, key, ota=False):
1818
def setup_iv(self, iv=None):
1919
self.iv = os.urandom(self.IV_LENGTH) if iv is None else iv
2020
self.setup()
21+
return self
2122
def decrypt(self, s):
2223
return self.cipher.decrypt(s)
2324
def encrypt(self, s):
@@ -26,6 +27,52 @@ def encrypt(self, s):
2627
def name(cls):
2728
return cls.__name__.replace('_Cipher', '').replace('_', '-').lower()
2829

30+
class AEADCipher(BaseCipher):
31+
def setup_iv(self, iv=None):
32+
self.iv = os.urandom(self.IV_LENGTH) if iv is None else iv
33+
randkey = hmac.new(self.iv, self.key, hashlib.sha1).digest()
34+
blocks_needed = (self.KEY_LENGTH + len(randkey) - 1) // len(randkey)
35+
okm = bytearray()
36+
output_block = b''
37+
for counter in range(blocks_needed):
38+
output_block = hmac.new(randkey, output_block + b'ss-subkey' + bytes([counter+1]), hashlib.sha1).digest()
39+
okm.extend(output_block)
40+
self.key = bytes(okm[:self.KEY_LENGTH])
41+
self._nonce = 0
42+
self._buffer = bytearray()
43+
self._declen = None
44+
self.setup()
45+
@property
46+
def nonce(self):
47+
ret = self._nonce.to_bytes(self.NONCE_LENGTH, 'little')
48+
self._nonce = (self._nonce+1) & ((1<<self.NONCE_LENGTH)-1)
49+
return ret
50+
def decrypt(self, s):
51+
self._buffer.extend(s)
52+
ret = bytearray()
53+
while 1:
54+
if self._declen is None:
55+
if len(self._buffer) < 2+self.TAG_LENGTH:
56+
break
57+
self._declen = int.from_bytes(self.decrypt_and_verify(self._buffer[:2], self._buffer[2:2+self.TAG_LENGTH]), 'big')
58+
assert self._declen <= 16*1024-1
59+
del self._buffer[:2+self.TAG_LENGTH]
60+
else:
61+
if len(self._buffer) < self._declen+self.TAG_LENGTH:
62+
break
63+
ret.extend(self.decrypt_and_verify(self._buffer[:self._declen], self._buffer[self._declen:self._declen+self.TAG_LENGTH]))
64+
del self._buffer[:self._declen+self.TAG_LENGTH]
65+
self._declen = None
66+
return bytes(ret)
67+
def encrypt(self, s):
68+
ret = bytearray()
69+
for i in range(0, len(s), 16*1024-1):
70+
buf = s[i:i+16*1024-1]
71+
len_chunk, len_tag = self.encrypt_and_digest(len(buf).to_bytes(2, 'big'))
72+
body_chunk, body_tag = self.encrypt_and_digest(buf)
73+
ret.extend(len_chunk+len_tag+body_chunk+body_tag)
74+
return bytes(ret)
75+
2976
class RC4_Cipher(BaseCipher):
3077
KEY_LENGTH = 16
3178
IV_LENGTH = 0
@@ -45,6 +92,8 @@ class ChaCha20_Cipher(BaseCipher):
4592
def setup(self):
4693
from Crypto.Cipher import ChaCha20
4794
self.cipher = ChaCha20.new(key=self.key, nonce=self.iv)
95+
class ChaCha20_IETF_Cipher(ChaCha20_Cipher):
96+
IV_LENGTH = 12
4897

4998
class Salsa20_Cipher(BaseCipher):
5099
KEY_LENGTH = 32
@@ -60,19 +109,15 @@ class AES_256_CFB_Cipher(BaseCipher):
60109
def setup(self):
61110
from Crypto.Cipher import AES
62111
self.cipher = AES.new(self.key, AES.MODE_CFB, iv=self.iv, segment_size=self.SEGMENT_SIZE)
63-
64112
class AES_128_CFB_Cipher(AES_256_CFB_Cipher):
65113
KEY_LENGTH = 16
66-
67114
class AES_192_CFB_Cipher(AES_256_CFB_Cipher):
68115
KEY_LENGTH = 24
69116

70117
class AES_256_CFB8_Cipher(AES_256_CFB_Cipher):
71118
SEGMENT_SIZE = 8
72-
73119
class AES_192_CFB8_Cipher(AES_256_CFB8_Cipher):
74120
KEY_LENGTH = 24
75-
76121
class AES_128_CFB8_Cipher(AES_256_CFB8_Cipher):
77122
KEY_LENGTH = 16
78123

@@ -82,13 +127,39 @@ class AES_256_OFB_Cipher(BaseCipher):
82127
def setup(self):
83128
from Crypto.Cipher import AES
84129
self.cipher = AES.new(self.key, AES.MODE_OFB, iv=self.iv)
85-
86130
class AES_192_OFB_Cipher(AES_256_OFB_Cipher):
87131
KEY_LENGTH = 24
88-
89132
class AES_128_OFB_Cipher(AES_256_OFB_Cipher):
90133
KEY_LENGTH = 16
91134

135+
class AES_256_CTR_Cipher(BaseCipher):
136+
KEY_LENGTH = 32
137+
IV_LENGTH = 16
138+
def setup(self):
139+
from Crypto.Cipher import AES
140+
self.cipher = AES.new(self.key, AES.MODE_CTR, nonce=b'', initial_value=self.iv)
141+
class AES_192_CTR_Cipher(AES_256_CTR_Cipher):
142+
KEY_LENGTH = 24
143+
class AES_128_CTR_Cipher(AES_256_CTR_Cipher):
144+
KEY_LENGTH = 16
145+
146+
class AES_256_GCM_Cipher(AEADCipher):
147+
KEY_LENGTH = 32
148+
IV_LENGTH = 32
149+
NONCE_LENGTH = 12
150+
TAG_LENGTH = 16
151+
def decrypt_and_verify(self, buffer, tag):
152+
return self.cipher_new(self.nonce).decrypt_and_verify(buffer, tag)
153+
def encrypt_and_digest(self, buffer):
154+
return self.cipher_new(self.nonce).encrypt_and_digest(buffer)
155+
def setup(self):
156+
from Crypto.Cipher import AES
157+
self.cipher_new = lambda nonce: AES.new(self.key, AES.MODE_GCM, nonce=nonce, mac_len=self.TAG_LENGTH)
158+
class AES_192_GCM_Cipher(AES_256_GCM_Cipher):
159+
KEY_LENGTH = IV_LENGTH = 24
160+
class AES_128_GCM_Cipher(AES_256_GCM_Cipher):
161+
KEY_LENGTH = IV_LENGTH = 16
162+
92163
class BF_CFB_Cipher(BaseCipher):
93164
KEY_LENGTH = 16
94165
IV_LENGTH = 8
@@ -118,7 +189,7 @@ def get_cipher(cipher_key):
118189
cipher_name, ota, _ = cipher.partition('!')
119190
if not key:
120191
return 'empty key', None
121-
if cipher_name not in MAP and cipher_name not in MAP_PY:
192+
if cipher_name not in MAP and cipher_name not in MAP_PY and not (cipher_name.endswith('-py') and cipher_name[:-3] in MAP_PY):
122193
return f'existing ciphers: {sorted(set(MAP)|set(MAP_PY))}', None
123194
key, ota = key.encode(), bool(ota) if ota else False
124195
cipher = MAP.get(cipher_name)
@@ -129,6 +200,9 @@ def get_cipher(cipher_key):
129200
cipher = None
130201
if cipher is None:
131202
cipher = MAP_PY.get(cipher_name)
203+
if cipher is None and cipher_name.endswith('-py'):
204+
cipher_name = cipher_name[:-3]
205+
cipher = MAP_PY.get(cipher_name)
132206
if cipher is None:
133207
return 'this cipher needs library: "pip3 install pycryptodome"', None
134208
def apply_cipher(reader, writer):

pproxy/cipherpy.py

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import hashlib, struct, base64
22

3-
from pproxy.cipher import BaseCipher
3+
from pproxy.cipher import BaseCipher, AEADCipher
44

55
# Pure Python Ciphers
66

@@ -62,8 +62,11 @@ def setup(self):
6262
class ChaCha20_Cipher(StreamCipher):
6363
KEY_LENGTH = 32
6464
IV_LENGTH = 8
65+
def __init__(self, key, ota=False, setup_key=True, *, counter=0):
66+
super().__init__(key, ota, setup_key)
67+
self.counter = counter
6568
def core(self):
66-
data = list(struct.unpack('<16I', b'expand 32-byte k' + self.key + self.iv.rjust(16, b'\x00')))
69+
data = list(struct.unpack('<16I', b'expand 32-byte k' + self.key + self.counter.to_bytes(4, 'little') + self.iv.rjust(12, b'\x00')))
6770
ORDERS = ((0,4,8,12),(1,5,9,13),(2,6,10,14),(3,7,11,15),(0,5,10,15),(1,6,11,12),(2,7,8,13),(3,4,9,14)) * 10
6871
while 1:
6972
H = data[:]
@@ -82,6 +85,33 @@ def core(self):
8285
class ChaCha20_IETF_Cipher(ChaCha20_Cipher):
8386
IV_LENGTH = 12
8487

88+
def poly1305(cipher_new, nonce, ciphertext):
89+
otk = cipher_new(nonce).encrypt(bytes(32))
90+
mac_data = ciphertext + bytes((-len(ciphertext))%16 + 8) + len(ciphertext).to_bytes(8, 'little')
91+
acc, r, s = 0, int.from_bytes(otk[:16], 'little') & 0x0ffffffc0ffffffc0ffffffc0fffffff, int.from_bytes(otk[16:], 'little')
92+
for i in range(0, len(mac_data), 16):
93+
acc = (r * (acc+int.from_bytes(mac_data[i:i+16]+b'\x01', 'little'))) % ((1<<130)-5)
94+
return ((acc + s) & ((1<<128)-1)).to_bytes(16, 'little')
95+
96+
class ChaCha20_IETF_POLY1305_Cipher(AEADCipher):
97+
PYTHON = True
98+
KEY_LENGTH = 32
99+
IV_LENGTH = 32
100+
NONCE_LENGTH = 12
101+
TAG_LENGTH = 16
102+
def process(self, s, tag=None):
103+
nonce = self.nonce
104+
if tag is not None:
105+
assert tag == poly1305(self.cipher_new, nonce, s)
106+
data = self.cipher_new(nonce, counter=1).encrypt(s)
107+
if tag is None:
108+
return data, poly1305(self.cipher_new, nonce, data)
109+
else:
110+
return data
111+
encrypt_and_digest = decrypt_and_verify = process
112+
def setup(self):
113+
self.cipher_new = lambda nonce, counter=0: ChaCha20_IETF_Cipher(self.key, setup_key=False, counter=counter).setup_iv(nonce)
114+
85115
class Salsa20_Cipher(StreamCipher):
86116
KEY_LENGTH = 32
87117
IV_LENGTH = 8
@@ -135,7 +165,7 @@ def core_bit(self, segment_bit):
135165
next_iv = int.from_bytes(self.iv, 'big')
136166
mask = (1 << self.IV_LENGTH*8) - 1
137167
while 1:
138-
data = self.cipher.encrypt(next_iv.to_bytes(self.IV_LENGTH, 'big'))
168+
data = self.cipher.encrypt(next_iv)
139169
next_iv = next_iv<<segment_bit & mask
140170
for i in range(segment_bit):
141171
next_iv |= (yield data[i//8]>>(7-i%8)&1)<<(segment_bit-1-i)
@@ -153,7 +183,7 @@ def setup(self):
153183
def core(self):
154184
next_iv = int.from_bytes(self.iv, 'big')
155185
while 1:
156-
yield from self.cipher.encrypt(next_iv.to_bytes(self.IV_LENGTH, 'big'))
186+
yield from self.cipher.encrypt(next_iv)
157187
next_iv = 0 if next_iv >= (1<<(self.IV_LENGTH*8))-1 else next_iv+1
158188

159189
class OFBCipher(CTRCipher):
@@ -163,6 +193,41 @@ def core(self):
163193
data = self.cipher.encrypt(data)
164194
yield from data
165195

196+
class GCMCipher(AEADCipher):
197+
PYTHON = True
198+
NONCE_LENGTH = 12
199+
TAG_LENGTH = 16
200+
def setup(self):
201+
self.cipher = self.CIPHER.new(self.key)
202+
self.hkey = []
203+
x = int.from_bytes(self.cipher.encrypt(0), 'big')
204+
for i in range(128):
205+
self.hkey.insert(0, x)
206+
x = (x>>1)^(0xe1<<120) if x&1 else x>>1
207+
def process(self, s, tag=None):
208+
def multh(y):
209+
z = 0
210+
for i in range(128):
211+
if y & (1<<i):
212+
z ^= self.hkey[i]
213+
return z
214+
def ghash(d):
215+
dt = d + bytes((-len(d))%16)
216+
z = 0
217+
for i in range(0, len(dt), 16):
218+
z = multh(z^int.from_bytes(dt[i:i+16], 'big'))
219+
return multh(z^(len(d)*8))
220+
z = int.from_bytes(self.nonce, 'big')<<32
221+
h = int.from_bytes(self.cipher.encrypt(z|1), 'big')
222+
if tag is not None:
223+
assert (ghash(s)^h).to_bytes(self.TAG_LENGTH, 'big') == tag
224+
ret = bytes(s[i*16+j]^o for i in range((len(s)+15)//16) for j, o in enumerate(self.cipher.encrypt(z|(i+2)&((1<<32)-1))) if i*16+j < len(s))
225+
if tag is None:
226+
return ret, (ghash(ret)^h).to_bytes(self.TAG_LENGTH, 'big')
227+
else:
228+
return ret
229+
encrypt_and_digest = decrypt_and_verify = process
230+
166231
class RAW:
167232
CACHE = {}
168233
@classmethod
@@ -191,15 +256,16 @@ def __init__(self, key):
191256
ekey.extend(m^ekey[i-size] for i, m in enumerate(t))
192257
self.ekey = tuple(ekey[i*16:i*16+16] for i in range(nbr+1))
193258
def encrypt(self, data):
259+
data = data.to_bytes(16, 'big') if isinstance(data, int) else data
194260
s = [data[j]^self.ekey[0][j] for j in range(16)]
195261
for key in self.ekey[1:-1]:
196262
s = [self.g2[s[a]]^self.g1[s[b]]^self.g1[s[c]]^self.g3[s[d]]^key[j] for j,a,b,c,d in self.shifts]
197263
return bytes([self.g1[s[self.shifts[j][1]]]^self.ekey[-1][j] for j in range(16)])
198264

199-
for method in (CFBCipher, CFB8Cipher, CFB1Cipher, CTRCipher, OFBCipher):
265+
for method in (CFBCipher, CFB8Cipher, CFB1Cipher, CTRCipher, OFBCipher, GCMCipher):
200266
for key in (32, 24, 16):
201267
name = 'AES_{}_{}_Cipher'.format(key*8, method.__name__[:-6])
202-
globals()[name] = type(name, (method,), dict(KEY_LENGTH=key, IV_LENGTH=16, CIPHER=AES))
268+
globals()[name] = type(name, (method,), dict(KEY_LENGTH=key, IV_LENGTH=key if method is GCMCipher else 16, CIPHER=AES))
203269

204270
class Blowfish(RAW):
205271
P = None
@@ -220,6 +286,7 @@ def __init__(self, key):
220286
buf = self.encrypt(buf)
221287
self.p[i:i+2] = struct.unpack('>II', buf)
222288
def encrypt(self, s):
289+
s = data.to_bytes(8, 'big') if isinstance(s, int) else s
223290
sl, sr = struct.unpack('>II', s)
224291
sl ^= self.p[0]
225292
for i in self.p[1:17]:
@@ -250,7 +317,7 @@ def __init__(self, key):
250317
e = [(q[n]<<m>>o|q[n]>>128-m+o)&(1<<64)-1 for n, m, o in ks]
251318
self.e = [e[i+i//7]<<64|e[i+i//7+1] if i%7==0 else e[i+i//7+1] for i in range(nr)]
252319
def encrypt(self, s):
253-
s = int.from_bytes(s, 'big')^self.e[0]
320+
s = (s if isinstance(s, int) else int.from_bytes(s, 'big'))^self.e[0]
254321
for idx, k in enumerate(self.e[1:-1]):
255322
s = s^((s&k)>>95&0xfffffffe|(s&k)>>127)<<64^((s&k)<<1&~1<<96^(s&k)>>31^s<<32|k<<32)&0xffffffff<<96 ^ ((s|k)&0xffffffff)<<32^((s|k)<<1^s>>31)&k>>31&0xfffffffe^((s|k)>>31^s>>63)&k>>63&1 if (idx+1)%7==0 else self.R(s, k)
256323
return (s>>64^(s&(1<<64)-1)<<64^self.e[-1]).to_bytes(16, 'big')
@@ -273,6 +340,7 @@ def __init__(self, key):
273340
e.append((e[i-8&0xf8|i+1&0x7]&0x7f)<<9|e[i-8&0xf8|i+2&0x7]>>7)
274341
self.e = [e[i*6:i*6+6] for i in range(9)]
275342
def encrypt(self, s):
343+
s = data.to_bytes(8, 'big') if isinstance(s, int) else s
276344
M = lambda a,b: (a*b-(a*b>>16)+(a*b&0xffff<a*b>>16) if a else 1-b if b else 1-a)&0xffff
277345
s0, s1, s2, s3 = struct.unpack('>4H', s)
278346
for e in self.e[:-1]:
@@ -299,6 +367,7 @@ def __init__(self, key):
299367
self.e.append((self.G((key0>>32)+(key1>>32)-kc), self.G(key0-key1+kc)))
300368
key0, key1 = (key0, (key1<<8|key1>>56)&(1<<64)-1) if i&1 else ((key0<<56|key0>>8)&(1<<64)-1, key1)
301369
def encrypt(self, s):
370+
s = data.to_bytes(16, 'big') if isinstance(s, int) else s
302371
s0, s1, s2, s3 = struct.unpack('>4I', s)
303372
for k0, k1 in self.e:
304373
t0 = self.G(s2^k0^s3^k1)
@@ -324,6 +393,7 @@ def __init__(self, key):
324393
e[i] = self.S[e[i+1]^e[i+len(key)]]
325394
self.e = struct.unpack('<64H', e)
326395
def encrypt(self, s):
396+
s = data.to_bytes(8, 'big') if isinstance(s, int) else s
327397
s = list(struct.unpack('<4H', s))
328398
for j in self.B:
329399
s[j&3] = s[j&3]+self.e[j]+(s[j+3&3]&s[j+2&3])+(~s[j+3&3]&s[j+1&3])<<j%4*4//3+1&0xffff|(s[j&3]+self.e[j]+(s[j+3&3]&s[j+2&3])+(~s[j+3&3]&s[j+1&3])&0xffff)>>15-j%4*4//3 if j>=0 else s[j]+self.e[s[j+3]&0x3f]

0 commit comments

Comments
 (0)