From 1caa9258685fa5fad8935d3bfcd0eac7d7f84f1e Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sat, 19 Dec 2015 14:41:39 +0100 Subject: Increase GCM IV size from 96 to 128 bits This pushes back the birthday bound for collisions to make it virtually irrelevant. --- cryptfs/address_translation.go | 4 ++-- cryptfs/config_file.go | 11 +++++++---- cryptfs/content_test.go | 6 +++--- cryptfs/cryptfs.go | 26 ++++++++++++++++++++------ cryptfs/cryptfs_content.go | 8 ++++---- cryptfs/names_test.go | 4 ++-- cryptfs/nonce.go | 11 +++++------ cryptfs/openssl_aead.go | 7 ++++--- 8 files changed, 47 insertions(+), 30 deletions(-) (limited to 'cryptfs') diff --git a/cryptfs/address_translation.go b/cryptfs/address_translation.go index 147040c..b21cfc7 100644 --- a/cryptfs/address_translation.go +++ b/cryptfs/address_translation.go @@ -44,7 +44,7 @@ func (be *CryptFS) CipherSizeToPlainSize(cipherSize uint64) uint64 { blockNo := be.CipherOffToBlockNo(cipherSize - 1) blockCount := blockNo + 1 - overhead := BLOCK_OVERHEAD*blockCount + HEADER_LEN + overhead := be.BlockOverhead()*blockCount + HEADER_LEN return cipherSize - overhead } @@ -56,7 +56,7 @@ func (be *CryptFS) PlainSizeToCipherSize(plainSize uint64) uint64 { blockNo := be.PlainOffToBlockNo(plainSize - 1) blockCount := blockNo + 1 - overhead := BLOCK_OVERHEAD*blockCount + HEADER_LEN + overhead := be.BlockOverhead()*blockCount + HEADER_LEN return plainSize + overhead } diff --git a/cryptfs/config_file.go b/cryptfs/config_file.go index 48e5474..138426a 100644 --- a/cryptfs/config_file.go +++ b/cryptfs/config_file.go @@ -46,6 +46,7 @@ func CreateConfFile(filename string, password string, plaintextNames bool, logN cf.EncryptKey(key, password, logN) // Set feature flags + cf.FeatureFlags = append(cf.FeatureFlags, FlagGCMIV128) if plaintextNames { cf.FeatureFlags = append(cf.FeatureFlags, FlagPlaintextNames) } else { @@ -94,7 +95,7 @@ func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) { // Unlock master key using password-based key // We use stock go GCM instead of OpenSSL here as speed is not important // and we get better error messages - cfs := NewCryptFS(scryptHash, false, false) + cfs := NewCryptFS(scryptHash, false, false, false) key, err := cfs.DecryptBlock(cf.EncryptedKey, 0, nil) if err != nil { Warn.Printf("failed to unlock master key: %s\n", err.Error()) @@ -115,7 +116,7 @@ func (cf *ConfFile) EncryptKey(key []byte, password string, logN int) { scryptHash := cf.ScryptObject.DeriveKey(password) // Lock master key using password-based key - cfs := NewCryptFS(scryptHash, false, false) + cfs := NewCryptFS(scryptHash, false, false, false) cf.EncryptedKey = cfs.EncryptBlock(key, 0, nil) } @@ -155,16 +156,18 @@ func (cf *ConfFile) WriteFile() error { const ( // Understood Feature Flags. - // Also teach isFeatureFlagKnown() about any additions + // Also teach isFeatureFlagKnown() about any additions and + // add it to CreateConfFile() if you want to have it enabled by default. FlagPlaintextNames = "PlaintextNames" FlagDirIV = "DirIV" FlagEMENames = "EMENames" + FlagGCMIV128 = "GCMIV128" ) // Verify that we understand a feature flag func (cf *ConfFile) isFeatureFlagKnown(flag string) bool { switch flag { - case FlagPlaintextNames, FlagDirIV, FlagEMENames: + case FlagPlaintextNames, FlagDirIV, FlagEMENames, FlagGCMIV128: return true default: return false diff --git a/cryptfs/content_test.go b/cryptfs/content_test.go index 1b269bd..3efa959 100644 --- a/cryptfs/content_test.go +++ b/cryptfs/content_test.go @@ -21,7 +21,7 @@ func TestSplitRange(t *testing.T) { testRange{6654, 8945}) key := make([]byte, KEY_LEN) - f := NewCryptFS(key, true, false) + f := NewCryptFS(key, true, false, true) for _, r := range ranges { parts := f.ExplodePlainRange(r.offset, r.length) @@ -48,7 +48,7 @@ func TestCiphertextRange(t *testing.T) { testRange{6654, 8945}) key := make([]byte, KEY_LEN) - f := NewCryptFS(key, true, false) + f := NewCryptFS(key, true, false, true) for _, r := range ranges { @@ -70,7 +70,7 @@ func TestCiphertextRange(t *testing.T) { func TestBlockNo(t *testing.T) { key := make([]byte, KEY_LEN) - f := NewCryptFS(key, true, false) + f := NewCryptFS(key, true, false, true) b := f.CipherOffToBlockNo(788) if b != 0 { diff --git a/cryptfs/cryptfs.go b/cryptfs/cryptfs.go index 58cca74..ae62045 100644 --- a/cryptfs/cryptfs.go +++ b/cryptfs/cryptfs.go @@ -11,9 +11,7 @@ import ( const ( DEFAULT_PLAINBS = 4096 KEY_LEN = 32 // AES-256 - NONCE_LEN = 12 AUTH_TAG_LEN = 16 - BLOCK_OVERHEAD = NONCE_LEN + AUTH_TAG_LEN DIRIV_LEN = 16 // identical to AES block size DIRIV_FILENAME = "gocryptfs.diriv" ) @@ -21,6 +19,8 @@ const ( type CryptFS struct { blockCipher cipher.Block gcm cipher.AEAD + gcmIVLen int + gcmIVGen nonceGenerator plainBS uint64 cipherBS uint64 // Stores an all-zero block of size cipherBS @@ -29,7 +29,7 @@ type CryptFS struct { DirIVCacheEnc DirIVCache } -func NewCryptFS(key []byte, useOpenssl bool, plaintextNames bool) *CryptFS { +func NewCryptFS(key []byte, useOpenssl bool, plaintextNames bool, GCMIV128 bool) *CryptFS { if len(key) != KEY_LEN { panic(fmt.Sprintf("Unsupported key length %d", len(key))) @@ -40,22 +40,31 @@ func NewCryptFS(key []byte, useOpenssl bool, plaintextNames bool) *CryptFS { panic(err) } + // We want the IV size in bytes + gcmIV := 96 / 8 + if GCMIV128 { + gcmIV = 128 / 8 + } + var gcm cipher.AEAD if useOpenssl { gcm = opensslGCM{key} } else { - gcm, err = cipher.NewGCM(b) + gcm, err = cipher.NewGCMWithNonceSize(b, gcmIV) if err != nil { panic(err) } } - cipherBS := DEFAULT_PLAINBS + NONCE_LEN + AUTH_TAG_LEN + plainBS := DEFAULT_PLAINBS + cipherBS := plainBS + gcmIV + AUTH_TAG_LEN return &CryptFS{ blockCipher: b, gcm: gcm, - plainBS: DEFAULT_PLAINBS, + gcmIVLen: gcmIV, + gcmIVGen: nonceGenerator{nonceLen: gcmIV}, + plainBS: uint64(plainBS), cipherBS: uint64(cipherBS), allZeroBlock: make([]byte, cipherBS), } @@ -65,3 +74,8 @@ func NewCryptFS(key []byte, useOpenssl bool, plaintextNames bool) *CryptFS { func (be *CryptFS) PlainBS() uint64 { return be.plainBS } + +// Per-block storage overhead +func (be *CryptFS) BlockOverhead() uint64 { + return be.cipherBS - be.plainBS +} diff --git a/cryptfs/cryptfs_content.go b/cryptfs/cryptfs_content.go index 25293a7..9a79db4 100644 --- a/cryptfs/cryptfs_content.go +++ b/cryptfs/cryptfs_content.go @@ -59,15 +59,15 @@ func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []byte return make([]byte, be.plainBS), nil } - if len(ciphertext) < NONCE_LEN { + if len(ciphertext) < be.gcmIVLen { Warn.Printf("DecryptBlock: Block is too short: %d bytes\n", len(ciphertext)) return nil, errors.New("Block is too short") } // Extract nonce - nonce := ciphertext[:NONCE_LEN] + nonce := ciphertext[:be.gcmIVLen] ciphertextOrig := ciphertext - ciphertext = ciphertext[NONCE_LEN:] + ciphertext = ciphertext[be.gcmIVLen:] // Decrypt var plaintext []byte @@ -94,7 +94,7 @@ func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte) } // Get fresh nonce - nonce := gcmNonce.Get() + nonce := be.gcmIVGen.Get() // Authenticate block with block number and file ID aData := make([]byte, 8) diff --git a/cryptfs/names_test.go b/cryptfs/names_test.go index 6dffae3..0207f0a 100644 --- a/cryptfs/names_test.go +++ b/cryptfs/names_test.go @@ -12,7 +12,7 @@ func TestEncryptPathNoIV(t *testing.T) { s = append(s, "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890") key := make([]byte, KEY_LEN) - fs := NewCryptFS(key, true, false) + fs := NewCryptFS(key, true, false, true) for _, n := range s { c := fs.EncryptPathNoIV(n) @@ -33,7 +33,7 @@ func TestPad16(t *testing.T) { s = append(s, []byte("12345678901234567abcdefg")) key := make([]byte, KEY_LEN) - fs := NewCryptFS(key, true, false) + fs := NewCryptFS(key, true, false, true) for i := range s { orig := s[i] diff --git a/cryptfs/nonce.go b/cryptfs/nonce.go index 3abfefa..be777fc 100644 --- a/cryptfs/nonce.go +++ b/cryptfs/nonce.go @@ -24,16 +24,15 @@ func RandUint64() uint64 { return binary.BigEndian.Uint64(b) } -var gcmNonce nonce96 - -type nonce96 struct { +type nonceGenerator struct { lastNonce []byte + nonceLen int // bytes } // Get a random 96 bit nonce -func (n *nonce96) Get() []byte { - nonce := RandBytes(12) - Debug.Printf("nonce96.Get(): %s\n", hex.EncodeToString(nonce)) +func (n *nonceGenerator) Get() []byte { + nonce := RandBytes(n.nonceLen) + Debug.Printf("nonceGenerator.Get(): %s\n", hex.EncodeToString(nonce)) if bytes.Equal(nonce, n.lastNonce) { m := fmt.Sprintf("Got the same nonce twice: %s. This should never happen!", hex.EncodeToString(nonce)) panic(m) diff --git a/cryptfs/openssl_aead.go b/cryptfs/openssl_aead.go index c70bd1f..5d38d38 100644 --- a/cryptfs/openssl_aead.go +++ b/cryptfs/openssl_aead.go @@ -7,6 +7,7 @@ import ( "github.com/spacemonkeygo/openssl" ) +// Supports all nonce sizes type opensslGCM struct { key []byte } @@ -16,13 +17,13 @@ func (be opensslGCM) Overhead() int { } func (be opensslGCM) NonceSize() int { - return NONCE_LEN + // We support any nonce size + return -1 } // Seal encrypts and authenticates plaintext, authenticates the // additional data and appends the result to dst, returning the updated -// slice. The nonce must be NonceSize() bytes long and unique for all -// time, for a given key. +// slice. opensslGCM supports any nonce size. func (be opensslGCM) Seal(dst, nonce, plaintext, data []byte) []byte { // Preallocate output buffer -- cgit v1.2.3