Skip to content

[AES-GCM] cipher.AEAD is no-longer safe for concurrent use #187

@lyoung-confluent

Description

@lyoung-confluent

In the Go 1.21 branch, the cipher.AEAD returned for AES GCM was safe for concurrent use, matching the (undocumented) behavior from Go's stdlib and boringcrypto implementation (golang/go#25882). This was because the aesGCM struct cached the raw AES key, creating a new EVP_CIPHER_CTX (which is not thread-safe) for each seal/open operation. Now, the pointer to EVP_CIPHER_CTX is cached on the cipherGCM struct and re-used for every seal/open operation.

This means if an application caches a returned cipher.AEAD and then calls it concurrently, eventually it will crash. I have observed this crash in a real-world application, specifically Hashicorp vault caches a returned cipher.AEAD and makes use of it across multiple goroutines, eventually resulting in errors like:

decryption failed: cipher: message authentication failed

The following application can reliably reproduce the crash on Go 1.22:

package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"log"
)

func main() {
	var key [32]byte
	if _, err := rand.Read(key[:]); err != nil {
		panic(err)
	}
	log.Printf("key: %#v", key)

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

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		panic(err)
	}

	nonce := make([]byte, gcm.NonceSize())
	if _, err := rand.Read(nonce[:]); err != nil {
		panic(err)
	}
	log.Printf("nonce: %#v", nonce)

	ciphertext := gcm.Seal(nil, nonce, []byte("hunter2"), nil)
	log.Printf("ciphertext: %#v", ciphertext)

	for parallel := 0; parallel < 8; parallel++ {
		go func() {
			for {
				if _, err := gcm.Open(nil, nonce, ciphertext, nil); err != nil {
					panic(err)
				}
			}
		}()
	}

	<-make(chan struct{})
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions