Aes

將 HMAC 與不需要填充的 AES 模式一起使用

  • July 22, 2020

我正在嘗試將 HMAC 與不需要任何填充的 AES 模式一起使用。雖然我知道類似AES-GCM並且AES-EAX已經提供身份驗證的模式,但我想知道 HMAC 是否適用於不提供任何身份驗證的模式。這是我嘗試過的:

編碼

from Cryptodome.Cipher import AES
import hmac


class DecryptionError(Exception):
   pass


class AESNonAEAD:
   def __init__(self, is_encrypting, key, mode, iv_or_nonce, digestmod='sha256'):
       self._is_encrypting = is_encrypting

       # Use KDF to derive key material from given key:
       # length of crp == length of key
       # length of authkey == (digest size of hashalgo for HMAC // 8)
       crpkey, authkey = derive_key_material(key, digestmod)

       self._auth = hmac.new(authkey, digestmod=digestmod)
       self._cipher = AES.new(key, mode, iv_or_nonce)
       if is_encrypting:
           self._update = self._cipher.encrypt
       else:
           self._update = self._cipher.decrypt

       self._auth.update(iv_or_nonce)

       self._updated = False
       self._len_ct = 0
       self._len_aad = 0

   def authenticate(self, data):
       if self._updated:
           raise ValueError('update has already been called')
       self._auth.update(data)
       self._len_aad += len(data)

   def _pad_aad(self):
       # pad to a multiple of 16 bytes
       if self._len_aad & 0x0F:
           self._auth.update(bytes(16 - (self._len_aad & 0x0F)))

   def update(self):
       if not self._updated:
           self._pad_aad()
           self._updated = True

       if not self._is_encrypting:
           self._auth.update(data)

       res = self._update(data)
       self._len_ct += len(res)

       if self._is_encrypting:
           self._auth.update(res)
       return res

   def finalize(self, tag=None):
       if not self._is_encrypting and tag is None:
           raise ValueError('tag is required')

       # pad to a multiple of 16 bytes
       if self._len_ct & 0x0F:
          self._auth.update(bytes(16 - (self._len_ct & 0x0F)))
       # include length of aad and ciphertext
       self._auth.update(self._len_aad.to_bytes(8, 'little'))
       self._auth.update(self._len_ct.to_bytes(8, 'little'))

       if self._is_encrypting:
           return
       if not hmac.compare_digest(tag, self._auth.digest()):
           raise DecryptionError

   def calcuate_tag(self):
       if self._is_encrypting:
           return self._hmac.digest()

用法

加密

from Cryptodome.Cipher import AES

key = os.urandom(32)
iv = os.urandom(16)
enc = AESNonAEAD(True, key, AES.MODE_CFB, iv)  # or MODE_CTR

enc.authenticate(b'yes this')
encdata = enc.update(b'no not this')
enc.finalize()
tag = enc.calculate_tag()

解密

dec = AESNonAEAD(False, key, AES.MODE_CFB, iv)  # or MODE_CTR

dec.authenticate(b'yes this')
decdata = dec.update(encdata)

問題:

  • 這種實現是否可以接受?
  • 這是使用 HMAC 的正確方法嗎?是否有必要為非填充密碼模式填充 HMAC?
  • 這個相同的方案可以與 Camellia 密碼一起使用嗎?

如果我理解正確,您對 HMAC 的輸入是密文 $ c $ , 用空字節填充(到 16 字節的倍數):

   # pad to a multiple of 16 bytes
   if self._len_ct & 0x0F:
      self._auth.update(bytes(16 - (self._len_ct & 0x0F)))

作為一個具體的例子,假設密文是 $ c= $ deadbeef00,那麼您將計算 HMAC 標記為 $ t = $ HMAC( deadbeef0000...00),16 個字節。但這也是不同密文的有效 HMAC 標籤 $ c’= $ deadbeef也 $ c’ = $ deadbeef000000等我希望你能明白為什麼這會破壞經過身份驗證的加密安全屬性。

我不確定您為什麼要以這種方式填充/編碼 HMAC 輸入,但我的猜測是您正試圖將密文和相關數據“序列化”為提供給 HMAC 的單個字元串。這是身份驗證的一個常見陷阱:您想對一對事物進行身份驗證 $ (x,y) $ ,所以你驗證字元串 $ x | y $ . 但是很多對 $ (x’,y’) $ 映射到同一個字元串 $ x|y $ (想想長度的情況 $ x $ , $ y $ 是可變的)!為了正確進行身份驗證,您需要對這對進行明確的編碼 $ (x,y) $ 成一個字元串。一種好方法是以某種方式包括 $ x $ 作為此編碼的一部分。

**編輯:**你似乎也從來沒有使用過self._cipher

引用自:https://crypto.stackexchange.com/questions/82021