Skip to content

Commit df95045

Browse files
authored
Add files via upload
1 parent 3c1210d commit df95045

3 files changed

Lines changed: 443 additions & 3 deletions

File tree

pproxy/__init__.py

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

44
__title__ = 'pproxy'
5-
__version__ = "0.9.6"
5+
__version__ = "0.9.9"
66
__description__ = "Proxy server that can tunnel among remote servers by regex rules."
77
__author__ = "Qian Wenjie"
88
__license__ = "MIT License"
@@ -80,8 +80,8 @@ def uri_compile(uri):
8080
sslclient = None
8181
cipher, _, loc = url.netloc.rpartition('@')
8282
if cipher:
83-
from pproxy import ciphers
84-
cipher = ciphers.get_cipher(cipher)
83+
from pproxy.cipher import get_cipher
84+
cipher = get_cipher(cipher)
8585
match = pattern_compile(url.query) if url.query else None
8686
if loc:
8787
host, _, port = loc.partition(':')

pproxy/cipher.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import os, hashlib, argparse, hmac
2+
3+
class BaseCipher(object):
4+
LIBRARY = True
5+
CACHE = {}
6+
def __init__(self, key, ota=False):
7+
if self.KEY_LENGTH > 0:
8+
self.key = self.CACHE.get(b'key'+key)
9+
if self.key is None:
10+
keybuf = []
11+
while len(b''.join(keybuf)) < self.KEY_LENGTH:
12+
keybuf.append(hashlib.md5((keybuf[-1] if keybuf else b'') + key).digest())
13+
self.key = self.CACHE[b'key'+key] = b''.join(keybuf)[:self.KEY_LENGTH]
14+
else:
15+
self.key = key
16+
self.ota = ota
17+
self.iv = None
18+
def setup_iv(self, iv=None):
19+
self.iv = os.urandom(self.IV_LENGTH) if iv is None else iv
20+
self.setup()
21+
def decrypt(self, s):
22+
return self.cipher.decrypt(s)
23+
def encrypt(self, s):
24+
return self.cipher.encrypt(s)
25+
def patch_ota_reader(self, reader):
26+
chunk_id = 0
27+
async def patched_read():
28+
nonlocal chunk_id
29+
try:
30+
data_len = int.from_bytes(await reader.readexactly(2), 'big')
31+
except Exception:
32+
return None
33+
checksum = await reader.readexactly(10)
34+
data = await reader.readexactly(data_len)
35+
checksum_server = hmac.new(self.iv+chunk_id.to_bytes(4, 'big'), data, 'sha1').digest()
36+
assert checksum_server[:10] == checksum
37+
chunk_id += 1
38+
return data
39+
reader.read_ = patched_read
40+
def patch_ota_writer(self, writer):
41+
chunk_id = 0
42+
write = writer.write
43+
def patched_write(data):
44+
nonlocal chunk_id
45+
if not data: return
46+
checksum = hmac.new(self.iv+chunk_id.to_bytes(4, 'big'), data, 'sha1').digest()
47+
chunk_id += 1
48+
return write(len(data).to_bytes(2, 'big') + checksum[:10] + data)
49+
writer.write = patched_write
50+
51+
class RC4_Cipher(BaseCipher):
52+
KEY_LENGTH = 16
53+
IV_LENGTH = 0
54+
def setup(self):
55+
from Crypto.Cipher import ARC4
56+
self.cipher = ARC4.new(self.key)
57+
58+
class RC4_MD5_Cipher(RC4_Cipher):
59+
IV_LENGTH = 16
60+
def setup(self):
61+
self.key = hashlib.md5(self.key + self.iv).digest()
62+
RC4_Cipher.setup(self)
63+
64+
class ChaCha20_Cipher(BaseCipher):
65+
KEY_LENGTH = 32
66+
IV_LENGTH = 8
67+
def setup(self):
68+
from Crypto.Cipher import ChaCha20
69+
self.cipher = ChaCha20.new(key=self.key, nonce=self.iv)
70+
71+
class Salsa20_Cipher(BaseCipher):
72+
KEY_LENGTH = 32
73+
IV_LENGTH = 8
74+
def setup(self):
75+
from Crypto.Cipher import Salsa20
76+
self.cipher = Salsa20.new(key=self.key, nonce=self.iv)
77+
78+
class AES_256_CFB_Cipher(BaseCipher):
79+
KEY_LENGTH = 32
80+
IV_LENGTH = 16
81+
SEGMENT_SIZE = 128
82+
def setup(self):
83+
from Crypto.Cipher import AES
84+
self.cipher = AES.new(self.key, AES.MODE_CFB, iv=self.iv, segment_size=self.SEGMENT_SIZE)
85+
86+
class AES_128_CFB_Cipher(AES_256_CFB_Cipher):
87+
KEY_LENGTH = 16
88+
89+
class AES_192_CFB_Cipher(AES_256_CFB_Cipher):
90+
KEY_LENGTH = 24
91+
92+
class AES_256_CFB8_Cipher(AES_256_CFB_Cipher):
93+
SEGMENT_SIZE = 8
94+
95+
class AES_192_CFB8_Cipher(AES_256_CFB8_Cipher):
96+
KEY_LENGTH = 24
97+
98+
class AES_128_CFB8_Cipher(AES_256_CFB8_Cipher):
99+
KEY_LENGTH = 16
100+
101+
class BF_CFB_Cipher(BaseCipher):
102+
KEY_LENGTH = 16
103+
IV_LENGTH = 8
104+
def setup(self):
105+
from Crypto.Cipher import Blowfish
106+
self.cipher = Blowfish.new(self.key, Blowfish.MODE_CFB, iv=self.iv, segment_size=64)
107+
108+
class CAST5_CFB_Cipher(BaseCipher):
109+
KEY_LENGTH = 16
110+
IV_LENGTH = 8
111+
def setup(self):
112+
from Crypto.Cipher import CAST
113+
self.cipher = CAST.new(self.key, CAST.MODE_CFB, iv=self.iv, segment_size=64)
114+
115+
class DES_CFB_Cipher(BaseCipher):
116+
KEY_LENGTH = 8
117+
IV_LENGTH = 8
118+
def setup(self):
119+
from Crypto.Cipher import DES
120+
self.cipher = DES.new(self.key, DES.MODE_CFB, iv=self.iv, segment_size=64)
121+
122+
MAP = {name[:-7].replace('_', '-').lower(): cls for name, cls in globals().items() if name.endswith('_Cipher')}
123+
124+
def get_cipher(cipher_key):
125+
from pproxy.cipherpy import MAP as MAP2
126+
CIPHER_MAP = dict(list(MAP.items())+list(MAP2.items()))
127+
cipher, _, key = cipher_key.partition(':')
128+
cipher_name, ota, _ = cipher.partition('!')
129+
if not key:
130+
raise argparse.ArgumentTypeError('empty key')
131+
if cipher_name not in CIPHER_MAP:
132+
raise argparse.ArgumentTypeError(f'existing ciphers: {list(sorted(CIPHER_MAP.keys()))}')
133+
cipher, key, ota = CIPHER_MAP[cipher_name], key.encode(), bool(ota) if ota else False
134+
if cipher.LIBRARY:
135+
try:
136+
assert __import__('Crypto').version_info >= (3, 4)
137+
except Exception:
138+
if cipher_name+'-py' in CIPHER_MAP:
139+
cipher = CIPHER_MAP[cipher_name+'-py']
140+
print(f'Switch to python cipher [{cipher_name}-py]')
141+
else:
142+
raise argparse.ArgumentTypeError(f'this cipher needs library: "pip3 install pycryptodome"')
143+
def apply_cipher(reader, writer):
144+
reader_cipher, writer_cipher = cipher(key, ota=ota), cipher(key, ota=ota)
145+
reader_cipher._buffer = b''
146+
def feed_data(s, o=reader.feed_data):
147+
if not reader_cipher.iv:
148+
s = reader_cipher._buffer + s
149+
if len(s) >= reader_cipher.IV_LENGTH:
150+
reader_cipher.setup_iv(s[:reader_cipher.IV_LENGTH])
151+
o(reader_cipher.decrypt(s[reader_cipher.IV_LENGTH:]))
152+
else:
153+
reader_cipher._buffer = s
154+
else:
155+
o(reader_cipher.decrypt(s))
156+
def write(s, o=writer.write):
157+
if not s:
158+
return
159+
if not writer_cipher.iv:
160+
writer_cipher.setup_iv()
161+
o(writer_cipher.iv)
162+
return o(writer_cipher.encrypt(s))
163+
reader.feed_data = feed_data
164+
writer.write = write
165+
return reader_cipher, writer_cipher
166+
apply_cipher.ota = ota
167+
return apply_cipher
168+

0 commit comments

Comments
 (0)