如何加密Go中的大文件/字节流?

时间:2018-03-29 01:16:34

标签: go encryption aes large-data

我有一些大型文件,我想在通过网络发送或保存到磁盘之前进行AES加密。虽然encrypt streams似乎有可能,但warnings似乎有doing this,而人们建议将文件拆分为块并使用GCM或crypto / nacl / secretbox。

  

由于真实性要求,处理数据流更加困难。我们不能加密 - 然后MAC:根据它的性质,我们通常不知道流的大小。我们无法在流完成后发送MAC,因为通常由关闭的流指示。我们无法动态解密流,因为我们必须查看整个密文才能检查MAC。尝试保护流会增加问题的复杂性,没有好的答案。解决方案是将流分解为离散的块,并将它们视为消息。

  

文件被分段为4KiB块。每次修改时,每个块都会获得一个新的随机128位IV。 128位身份验证标记(GHASH)可保护每个块免受修改。

  

如果解密了大量数据,则在验证身份验证标记之前,并不总是可以缓冲所有解密数据。将数据拆分成小块可以解决延迟身份验证检查的问题,但会引入一个新问题。块可以重新排序......因为每个块都是单独加密的。因此,必须以某种方式将块的顺序编码到块本身中,以便能够检测重新排列任意数量的块。

任何具有实际密码学经验的人都能指出我正确的方向吗?

更新

我在问这个问题之后意识到,简单地说不能将整个字节流放入内存(加密10GB文件)和字节流 是未知长度之间存在差异可能会持续很久以前需要对流进行解码(24小时直播视频流)。

我最感兴趣的是大blob,在开始需要解码之前可以到达流的末尾。换句话说,加密不需要将整个明文/密文同时加载到内存中。

2 个答案:

答案 0 :(得分:7)

正如您已经从研究中发现的那样,对于大型文件的身份验证加密没有太多优雅的解决方案。

传统上有两种方法可以解决这个问题:

  • 将文件拆分为块,单独加密每个块,让每个块都有自己的身份验证标记。 AES-GCM将是最好的模式。此方法导致文件大小膨胀与文件大小成比例。您还需要为每个块提供唯一的随机数。您还需要一种方法来指示块开始/结束的位置。

  • 使用带缓冲区的AES-CTR加密,在HMAC上为每个加密数据缓冲区调用Hash.Write。这样做的好处是加密可以一次完成。缺点是解密需要一次通过验证HMAC,然后另一次通过实际解密。这里的优点是文件大小保持不变,加上IV和HMAC结果的大约约48个字节。

两者都不理想,但对于非常大的文件(~2GB或更多),第二种选择可能是首选。

我在Go中使用下面的第二种方法包含了一个加密示例。在这种情况下,最后48个字节是IV(16个字节)和HMAC的结果(32个字节)。注意IV的HMACing。

const BUFFER_SIZE int = 4096
const IV_SIZE int = 16

func encrypt(filePathIn, filePathOut string, keyAes, keyHmac []byte) error {
    inFile, err := os.Open(filePathIn)
    if err != nil { return err }
    defer inFile.Close()

    outFile, err := os.Create(filePathOut)
    if err != nil { return err }
    defer outFile.Close()

    iv := make([]byte, IV_SIZE)
    _, err = rand.Read(iv)
    if err != nil { return err }

    aes, err := aes.NewCipher(keyAes)
    if err != nil { return err }

    ctr := cipher.NewCTR(aes, iv)
    hmac := hmac.New(sha256.New, keyHmac)

    buf := make([]byte, BUFFER_SIZE)
    for {
        n, err := inFile.Read(buf)
        if err != nil && err != io.EOF { return err }

        outBuf := make([]byte, n)
        ctr.XORKeyStream(outBuf, buf[:n])
        hmac.Write(outBuf)
        outFile.Write(outBuf)

        if err == io.EOF { break }
    }

    outFile.Write(iv)
    hmac.Write(iv)
    outFile.Write(hmac.Sum(nil))

    return nil
}

答案 1 :(得分:4)

加密后使用HMAC是一种有效的方法。但是,HMAC可能非常慢,特别是如果使用SHA-2。实际上,您可以对GMAC(GCM的基础MAC)执行相同的操作。找到一个实现可能很棘手,但GMAC超过了密文,所以如果你真的想要的话,你可以单独执行它。还有其他方法,如用于TLS 1.2和1.3的Poly1305和AES。

对于GCM(或CCM或EAX或任何其他经过身份验证的密码),您需要验证块的顺序。您可以通过创建单独的文件加密密钥然后使用nonce输入(12字节IV)来指示块的编号来完成此操作。这将解决IV 的存储,确保块按顺序排列。您可以使用KDF生成文件加密密钥(如果您有一种唯一的方式来指示文件),或者使用主密钥包装随机密钥。