// Package contentenc encrypts and decrypts file blocks. package contentenc import ( "bytes" "encoding/binary" "encoding/hex" "errors" "github.com/rfjakob/gocryptfs/internal/cryptocore" "github.com/rfjakob/gocryptfs/internal/tlog" ) // NonceMode determines how nonces are created. type NonceMode int const ( // DefaultBS is the default plaintext block size DefaultBS = 4096 // DefaultIVBits is the default length of IV, in bits. // We always use 128-bit IVs for file content, but the // key in the config file is encrypted with a 96-bit IV. DefaultIVBits = 128 _ = iota // skip zero // RandomNonce chooses a random nonce. RandomNonce NonceMode = iota // ReverseDeterministicNonce chooses a deterministic nonce, suitable for // use in reverse mode. ReverseDeterministicNonce NonceMode = iota // ExternalNonce derives a nonce from external sources. ExternalNonce NonceMode = iota ) // ContentEnc is used to encipher and decipher file content. type ContentEnc struct { // Cryptographic primitives cryptoCore *cryptocore.CryptoCore // Plaintext block size plainBS uint64 // Ciphertext block size cipherBS uint64 // All-zero block of size cipherBS, for fast compares allZeroBlock []byte // All-zero block of size IVBitLen/8, for fast compares allZeroNonce []byte } // New returns an initialized ContentEnc instance. func New(cc *cryptocore.CryptoCore, plainBS uint64) *ContentEnc { cipherBS := plainBS + uint64(cc.IVLen) + cryptocore.AuthTagLen return &ContentEnc{ cryptoCore: cc, plainBS: plainBS, cipherBS: cipherBS, allZeroBlock: make([]byte, cipherBS), allZeroNonce: make([]byte, cc.IVLen), } } // PlainBS returns the plaintext block size func (be *ContentEnc) PlainBS() uint64 { return be.plainBS } // CipherBS returns the ciphertext block size func (be *ContentEnc) CipherBS() uint64 { return be.cipherBS } // DecryptBlocks decrypts a number of blocks // TODO refactor to three-param for func (be *ContentEnc) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, fileID []byte) ([]byte, error) { cBuf := bytes.NewBuffer(ciphertext) var err error var pBuf bytes.Buffer for cBuf.Len() > 0 { cBlock := cBuf.Next(int(be.cipherBS)) var pBlock []byte pBlock, err = be.DecryptBlock(cBlock, firstBlockNo, fileID) if err != nil { break } pBuf.Write(pBlock) firstBlockNo++ } return pBuf.Bytes(), err } // DecryptBlock - Verify and decrypt GCM block // // Corner case: A full-sized block of all-zero ciphertext bytes is translated // to an all-zero plaintext block, i.e. file hole passtrough. func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []byte) ([]byte, error) { // Empty block? if len(ciphertext) == 0 { return ciphertext, nil } // All-zero block? if bytes.Equal(ciphertext, be.allZeroBlock) { tlog.Debug.Printf("DecryptBlock: file hole encountered") return make([]byte, be.plainBS), nil } if len(ciphertext) < be.cryptoCore.IVLen { tlog.Warn.Printf("DecryptBlock: Block is too short: %d bytes", len(ciphertext)) return nil, errors.New("Block is too short") } // Extract nonce nonce := ciphertext[:be.cryptoCore.IVLen] if bytes.Equal(nonce, be.allZeroNonce) { panic("Hit an all-zero nonce. This MUST NOT happen!") } ciphertextOrig := ciphertext ciphertext = ciphertext[be.cryptoCore.IVLen:] // Decrypt var plaintext []byte aData := make([]byte, 8) aData = append(aData, fileID...) binary.BigEndian.PutUint64(aData, blockNo) plaintext, err := be.cryptoCore.AEADCipher.Open(plaintext, nonce, ciphertext, aData) if err != nil { tlog.Warn.Printf("DecryptBlock: %s, len=%d", err.Error(), len(ciphertextOrig)) tlog.Debug.Println(hex.Dump(ciphertextOrig)) return nil, err } return plaintext, nil } // EncryptBlock - Encrypt plaintext using a random nonce. // blockNo and fileID are used as associated data. // The output is nonce + ciphertext + tag. func (be *ContentEnc) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte) []byte { // Get a fresh random nonce nonce := be.cryptoCore.IVGenerator.Get() return be.doEncryptBlock(plaintext, blockNo, fileID, nonce) } // EncryptBlockNonce - Encrypt plaintext using a nonce chosen by the caller. // blockNo and fileID are used as associated data. // The output is nonce + ciphertext + tag. // This function can only be used in SIV mode. func (be *ContentEnc) EncryptBlockNonce(plaintext []byte, blockNo uint64, fileID []byte, nonce []byte) []byte { if be.cryptoCore.AEADBackend != cryptocore.BackendAESSIV { panic("deterministic nonces are only secure in SIV mode") } return be.doEncryptBlock(plaintext, blockNo, fileID, nonce) } // doEncryptBlock is the backend for EncryptBlock and EncryptBlockNonce. // blockNo and fileID are used as associated data. // The output is nonce + ciphertext + tag. func (be *ContentEnc) doEncryptBlock(plaintext []byte, blockNo uint64, fileID []byte, nonce []byte) []byte { // Empty block? if len(plaintext) == 0 { return plaintext } if len(nonce) != be.cryptoCore.IVLen { panic("wrong nonce length") } // Authenticate block with block number and file ID aData := make([]byte, 8) binary.BigEndian.PutUint64(aData, blockNo) aData = append(aData, fileID...) // Encrypt plaintext and append to nonce ciphertext := be.cryptoCore.AEADCipher.Seal(nonce, nonce, plaintext, aData) return ciphertext } // MergeBlocks - Merge newData into oldData at offset // New block may be bigger than both newData and oldData func (be *ContentEnc) MergeBlocks(oldData []byte, newData []byte, offset int) []byte { // Make block of maximum size out := make([]byte, be.plainBS) // Copy old and new data into it copy(out, oldData) l := len(newData) copy(out[offset:offset+l], newData) // Crop to length outLen := len(oldData) newLen := offset + len(newData) if outLen < newLen { outLen = newLen } return out[0:outLen] }