Golang AES StreamReader encryption - Example omits any authentication of the encrypted data
Asked Answered
P

1

8

Finally I am posting my first question on StackOverflow. I'm using this site for years now and I always found great answers to all my questions :)

I am implementing a file encryption background daemon which is based on the official Golang cipher example:

func ExampleStreamReader() {
    key := []byte("example key 1234")

    inFile, err := os.Open("encrypted-file")
    if err != nil {
        panic(err)
    }
    defer inFile.Close()

    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }

    // If the key is unique for each ciphertext, then it's ok to use a zero
    // IV.
    var iv [aes.BlockSize]byte
    stream := cipher.NewOFB(block, iv[:])

    outFile, err := os.OpenFile("decrypted-file", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
    if err != nil {
        panic(err)
    }
    defer outFile.Close()

    reader := &cipher.StreamReader{S: stream, R: inFile}
    // Copy the input file to the output file, decrypting as we go.
    if _, err := io.Copy(outFile, reader); err != nil {
        panic(err)
    }

    // Note that this example is simplistic in that it omits any
    // authentication of the encrypted data. If you were actually to use
    // StreamReader in this manner, an attacker could flip arbitrary bits in
    // the output.
}

func ExampleStreamWriter() {
    key := []byte("example key 1234")

    inFile, err := os.Open("plaintext-file")
    if err != nil {
        panic(err)
    }
    defer inFile.Close()

    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }

    // If the key is unique for each ciphertext, then it's ok to use a zero
    // IV.
    var iv [aes.BlockSize]byte
    stream := cipher.NewOFB(block, iv[:])

    outFile, err := os.OpenFile("encrypted-file", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
    if err != nil {
        panic(err)
    }
    defer outFile.Close()

    writer := &cipher.StreamWriter{S: stream, W: outFile}
    // Copy the input file to the output file, encrypting as we go.
    if _, err := io.Copy(writer, inFile); err != nil {
        panic(err)
    }

    // Note that this example is simplistic in that it omits any
    // authentication of the encrypted data. If you were actually to use
    // StreamReader in this manner, an attacker could flip arbitrary bits in
    // the decrypted result.
}

What is meant with the following quote. About what should I take care to provide a secure encryption and decryption?

Note that this example is simplistic in that it omits any authentication of the encrypted data. If you were actually to use StreamReader in this manner, an attacker could flip arbitrary bits in the output.

Thanks!

Pickax answered 9/5, 2015 at 9:52 Comment(2)
For authentication try having a look at HMAC first, If that does not suit then possibly an in-line authentication like GCM mode might be better for you.Perfidious
Great! That helped me figuring it out. Thank you.Pickax
D
4

From wikipedia:

The block cipher modes ECB, CBC, OFB, CFB, CTR, and XTS provide confidentiality, but they do not protect against accidental modification or malicious tampering.

A good explanation can be found here: https://security.stackexchange.com/a/33576.

Go has support for other modes which do support integrity and authentication checks. As rossum said you can use GCM or CCM. You can find lots of examples on godoc.org. For example HashiCorp's memberlist library.

Another library worth checking out is the NaCL port in golang.org/x/crypto/nacl:

func Open(out []byte, box []byte, nonce *[24]byte, key *[32]byte) ([]byte, bool)
func Seal(out, message []byte, nonce *[24]byte, key *[32]byte) []byte

If you're working with small messages this API will probably be a lot easier to use.

Desmarais answered 10/5, 2015 at 2:28 Comment(3)
Great understood it now! Managed to get this working in a small test environment. However Go's implementation of GCM or CCM are not streamable. If I'll encrypt a huge file, I would have to load everything into RAM at once... The NaCL port also implements the go crypt AEAD interface. So it's also not streamable. Any suggestions? ;)Pickax
Take the code and make it streamable. Note that using the plaintext before authentication is dangerous (which is possibly why it isn't streamable in the first place). Otherwise you can use e.g. CTR and perform a HMAC or CMAC encryption over it and the IV - basically creating your own two pass (two key!) AEAD cipher. Don't forget to accept good answers.Besought
@Pickax break the file up into chunks and encrypt each one. With secretbox there's an Overhead constant which will tell you how much bigger the encrypted chunk will be than the decrypted chunk. You will have to define a format (like the memberlist example) and don't reuse nonces. io.Reader should be fairly straightforward... io.Seeker would be harder.Desmarais

© 2022 - 2024 — McMap. All rights reserved.