encrypt.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2012-2015 clowwindy
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  6. # not use this file except in compliance with the License. You may obtain
  7. # a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. # License for the specific language governing permissions and limitations
  15. # under the License.
  16. from __future__ import absolute_import, division, print_function, \
  17. with_statement
  18. import os
  19. import sys
  20. import hashlib
  21. import logging
  22. from shadowsocks import common
  23. from shadowsocks.crypto import rc4_md5, openssl, sodium, table
  24. method_supported = {}
  25. method_supported.update(rc4_md5.ciphers)
  26. method_supported.update(openssl.ciphers)
  27. method_supported.update(sodium.ciphers)
  28. method_supported.update(table.ciphers)
  29. def random_string(length):
  30. try:
  31. return os.urandom(length)
  32. except NotImplementedError as e:
  33. return openssl.rand_bytes(length)
  34. cached_keys = {}
  35. def try_cipher(key, method=None):
  36. Encryptor(key, method)
  37. def EVP_BytesToKey(password, key_len, iv_len):
  38. # equivalent to OpenSSL's EVP_BytesToKey() with count 1
  39. # so that we make the same key and iv as nodejs version
  40. if hasattr(password, 'encode'):
  41. password = password.encode('utf-8')
  42. cached_key = '%s-%d-%d' % (password, key_len, iv_len)
  43. r = cached_keys.get(cached_key, None)
  44. if r:
  45. return r
  46. m = []
  47. i = 0
  48. while len(b''.join(m)) < (key_len + iv_len):
  49. md5 = hashlib.md5()
  50. data = password
  51. if i > 0:
  52. data = m[i - 1] + password
  53. md5.update(data)
  54. m.append(md5.digest())
  55. i += 1
  56. ms = b''.join(m)
  57. key = ms[:key_len]
  58. iv = ms[key_len:key_len + iv_len]
  59. cached_keys[cached_key] = (key, iv)
  60. return key, iv
  61. class Encryptor(object):
  62. def __init__(self, key, method, iv = None):
  63. self.key = key
  64. self.method = method
  65. self.iv = None
  66. self.iv_sent = False
  67. self.cipher_iv = b''
  68. self.iv_buf = b''
  69. self.cipher_key = b''
  70. self.decipher = None
  71. method = method.lower()
  72. self._method_info = self.get_method_info(method)
  73. if self._method_info:
  74. if iv is None or len(iv) != self._method_info[1]:
  75. self.cipher = self.get_cipher(key, method, 1,
  76. random_string(self._method_info[1]))
  77. else:
  78. self.cipher = self.get_cipher(key, method, 1, iv)
  79. else:
  80. logging.error('method %s not supported' % method)
  81. sys.exit(1)
  82. def get_method_info(self, method):
  83. method = method.lower()
  84. m = method_supported.get(method)
  85. return m
  86. def iv_len(self):
  87. return len(self.cipher_iv)
  88. def get_cipher(self, password, method, op, iv):
  89. password = common.to_bytes(password)
  90. m = self._method_info
  91. if m[0] > 0:
  92. key, iv_ = EVP_BytesToKey(password, m[0], m[1])
  93. else:
  94. # key_length == 0 indicates we should use the key directly
  95. key, iv = password, b''
  96. iv = iv[:m[1]]
  97. if op == 1:
  98. # this iv is for cipher not decipher
  99. self.cipher_iv = iv[:m[1]]
  100. self.cipher_key = key
  101. return m[2](method, key, iv, op)
  102. def encrypt(self, buf):
  103. if len(buf) == 0:
  104. return buf
  105. if self.iv_sent:
  106. return self.cipher.update(buf)
  107. else:
  108. self.iv_sent = True
  109. return self.cipher_iv + self.cipher.update(buf)
  110. def decrypt(self, buf):
  111. if len(buf) == 0:
  112. return buf
  113. if self.decipher is not None: #optimize
  114. return self.decipher.update(buf)
  115. decipher_iv_len = self._method_info[1]
  116. if len(self.iv_buf) <= decipher_iv_len:
  117. self.iv_buf += buf
  118. if len(self.iv_buf) > decipher_iv_len:
  119. decipher_iv = self.iv_buf[:decipher_iv_len]
  120. self.decipher = self.get_cipher(self.key, self.method, 0,
  121. iv=decipher_iv)
  122. buf = self.iv_buf[decipher_iv_len:]
  123. del self.iv_buf
  124. return self.decipher.update(buf)
  125. else:
  126. return b''
  127. def encrypt_all(password, method, op, data):
  128. result = []
  129. method = method.lower()
  130. (key_len, iv_len, m) = method_supported[method]
  131. if key_len > 0:
  132. key, _ = EVP_BytesToKey(password, key_len, iv_len)
  133. else:
  134. key = password
  135. if op:
  136. iv = random_string(iv_len)
  137. result.append(iv)
  138. else:
  139. iv = data[:iv_len]
  140. data = data[iv_len:]
  141. cipher = m(method, key, iv, op)
  142. result.append(cipher.update(data))
  143. return b''.join(result)
  144. def encrypt_key(password, method):
  145. method = method.lower()
  146. (key_len, iv_len, m) = method_supported[method]
  147. if key_len > 0:
  148. key, _ = EVP_BytesToKey(password, key_len, iv_len)
  149. else:
  150. key = password
  151. return key
  152. def encrypt_iv_len(method):
  153. method = method.lower()
  154. (key_len, iv_len, m) = method_supported[method]
  155. return iv_len
  156. def encrypt_new_iv(method):
  157. method = method.lower()
  158. (key_len, iv_len, m) = method_supported[method]
  159. return random_string(iv_len)
  160. def encrypt_all_iv(key, method, op, data, ref_iv):
  161. result = []
  162. method = method.lower()
  163. (key_len, iv_len, m) = method_supported[method]
  164. if op:
  165. iv = ref_iv[0]
  166. result.append(iv)
  167. else:
  168. iv = data[:iv_len]
  169. data = data[iv_len:]
  170. ref_iv[0] = iv
  171. cipher = m(method, key, iv, op)
  172. result.append(cipher.update(data))
  173. return b''.join(result)
  174. CIPHERS_TO_TEST = [
  175. 'aes-128-cfb',
  176. 'aes-256-cfb',
  177. 'rc4-md5',
  178. 'salsa20',
  179. 'chacha20',
  180. 'table',
  181. ]
  182. def test_encryptor():
  183. from os import urandom
  184. plain = urandom(10240)
  185. for method in CIPHERS_TO_TEST:
  186. logging.warn(method)
  187. encryptor = Encryptor(b'key', method)
  188. decryptor = Encryptor(b'key', method)
  189. cipher = encryptor.encrypt(plain)
  190. plain2 = decryptor.decrypt(cipher)
  191. assert plain == plain2
  192. def test_encrypt_all():
  193. from os import urandom
  194. plain = urandom(10240)
  195. for method in CIPHERS_TO_TEST:
  196. logging.warn(method)
  197. cipher = encrypt_all(b'key', method, 1, plain)
  198. plain2 = encrypt_all(b'key', method, 0, cipher)
  199. assert plain == plain2
  200. if __name__ == '__main__':
  201. test_encrypt_all()
  202. test_encryptor()