21Haz
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
}