I'm using the golang.org/x/crypto/xts
package to create an XTS-AES cipher in Go, like this:
xtsCipher, err := xts.NewCipher(aes.NewCipher, key)
if err != nil {
log.Fatal(err)
}
And I am using an in-file C
function to wipe-out the keyXTS
which is of type []byte
. Like this:
package main
/*
#include <stddef.h>
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#endif
// Securely zeroes memory. Returns 0 on success; nonzero on error.
int secureZero(void *ptr, size_t n) {
if (ptr == NULL || n == 0) {
fprintf(stderr, "Error: secureZero received an invalid pointer or zero size.\n");
return -1;
}
#ifdef _WIN32
RtlSecureZeroMemory(ptr, n);
return 0;
#else
volatile unsigned char *p = (volatile unsigned char *)ptr;
for (size_t i = 0; i < n; ++i) {
p[i] = 0;
}
return 0;
#endif
}
*/
import "C"
import "unsafe"
import "crypto/rand"
import "log"
import "fmt"
import "crypto/aes"
import "golang.org/x/crypto/xts"
// zeroMemory securely wipes a byte slice.
func zeroMemory(data []byte) error {
if len(data) == 0 {
return nil
}
if ret := C.secureZero(unsafe.Pointer(&data[0]), C.size_t(len(data))); ret != 0 {
return fmt.Errorf("secureZero failed with error code: %d", ret)
}
return nil
}
func main() {
key := make([]byte, 64)
if _, err := rand.Read(key); err != nil {
log.Fatalf("failed to generate key: %v", err)
}
defer func() {
if err := zeroMemory(key); err != nil {
log.Printf("failed to zero memory: %v", err)
}
}()
xtsCipher, err := xts.NewCipher(aes.NewCipher, key)
if err != nil {
log.Fatal(err)
}
}
But I assume xtsCipher
still has an internal copy of the key.
xts.NewCipher
just use the underlaying array of keyXTS
? Which should mean that if I zero the memory of keyXTS
it would also zero out xtsCipher
.xtsCipher
's internal state?xtsCipher
is not a built-in type I don't think so.keyXTS
variable inside of the xtsCipher
lingering in memory?1. ... does
xts.NewCipher
just use the underlying array ofkeyXTS
?
No. An AES cipher contains a private copy of the key. And AES-XTS contains two AES ciphers as internal state.
2. Is there a way to explicitly clear
xtsCipher
's internal state?
No, as of right now (Go 1.24), there is no public API to clear AES or XTS state.
3. If not, are there any workarounds, like using unsafe or reflection? But since
xtsCipher
is not a built-in type I don't think so.
You can do a lot with unsafe API. However, using unsafe or reflection to clear the internal state of xtsCipher
is not recommended because it can lead to undefined behavior and potential security vulnerabilities,
not to mention being non-portable across different platforms, Go runtime versions and crypto modes, such as e.g. native crypto (with/without fips140), system crypto, BoringCrypto, etc.
With that said, if you really want to do it using unsafe API, here's a solution for the two most common crypto modes, fips140 native crypto and BoringCrypto:
func unsafeClearXTSCipher(v *xts.Cipher) {
r := reflect.ValueOf(*v)
unsafeClearAESCipher(r.FieldByName("k1").Elem().Elem())
unsafeClearAESCipher(r.FieldByName("k2").Elem().Elem())
}
// unsafeClearAESCipher clears an instance of an AES cipher. Currently supports
// crypto/internal/fips140/aes.Block and crypto/internal/boring.aesCipher.
func unsafeClearAESCipher(v reflect.Value) {
key := v.FieldByName("key")
if key.IsValid() {
// "key" field exists only in boring crypto
unsafeClearValue(key)
}
unsafeClearValue(v.FieldByName("enc"))
unsafeClearValue(v.FieldByName("dec"))
}
func unsafeClearValue(v reflect.Value) {
t := v.Type()
fmt.Println(t.Kind(), t.Size())
switch t.Kind() {
case reflect.Array, reflect.Struct:
arrType := reflect.ArrayOf(int(t.Size()), reflect.TypeOf(uint8(0)))
reflect.NewAt(arrType, v.Addr().UnsafePointer()).Elem().SetZero()
case reflect.Slice:
arrType := reflect.ArrayOf(v.Len(), reflect.TypeOf(uint8(0)))
reflect.NewAt(arrType, v.Index(0).Addr().UnsafePointer()).Elem().SetZero()
default:
panic("unsupported value type " + t.String())
}
}
You can verify in a debugger that after invoking unsafeClearXTSCipher(xtsCipher)
, the ciphers internal state contains all-zeros.
For other crypto modes you'd have reverse-engineer the crypto internal state and expand the solution.
4. What other approach would help me prevent keeping the internal state of the
keyXTS
variable inside of thextsCipher
lingering in memory?
It depends on what you're trying to protect against. There are 3rd-party libraries like libsodium
, that make an effort to protect key memory. There are also hardware solutions that store the key in hardware, like go-tpm2.