• caglararli@hotmail.com
  • 05386281520

How to verify authentication tag during chunked AES-GSM-128 decryption

Çağlar Arlı      -    12 Views

How to verify authentication tag during chunked AES-GSM-128 decryption

Due to there are large encrypted files we are dealing with, we can't afford to keep entire file in memory during a decryption process.
I've implemented the algorithm of chunked decryption of AES GSM encrypted file.

const (
    tagSizeInBytes    = 16
    BufferSizeInBytes = 5 * 1024 * 1024
)

func DecryptFile(encryptedFile, decryptedFile *os.File, encryptedFileSize int64, dataKey, iv []byte) error {
    sizeOfEncryptedFileWithoutTag := encryptedFileSize - int64(tagSizeInBytes)

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

    var encryptedChunk = make([]byte, BufferSizeInBytes)

    var lastIteration = false
    var offset = 0

    for {
        nBytes, err := encryptedFile.Read(encryptedChunk)

        if err != nil {
            if err != io.EOF {
                fmt.Println(err)
            }
            break
        }

        if int64(offset+BufferSizeInBytes) >= sizeOfEncryptedFileWithoutTag {
            var endIndex = sizeOfEncryptedFileWithoutTag - int64(offset)
            if endIndex < 0 {
                endIndex = int64(nBytes)
            }
            encryptedChunk = encryptedChunk[0:endIndex]
            lastIteration = true
        }

        // iv is of 12 bytes size
        // counter has to be 16 bytes size
        extra := make([]byte, 4)
        counter := append(iv, extra[:]...)

        // in order to "jump" to specific offset in encrypted message,
        // we need to increment counter

        // init counter
        inc(counter)

        for i := 0; i < offset/aes.BlockSize+1; i++ {
            inc(counter)
        }

        var endIndex = offset + BufferSizeInBytes
        if endIndex > len(encryptedChunk) {
            endIndex = len(encryptedChunk)
        }
        encryptedBlock := encryptedChunk[0:endIndex]
        ctr := cipher.NewCTR(c, counter)

        plain := make([]byte, len(encryptedBlock))
        ctr.XORKeyStream(plain, encryptedBlock)

        if _, err = decryptedFile.Write(plain); err != nil {
            fmt.Println(err)
            os.Exit(1)
        }

        offset += BufferSizeInBytes

        if lastIteration {
            break
        }
    }

    return nil
}

The method works fine, but there is no tag authentication check.
Is it possible to implement tag verification for chunked AES-GCM decryption?

UPDATE: I've implemented the decryption based on Openssl .Update(), .setTag(), .Final() methods. From my understanding, using them all provides the tag based auth.

func DecryptFile(encryptedFile, decryptedFile *os.File, encryptedFileSize int64, dataKey, iv []byte) error {
    ctx, err := openssl.NewGCMDecryptionCipherCtx(len(dataKey)*8, dataKey, iv)

    start := time.Now()

    sizeOfEncryptedFileWithoutTag := encryptedFileSize - tagSizeInBytes

    var encryptedChunk = make([]byte, BufferSizeInBytes)

    var offset int64 = 0

    for {
        _, err := encryptedFile.Read(encryptedChunk)

        if err != nil {
            if err != io.EOF {
                fmt.Println(err)
            }
            break
        }

        // iv is of 12 bytes size
        // counter has to be 16 bytes size
        extra := make([]byte, 4)
        counter := append(iv, extra[:]...)

        // in order to "jump" to specific offset in encrypted message,
        // we need to increment counter

        // init counter
        inc(counter)

        for i := int64(0); i < offset/aes.BlockSize+1; i++ {
            inc(counter)
        }

        var endIndex int64 = BufferSizeInBytes

        if int64(offset)+BufferSizeInBytes >= sizeOfEncryptedFileWithoutTag {
            endIndex = sizeOfEncryptedFileWithoutTag - offset
            encryptedChunk = encryptedChunk[0:endIndex]

            _, err := encryptedFile.Seek(encryptedFileSize, 0)
            if err != nil {
                return err
            }
        }

        plain, err := ctx.Update(encryptedChunk[0:endIndex])
        if err != nil {
            return err
        }

        if _, err = decryptedFile.Write(plain); err != nil {
            fmt.Println(err)
            os.Exit(1)
        }

        offset += BufferSizeInBytes
    }

    // seeking in order to get authentication tag
    _, err = encryptedFile.Seek(sizeOfEncryptedFileWithoutTag, 0)
    if err != nil {
        return err
    }

    tag := make([]byte, tagSizeInBytes)
    _, err = encryptedFile.Read(tag)
    if err != nil {
        return err
    }

    if err := ctx.SetTag(tag); err != nil {
        return fmt.Errorf("Failed to set expected GCM tag: %v", err)
    }

    _, err = ctx.Final()
    if err != nil {
        return fmt.Errorf("Failed to decrypt final: %v", err)
    }

    fmt.Println("Elapsed time %f sec", time.Since(start).Seconds())

    return nil
}