1+ #!/usr/bin/env python
2+
3+ # Copyright (c) 2014 clowwindy
4+ #
5+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6+ # of this software and associated documentation files (the "Software"), to deal
7+ # in the Software without restriction, including without limitation the rights
8+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+ # copies of the Software, and to permit persons to whom the Software is
10+ # furnished to do so, subject to the following conditions:
11+ #
12+ # The above copyright notice and this permission notice shall be included in
13+ # all copies or substantial portions of the Software.
14+ #
15+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+ # SOFTWARE.
22+
23+
24+
25+ import os
26+ import sys
27+ import hashlib
28+ import logging as xlog
29+
30+
31+ from scrypto import m2 , rc4_md5 , salsa20_ctr , ctypes_openssl , table
32+
33+
34+ method_supported = {}
35+ method_supported .update (rc4_md5 .ciphers )
36+ method_supported .update (salsa20_ctr .ciphers )
37+ method_supported .update (ctypes_openssl .ciphers )
38+ # let M2Crypto override ctypes_openssl
39+ method_supported .update (m2 .ciphers )
40+ method_supported .update (table .ciphers )
41+
42+
43+ def random_string (length ):
44+ try :
45+ import M2Crypto .Rand
46+ return M2Crypto .Rand .rand_bytes (length )
47+ except ImportError :
48+ return os .urandom (length )
49+
50+
51+ cached_keys = {}
52+
53+
54+ def try_cipher (key , method = None ):
55+ Encryptor (key , method )
56+
57+
58+ def EVP_BytesToKey (password , key_len , iv_len ):
59+ # equivalent to OpenSSL's EVP_BytesToKey() with count 1
60+ # so that we make the same key and iv as nodejs version
61+ if hasattr (password , 'encode' ):
62+ password = password .encode ('utf-8' )
63+ r = cached_keys .get (password , None )
64+ if r :
65+ return r
66+ m = []
67+ i = 0
68+ while len (b'' .join (m )) < (key_len + iv_len ):
69+ md5 = hashlib .md5 ()
70+ data = password
71+ if i > 0 :
72+ data = m [i - 1 ] + password
73+ md5 .update (data )
74+ m .append (md5 .digest ())
75+ i += 1
76+ ms = b'' .join (m )
77+ key = ms [:key_len ]
78+ iv = ms [key_len :key_len + iv_len ]
79+ cached_keys [password ] = (key , iv )
80+ return key , iv
81+
82+
83+ class Encryptor (object ):
84+ def __init__ (self , key , method ):
85+ self .key = key
86+ self .method = method
87+ self .iv = None
88+ self .iv_sent = False
89+ self .cipher_iv = b''
90+ self .decipher = None
91+ method = method .lower ()
92+ self ._method_info = self .get_method_info (method )
93+ if self ._method_info :
94+ self .cipher = self .get_cipher (key , method , 1 ,
95+ random_string (self ._method_info [1 ]))
96+ else :
97+ xlog .error ('method %s not supported' % method )
98+ sys .exit (1 )
99+
100+ def get_method_info (self , method ):
101+ method = method .lower ()
102+ m = method_supported .get (method )
103+ return m
104+
105+ def iv_len (self ):
106+ return len (self .cipher_iv )
107+
108+ def get_cipher (self , password , method , op , iv ):
109+ if hasattr (password , 'encode' ):
110+ password = password .encode ('utf-8' )
111+ m = self ._method_info
112+ if m [0 ] > 0 :
113+ key , iv_ = EVP_BytesToKey (password , m [0 ], m [1 ])
114+ else :
115+ # key_length == 0 indicates we should use the key directly
116+ key , iv = password , b''
117+
118+ iv = iv [:m [1 ]]
119+ if op == 1 :
120+ # this iv is for cipher not decipher
121+ self .cipher_iv = iv [:m [1 ]]
122+ return m [2 ](method , key , iv , op )
123+
124+ def encrypt (self , buf ):
125+ if len (buf ) == 0 :
126+ return buf
127+
128+ if not self .iv_sent :
129+ head = self .cipher_iv
130+ self .iv_sent = True
131+ else :
132+ head = ""
133+ return head + self .cipher .update (buf )
134+
135+ def decrypt (self , buf ):
136+ if len (buf ) == 0 :
137+ return buf
138+ if self .decipher is None :
139+ decipher_iv_len = self ._method_info [1 ]
140+ decipher_iv = buf [:decipher_iv_len ]
141+ self .decipher = self .get_cipher (self .key , self .method , 0 ,
142+ iv = decipher_iv )
143+ buf = buf [decipher_iv_len :]
144+ if len (buf ) == 0 :
145+ return buf
146+ return self .decipher .update (buf )
147+
148+ def encrypt_all (password , method , op , data ):
149+ result = []
150+ method = method .lower ()
151+ (key_len , iv_len , m ) = method_supported [method ]
152+ if key_len > 0 :
153+ key , _ = EVP_BytesToKey (password , key_len , iv_len )
154+ else :
155+ key = password
156+ if op :
157+ iv = random_string (iv_len )
158+ result .append (iv )
159+ else :
160+ iv = data [:iv_len ]
161+ data = data [iv_len :]
162+ cipher = m (method , key , iv , op )
163+ result .append (cipher .update (data ))
164+ return b'' .join (result )
165+
166+
167+ try :
168+ from Crypto .Cipher .ARC4 import new as RC4Cipher
169+ except ImportError :
170+ xlog .warn ('Load Crypto.Cipher.ARC4 Failed, Use Pure Python Instead.' )
171+ class RC4Cipher (object ):
172+ def __init__ (self , key ):
173+ x = 0
174+ box = list (range (256 ))
175+ for i , y in enumerate (box ):
176+ x = (x + y + ord (key [i % len (key )])) & 0xff
177+ box [i ], box [x ] = box [x ], y
178+ self .__box = box
179+ self .__x = 0
180+ self .__y = 0
181+ def encrypt (self , data ):
182+ out = []
183+ out_append = out .append
184+ x = self .__x
185+ y = self .__y
186+ box = self .__box
187+ for char in data :
188+ x = (x + 1 ) & 0xff
189+ y = (y + box [x ]) & 0xff
190+ box [x ], box [y ] = box [y ], box [x ]
191+ out_append (chr (ord (char ) ^ box [(box [x ] + box [y ]) & 0xff ]))
192+ self .__x = x
193+ self .__y = y
194+ return '' .join (out )
0 commit comments