web-dev-qa-db-ja.com

Goで大きなファイル/バイトストリームをどのように暗号化しますか?

ネットワーク経由で送信したりディスクに保存したりする前に、AESで暗号化したい大きなファイルがいくつかあります。 ストリームを暗号化 することは可能であるようですが、 警告 に対して これを行う に対するようであり、代わりにファイルをチャンクに分割してGCMを使用することをお勧めしますまたはcrypto/nacl/secretbox。

信憑性の要件により、データのストリームの処理はより困難です。暗号化してからMACにすることはできません。その性質上、通常、ストリームのサイズはわかりません。ストリームが完了した後にMACを送信することはできません。これは通常、ストリームが閉じていることで示されます。 MACを確認するために暗号文全体を確認する必要があるため、その場でストリームを復号化することはできません。ストリームを保護しようとすると、問題が非常に複雑になり、適切な回答が得られません。解決策は、ストリームを個別のチャンクに分割し、それらをメッセージとして扱うことです。

ファイルは4KiBブロックに分割されます。各ブロックは、変更されるたびに新しいランダム128ビットIVを取得します。 128ビット認証タグ(GHASH)は、各ブロックを変更から保護します。

大量のデータが復号化される場合、認証タグが検証されるまで、すべての復号化されたデータを常にバッファリングできるとは限りません。データを小さなチャンクに分割すると、遅延認証チェックの問題が修正されますが、新しいチェックが導入されます。チャンクは並べ替えることができます... ...すべてのチャンクが個別に暗号化されるためです。したがって、任意の数のチャンクの再配置を検出できるように、チャンクの順序をチャンク自体に何らかの方法でエンコードする必要があります。

実際の暗号化の経験を持つ誰かが私を正しい方向に向けることができますか?

更新

この質問を聞いた後、バイトストリーム全体をメモリに収めることができない(10GBファイルを暗号化する)ことと、バイトストリームも不明であるの間に違いがあることに気付きましたストリームの開始をデコードする必要性を超えて長続きする長さ(24時間のライブビデオストリーム)。

私は、最初にデコードする必要がある前にストリームの終わりに到達できる大きなblobに主に関心があります。つまり、平文/暗号文全体を同時にメモリにロードする必要がない暗号化

14
Xeoncross

あなたの研究からすでに発見したように、大きなファイルのauthenticated暗号化のための洗練されたソリューションはあまりありません。

この問題に対処するには、従来2つの方法があります。

  • ファイルをチャンクに分割し、各チャンクを個別に暗号化して、各チャンクに独自の認証タグを持たせます。これにはAES-GCMが最適なモードです。この方法では、ファイルのサイズに比例してファイルサイズが膨張します。また、チャンクごとに一意のナンスが必要です。また、チャンクが開始/終了する場所を示す方法も必要です。

  • バッファーでAES-CTRを使用して暗号化し、暗号化されたデータのバッファーごとにHMACでHash.Writeを呼び出します。これの利点は、暗号化を1つのパスで実行できることです。欠点は、HMACを検証するために復号化に1つのパスが必要であり、実際に復号化するために別のパスが必要なことです。ここでの利点は、ファイルサイズが変わらないことと、IVとHMACの結果に約48バイト程度の変更が加えられていることです。

どちらも理想的ではありませんが、非常に大きなファイル(約2GB以上)の場合、2番目のオプションがおそらく推奨されます。

以下の2番目の方法を使用してGoに暗号化の例を含めました。このシナリオでは、最後の48バイトはIV(16バイト)とHMACの結果(32バイト)です。 IVのHMACにも注意してください。

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
}
9

暗号化後にHMACを使用することは有効な方法です。ただし、特にSHA-2が使用されている場合、HMACはかなり遅くなる可能性があります。 GCMの基礎となるMACであるGMACでも同じことができます。実装を見つけるのは難しいかもしれませんが、GMACは暗号文上にあるため、本当に必要な場合は、個別に実行できます。 TLS 1.2および1.3で使用されるAESを備えたPoly1305などの他の方法もあります。

GCM(またはCCMまたはEAXまたはその他の認証済み暗号)の場合、チャンクの順序を認証する必要があります。これを行うには、別のファイル暗号化キーを作成し、ナンス入力(12バイトのIV)を使用してチャンクの数を示します。これはIVのストレージを解決しますandチャンクが正しい順序であることを確認します。 KDFを使用して(ファイルを示す独自の方法がある場合)、またはランダムキーをマスターキーでラップすることにより、ファイル暗号化キーを生成できます。

4
Maarten Bodewes