We only encrypt the actual tensor payload itself. This helps us avoid a format change. There's a case to encrypting the metadata header as well, but that requires a format change so I'm punting that.
We leverage the existing hash fields to contain the salt, nonce, and block_size for each tensor.
Encryption is easy. Just provide passphrase= argument to the Deserializer and Serializer.
We follow good practices, such as: random nonce's, ensuring that there's a new nonce each and every time, salting the passphrase with a random salt.
By design, we don't encrypt the entire tensor payload in one go -- instead we use a streaming block cipher, xsalsa20 with poly HMAC -- this allows us to decrypt concurrently with reads rather than waiting for the entire thing.
Tensor encryption and decryption.
Some design decisions and notes: