2013-02-22 143 views
2

我最近碰到the following code sample来到了一个SHA256 HMAC认证和验证与加密AES-256 CBC文件:HMAC-SHA256使用AES-256 CBC模式

aes_key, hmac_key = self.keys 
# create a PKCS#7 pad to get us to `len(data) % 16 == 0` 
pad_length = 16 - len(data) % 16 
data = data + (pad_length * chr(pad_length)) 
# get IV 
iv = os.urandom(16) 
# create cipher 
cipher = AES.new(aes_key, AES.MODE_CBC, iv) 
data = iv + cipher.encrypt(data) 
sig = hmac.new(hmac_key, data, hashlib.sha256).digest() 
# return the encrypted data (iv, followed by encrypted data, followed by hmac sig): 
return data + sig 

因为在我的情况下, ,我加密更不是一个字符串,而是一个相当大的文件,我修改了代码执行以下操作:

aes_key, hmac_key = self.keys 
iv = os.urandom(16) 
cipher = AES.new(aes_key, AES.MODE_CBC, iv) 

with open('input.file', 'rb') as infile: 
    with open('output.file', 'wb') as outfile: 
     # write the iv to the file: 
     outfile.write(iv) 

     # start the loop 
     end_of_line = True 

     while True: 
      input_chunk = infile.read(64 * 1024) 

      if len(input_chunk) == 0: 
       # we have reached the end of the input file and it matches `% 16 == 0` 
       # so pad it with 16 bytes of PKCS#7 padding: 
       end_of_line = True 
       input_chunk += 16 * chr(16) 
      elif len(input_chunk) % 16 > 0: 
       # we have reached the end of the input file and it doesn't match `% 16 == 0` 
       # pad it by the remainder of bytes in PKCS#7: 
       end_of_line = True 
       input_chunk_remainder = 16 - (len(input_chunk) & 16) 
       input_chunk += input_chunk_remainder * chr(input_chunk_remainder) 

      # write out encrypted data and an HMAC of the block 
      outfile.write(cipher.encrypt(input_chunk) + hmac.new(hmac_key, data, 
        hashlib.sha256).digest()) 

      if end_of_line: 
       break 

简单地说,这种读取64KB的块输入文件的时间和加密这些块使用加密数据的SHA-256生成HMAC,并附加该HMAC每块后。解密将通过读取64KB + 32B块并计算前64KB的HMAC并将其与占用块中最后32个字节的SHA-256总数进行比较来实现。

这是使用HMAC的正确方法吗?它是否确保数据未经修改且使用正确密钥解密的安全性和身份验证?

仅供参考,AES和HMAC密钥都是由通过SHA-512运行输入文本,然后再通过bcrypt,然后再通过SHA-512生成的相同密码派生的。然后将最终SHA-512的输出分成两个块,一个用于AES密码,另一个用于HMAC。

回答

5

是的,有2个安全问题。

但首先,我认为在末尾这样的说法:

# write out encrypted data and an HMAC of the block 
outfile.write(cipher.encrypt(input_chunk) + hmac.new(hmac_key, data, hashlib.sha256).digest()) 

你实际上意味着:

# write out encrypted data and an HMAC of the block 
data = cipher.encrypt(input_chunk) 
outfile.write(data + hmac.new(hmac_key, data, hashlib.sha256).digest()) 

因为data不被任何定义。

第一个安全问题是您正在独立于其他人认证每个部分,但不是组成部分。换句话说,攻击者可以重新组合,复制或删除任何组块,并且接收者不会注意到。

更安全的方法是只有一个HMAC实例,通过update方法将所有加密数据传递给它,并在最后输出一个摘要。

或者,如果您希望在接收整个文件之前启用接收器检测篡改,则可以输出每个片段的中间MAC。实际上,拨打digest不会改变HMAC的状态;之后您可以继续拨打update

第二个安全问题是,你不使用salt作为你的密钥派生(我说因为你不发送它)。除了密码破解之外,如果使用相同的密码加密超过2个文件,攻击者还可以自由混合由加密文件获取的块 - 因为HMAC密钥是相同的。解决方案:使用盐。

最后一件小事:infile.read(64 * 1024)可能会返回小于64*1024字节,但是that does not mean you reached the end of the file

+1

对于点编号1,如果密码不正确,我需要在解密数据时很快失败。所以,我基本上应该在进入循环之前创建一个HMAC实例,然后使用update将输入数据转储到HMAC实例中,然后在每个块之后调用digest来存储散列,对吧?然后,每个块将成为它之前的数据的累积散列,从而确保不仅一个块没有被篡改,而且还确保整个文件是有序的并与散列匹配。这是否解决了这个问题? – 2013-02-23 00:03:41

+1

对于点编号2,我使用盐作为我的密钥派生。上面的代码过于简化,但salt会与头文件一起存储以提高安全性。我还使用盐中的高迭代次数来增加安全性,使得难以强制密码。 – 2013-02-23 00:05:52

+0

对于最后一点,我需要找到一种解决此问题的方法,因为我将通过网络套接字读取文件。 – 2013-02-23 00:06:46

-2

我不认为你在用HMAC做什么时存在安全问题(这不意味着没有安全问题),但我不知道HMAC中的实际值密文的子元素得到你。除非您希望在篡改事件时支持部分明文恢复,否则没有太大理由导致HMACing 64 KB块的开销与完整密文相比较。

从密钥生成角度来看,使用密钥生成的密钥对两个随机生成的密钥进行加密,然后使用随机生成的密钥执行HMAC和AES操作可能更有意义。我知道对于分组密码和HMAC使用相同的密钥是个坏消息,但我不知道使用以相同方式生成的密钥是否同样不好。

至少,你应该调整你的密钥派生机制。 bcrypt是一个密码哈希机制,而不是密钥派生函数。您应该使用PBKDF2来做密钥派生。

+1

使用bcrypt很好。这是一个关键的派生函数[实际上比PBKDF2更好](http://security.stackexchange.com/questions/4781/do-any-security-experts-recommend-bcrypt-for-password-storage)。 – SquareRootOfTwentyThree 2013-02-22 23:39:58

+0

@SquareRootOfTwentyThree这是一个关于密码散列的帖子,这是与密钥派生不同的问题。 bcrypt具有192位的固定输出,这意味着您必须执行额外的操作,例如散列输出以获得可用的密钥。您不必使用PBKDF2来做到这一点。 – 2013-02-22 23:51:45

+0

对我来说,“密码哈希”意味着“基于密码的KDF”,但老实说我不在乎命名。在这种情况下,bcrypt更好,因为它比PBKDF2更能抵御硬件攻击。在最后做一个额外的散列是微不足道的。重要的是它是安全的。 – SquareRootOfTwentyThree 2013-02-23 00:23:46