Search code examples
goencryptionhttp-live-streaming

Processing data in chunks with io.ReadFull results in corrupted file?


I'm trying to download and decrypt HLS streams by using io.ReadFull to process the data in chunks to conserve memory:

Irrelevant parts of code has been left out for simplicity.

func main() {
    f, _ := os.Create(out.ts)

    for _, v := range mediaPlaylist {
        resp, _ := http.Get(v.URI)
        for {
            r, err := decryptHLS(key, iv, resp.Body)
            if err != nil && err == io.EOF {
                break
            else if err != nil && err != io.ErrUnexpectedEOF {
                panic(err)
            }
            io.Copy(f, r)
        }
    }
}

func decryptHLS(key []byte, iv []byte, r io.Reader) (io.Reader, error) {
    block, _ := aes.NewCipher(key)

    buf := make([]byte, 8192)

    mode := cipher.NewCBCDecrypter(block, iv)

        n, err := io.ReadFull(r, buf)
        if err != nil && err != io.ErrUnexpectedEOF {
                return nil, err
        }

    mode.CryptBlocks(buf, buf)

    return bytes.NewReader(buf[:n]), err
}

At first this seems to work as file size is correct and no errors during download, but the video is corrupted. Not completely as the file is still recognized as a video, but image and sound is distorted.

If I change the code to use ioutil.ReadAll instead, the final video files will no longer be corrupted:

func main() {
    f, _ := os.Create(out.ts)

    for _, v := range mediaPlaylist {
        resp, _ := http.Get(v.URI)
        segment, _ := ioutil.ReadAll(resp.Body)
        r, _ := decryptHLS(key, iv, &segment)
        io.Copy(f, r)
    }
}

func decryptHLS(key []byte, iv []byte, s *[]byte) io.Reader {
    block, _ := aes.NewCipher(key)

    mode := cipher.NewCBCDecrypter(block, iv)

    mode.CryptBlocks(*s, *s)

    return bytes.NewReader(*s)
}

Any ideas why it works correctly when reading the entire segment into memory, and not when using io.ReadFull and processing it in chunks?


Solution

  • Internally, CBCDecrypter makes a copy of your iv, so subsequent blocks start with the initial IV rather than the one that's been mutated by previous decryptions.

    Create the decrypter once, and you should be able to keep re-using it to decrypt block by block (assuming the block size is a multiple of the block size expected by this crypto algorithm).