gcm.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. #!/usr/bin/env python
  2. """
  3. Copyright (C) 2013 Bo Zhu http://about.bozhu.me
  4. Permission is hereby granted, free of charge, to any person obtaining a
  5. copy of this software and associated documentation files (the "Software"),
  6. to deal in the Software without restriction, including without limitation
  7. the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. and/or sell copies of the Software, and to permit persons to whom the
  9. Software is furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  15. THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  17. FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  18. DEALINGS IN THE SOFTWARE.
  19. """
  20. from tls.Crypto.Cipher import AES
  21. from tls.Crypto.Util import Counter
  22. from tls.Crypto.Util.number import long_to_bytes, bytes_to_long
  23. # GF(2^128) defined by 1 + a + a^2 + a^7 + a^128
  24. # Please note the MSB is x0 and LSB is x127
  25. def gf_2_128_mul(x, y):
  26. assert x < (1 << 128)
  27. assert y < (1 << 128)
  28. res = 0
  29. for i in range(127, -1, -1):
  30. res ^= x * ((y >> i) & 1) # branchless
  31. x = (x >> 1) ^ ((x & 1) * 0xE1000000000000000000000000000000)
  32. assert res < 1 << 128
  33. return res
  34. class InvalidInputException(Exception):
  35. def __init__(self, msg):
  36. self.msg = msg
  37. def __str__(self):
  38. return str(self.msg)
  39. class InvalidTagException(Exception):
  40. def __str__(self):
  41. return 'The authenticaiton tag is invalid.'
  42. # Galois/Counter Mode with AES-128 and 96-bit IV
  43. class AES_GCM:
  44. def __init__(self, master_key):
  45. self.change_key(master_key)
  46. def change_key(self, master_key):
  47. if master_key >= (1 << 128):
  48. raise InvalidInputException('Master key should be 128-bit')
  49. self.__master_key = long_to_bytes(master_key, 16)
  50. self.__aes_ecb = AES.new(self.__master_key, AES.MODE_ECB)
  51. self.__auth_key = bytes_to_long(self.__aes_ecb.encrypt(b'\x00' * 16))
  52. # precompute the table for multiplication in finite field
  53. table = [] # for 8-bit
  54. for i in range(16):
  55. row = []
  56. for j in range(256):
  57. row.append(gf_2_128_mul(self.__auth_key, j << (8 * i)))
  58. table.append(tuple(row))
  59. self.__pre_table = tuple(table)
  60. self.prev_init_value = None # reset
  61. def __times_auth_key(self, val):
  62. res = 0
  63. for i in range(16):
  64. res ^= self.__pre_table[i][val & 0xFF]
  65. val >>= 8
  66. return res
  67. def __ghash(self, aad, txt):
  68. len_aad = len(aad)
  69. len_txt = len(txt)
  70. # padding
  71. if 0 == len_aad % 16:
  72. data = aad
  73. else:
  74. data = aad + b'\x00' * (16 - len_aad % 16)
  75. if 0 == len_txt % 16:
  76. data += txt
  77. else:
  78. data += txt + b'\x00' * (16 - len_txt % 16)
  79. tag = 0
  80. assert len(data) % 16 == 0
  81. for i in range(len(data) // 16):
  82. tag ^= bytes_to_long(data[i * 16: (i + 1) * 16])
  83. tag = self.__times_auth_key(tag)
  84. # print 'X\t', hex(tag)
  85. tag ^= ((8 * len_aad) << 64) | (8 * len_txt)
  86. tag = self.__times_auth_key(tag)
  87. return tag
  88. def encrypt(self, init_value, plaintext, auth_data=b''):
  89. if init_value >= (1 << 96):
  90. raise InvalidInputException('IV should be 96-bit')
  91. # a naive checking for IV reuse
  92. if init_value == self.prev_init_value:
  93. raise InvalidInputException('IV must not be reused!')
  94. self.prev_init_value = init_value
  95. len_plaintext = len(plaintext)
  96. # len_auth_data = len(auth_data)
  97. if len_plaintext > 0:
  98. counter = Counter.new(
  99. nbits=32,
  100. prefix=long_to_bytes(init_value, 12),
  101. initial_value=2, # notice this
  102. allow_wraparound=False)
  103. aes_ctr = AES.new(self.__master_key, AES.MODE_CTR, counter=counter)
  104. if 0 != len_plaintext % 16:
  105. padded_plaintext = plaintext + \
  106. b'\x00' * (16 - len_plaintext % 16)
  107. else:
  108. padded_plaintext = plaintext
  109. ciphertext = aes_ctr.encrypt(padded_plaintext)[:len_plaintext]
  110. else:
  111. ciphertext = b''
  112. auth_tag = self.__ghash(auth_data, ciphertext)
  113. # print 'GHASH\t', hex(auth_tag)
  114. auth_tag ^= bytes_to_long(self.__aes_ecb.encrypt(
  115. long_to_bytes((init_value << 32) | 1, 16)))
  116. # assert len(ciphertext) == len(plaintext)
  117. assert auth_tag < (1 << 128)
  118. return ciphertext, auth_tag
  119. def decrypt(self, init_value, ciphertext, auth_tag, auth_data=b''):
  120. if init_value >= (1 << 96):
  121. raise InvalidInputException('IV should be 96-bit')
  122. if auth_tag >= (1 << 128):
  123. raise InvalidInputException('Tag should be 128-bit')
  124. if auth_tag != self.__ghash(auth_data, ciphertext) ^ \
  125. bytes_to_long(self.__aes_ecb.encrypt(
  126. long_to_bytes((init_value << 32) | 1, 16))):
  127. raise InvalidTagException
  128. len_ciphertext = len(ciphertext)
  129. if len_ciphertext > 0:
  130. counter = Counter.new(
  131. nbits=32,
  132. prefix=long_to_bytes(init_value, 12),
  133. initial_value=2,
  134. allow_wraparound=True)
  135. aes_ctr = AES.new(self.__master_key, AES.MODE_CTR, counter=counter)
  136. if 0 != len_ciphertext % 16:
  137. padded_ciphertext = ciphertext + \
  138. b'\x00' * (16 - len_ciphertext % 16)
  139. else:
  140. padded_ciphertext = ciphertext
  141. plaintext = aes_ctr.decrypt(padded_ciphertext)[:len_ciphertext]
  142. else:
  143. plaintext = b''
  144. return plaintext
  145. if __name__ == '__main__':
  146. master_key = 0xfeffe9928665731c6d6a8f9467308308
  147. plaintext = b'\xd9\x31\x32\x25\xf8\x84\x06\xe5' + \
  148. b'\xa5\x59\x09\xc5\xaf\xf5\x26\x9a' + \
  149. b'\x86\xa7\xa9\x53\x15\x34\xf7\xda' + \
  150. b'\x2e\x4c\x30\x3d\x8a\x31\x8a\x72' + \
  151. b'\x1c\x3c\x0c\x95\x95\x68\x09\x53' + \
  152. b'\x2f\xcf\x0e\x24\x49\xa6\xb5\x25' + \
  153. b'\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57' + \
  154. b'\xba\x63\x7b\x39'
  155. auth_data = b'\xfe\xed\xfa\xce\xde\xad\xbe\xef' + \
  156. b'\xfe\xed\xfa\xce\xde\xad\xbe\xef' + \
  157. b'\xab\xad\xda\xd2'
  158. init_value = 0xcafebabefacedbaddecaf888
  159. ciphertext = b'\x42\x83\x1e\xc2\x21\x77\x74\x24' + \
  160. b'\x4b\x72\x21\xb7\x84\xd0\xd4\x9c' + \
  161. b'\xe3\xaa\x21\x2f\x2c\x02\xa4\xe0' + \
  162. b'\x35\xc1\x7e\x23\x29\xac\xa1\x2e' + \
  163. b'\x21\xd5\x14\xb2\x54\x66\x93\x1c' + \
  164. b'\x7d\x8f\x6a\x5a\xac\x84\xaa\x05' + \
  165. b'\x1b\xa3\x0b\x39\x6a\x0a\xac\x97' + \
  166. b'\x3d\x58\xe0\x91'
  167. auth_tag = 0x5bc94fbc3221a5db94fae95ae7121a47
  168. # print('plaintext:', hex(bytes_to_long(plaintext)))
  169. my_gcm = AES_GCM(master_key)
  170. encrypted, new_tag = my_gcm.encrypt(init_value, plaintext, auth_data)
  171. # print('encrypted:', hex(bytes_to_long(encrypted)))
  172. # print('auth tag: ', hex(new_tag))
  173. try:
  174. decrypted = my_gcm.decrypt(init_value, encrypted,
  175. new_tag + 1, auth_data)
  176. except InvalidTagException:
  177. decrypted = my_gcm.decrypt(init_value, encrypted, new_tag, auth_data)
  178. print('decrypted:', hex(bytes_to_long(decrypted)))