aes.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. # -*- coding: utf-8 -*-
  2. """
  3. # AES wrapper
  4. # Copyright (c) 2023-2024 Michael Büsch <m@bues.ch>
  5. # Licensed under the GNU/GPL version 2 or later.
  6. """
  7. from libpwman.exception import PWManError
  8. import os
  9. __all__ = [
  10. "AES",
  11. ]
  12. class AES:
  13. """Abstraction layer for the AES implementation.
  14. """
  15. BLOCK_SIZE = 128 // 8
  16. __singleton = None
  17. @classmethod
  18. def get(cls):
  19. """Get the AES singleton.
  20. """
  21. if cls.__singleton is None:
  22. cls.__singleton = cls()
  23. return cls.__singleton
  24. def __init__(self):
  25. self.__pyaes = None
  26. self.__cryptodome = None
  27. cryptolib = os.getenv("PWMAN_CRYPTOLIB", "").lower().strip()
  28. if cryptolib in ("", "cryptodome"):
  29. # Try to use Cryptodome
  30. try:
  31. import Cryptodome
  32. import Cryptodome.Cipher.AES
  33. import Cryptodome.Util.Padding
  34. self.__cryptodome = Cryptodome
  35. return
  36. except ImportError as e:
  37. pass
  38. if cryptolib in ("", "pyaes"):
  39. # Try to use pyaes
  40. try:
  41. import pyaes
  42. self.__pyaes = pyaes
  43. return
  44. except ImportError as e:
  45. pass
  46. msg = "Python module import error."
  47. if cryptolib == "":
  48. msg += "\nNeither 'Cryptodome' nor 'pyaes' is installed."
  49. else:
  50. msg += "\n'PWMAN_CRYPTOLIB=%s' is not supported or not installed." % cryptolib
  51. raise PWManError(msg)
  52. def encrypt(self, key, iv, data):
  53. """Encrypt data.
  54. """
  55. # Check parameters.
  56. if len(key) != 256 // 8:
  57. raise PWManError("AES: Invalid key length.")
  58. if len(iv) != self.BLOCK_SIZE:
  59. raise PWManError("AES: Invalid iv length.")
  60. if len(data) <= 0:
  61. raise PWManError("AES: Invalid data length.")
  62. try:
  63. if self.__cryptodome is not None:
  64. # Use Cryptodome
  65. padData = self.__cryptodome.Util.Padding.pad(
  66. data_to_pad=data,
  67. block_size=self.BLOCK_SIZE,
  68. style="pkcs7")
  69. cipher = self.__cryptodome.Cipher.AES.new(
  70. key=key,
  71. mode=self.__cryptodome.Cipher.AES.MODE_CBC,
  72. iv=iv)
  73. encData = cipher.encrypt(padData)
  74. return encData
  75. if self.__pyaes is not None:
  76. # Use pyaes
  77. mode = self.__pyaes.AESModeOfOperationCBC(key=key, iv=iv)
  78. padding = self.__pyaes.PADDING_DEFAULT
  79. enc = self.__pyaes.Encrypter(mode=mode, padding=padding)
  80. encData = enc.feed(data)
  81. encData += enc.feed()
  82. return encData
  83. except Exception as e:
  84. raise PWManError("AES error: %s: %s" % (type(e), str(e)))
  85. raise PWManError("AES not implemented.")
  86. def decrypt(self, key, iv, data, legacyPadding=False):
  87. """Decrypt data.
  88. """
  89. # Check parameters.
  90. if len(key) != 256 // 8:
  91. raise PWManError("AES: Invalid key length.")
  92. if len(iv) != self.BLOCK_SIZE:
  93. raise PWManError("AES: Invalid iv length.")
  94. if len(data) <= 0:
  95. raise PWManError("AES: Invalid data length.")
  96. try:
  97. if self.__cryptodome is not None:
  98. # Use Cryptodome
  99. cipher = self.__cryptodome.Cipher.AES.new(
  100. key=key,
  101. mode=self.__cryptodome.Cipher.AES.MODE_CBC,
  102. iv=iv)
  103. decData = cipher.decrypt(data)
  104. if legacyPadding:
  105. unpadData = self.__unpadLegacy(decData)
  106. else:
  107. unpadData = self.__cryptodome.Util.Padding.unpad(
  108. padded_data=decData,
  109. block_size=self.BLOCK_SIZE,
  110. style="pkcs7")
  111. return unpadData
  112. if self.__pyaes is not None:
  113. # Use pyaes
  114. mode = self.__pyaes.AESModeOfOperationCBC(key=key, iv=iv)
  115. if legacyPadding:
  116. padding = self.__pyaes.PADDING_NONE
  117. else:
  118. padding = self.__pyaes.PADDING_DEFAULT
  119. dec = self.__pyaes.Decrypter(mode=mode, padding=padding)
  120. decData = dec.feed(data)
  121. decData += dec.feed()
  122. if legacyPadding:
  123. unpadData = self.__unpadLegacy(decData)
  124. else:
  125. unpadData = decData
  126. return unpadData
  127. except Exception as e:
  128. raise PWManError("AES error: %s: %s" % (type(e), str(e)))
  129. raise PWManError("AES not implemented.")
  130. @staticmethod
  131. def __unpadLegacy(data):
  132. """Strip legacy padding.
  133. """
  134. index = data.rfind(b"\xFF")
  135. if index < 0 or index >= len(data):
  136. raise PWManError("Legacy padding: Did not find start.")
  137. return data[:index]
  138. @classmethod
  139. def quickSelfTest(cls):
  140. inst = cls.get()
  141. enc = inst.encrypt(key=(b"_keykey_" * 4), iv=(b"iv" * 8), data=b"pwman")
  142. if enc != bytes.fromhex("cf73a286509e1265d26490a76dcbb2fd"):
  143. raise PWManError("AES encrypt: Quick self test failed.")
  144. dec = inst.decrypt(key=(b"_keykey_" * 4), iv=(b"iv" * 8), data=enc)
  145. if dec != b"pwman":
  146. raise PWManError("AES decrypt: Quick self test failed.")