diff --git a/backend/crypt/cipher.go b/backend/crypt/cipher.go
index 0d3f10f61..9428c7ac4 100644
--- a/backend/crypt/cipher.go
+++ b/backend/crypt/cipher.go
@@ -922,7 +922,7 @@ type decrypter struct {
 }
 
 // newDecrypter creates a new file handle decrypting on the fly
-func (c *Cipher) newDecrypter(rc io.ReadCloser, customCek *cek) (*decrypter, error) {
+func (c *Cipher) newDecrypter(rc io.ReadCloser, customCek *cek, o *Object) (*decrypter, error) {
 	fh := &decrypter{
 		rc:      rc,
 		c:       c,
@@ -952,6 +952,10 @@ func (c *Cipher) newDecrypter(rc io.ReadCloser, customCek *cek) (*decrypter, err
 		return nil, fh.finishAndClose(ErrorEncryptedBadMagic)
 	}
 
+	if o != nil { // Set cipher version on the object level
+		o.cipherVersion = fh.cipherVersion
+	}
+
 	offsetStart := getFileMagicSize(fh.cipherVersion)
 	offsetEnd := offsetStart + getFileNonceSize(fh.cipherVersion)
 	fh.nonce.fromBuf(readBuf[offsetStart:offsetEnd], getFileNonceSize(fh.cipherVersion))
@@ -1002,7 +1006,7 @@ func (c *Cipher) newDecrypter(rc io.ReadCloser, customCek *cek) (*decrypter, err
 }
 
 // newDecrypterSeek creates a new file handle decrypting on the fly
-func (c *Cipher) newDecrypterSeek(ctx context.Context, open OpenRangeSeek, offset, limit int64, customCek *cek) (fh *decrypter, err error) {
+func (c *Cipher) newDecrypterSeek(ctx context.Context, o *Object, open OpenRangeSeek, offset, limit int64, customCek *cek) (fh *decrypter, err error) {
 	var rc io.ReadCloser
 	doRangeSeek := false
 	setLimit := false
@@ -1024,7 +1028,7 @@ func (c *Cipher) newDecrypterSeek(ctx context.Context, open OpenRangeSeek, offse
 		return nil, err
 	}
 	// Open the stream which fills in the nonce
-	fh, err = c.newDecrypter(rc, customCek)
+	fh, err = c.newDecrypter(rc, customCek, o)
 	if err != nil {
 		return nil, err
 	}
@@ -1293,7 +1297,7 @@ func (fh *decrypter) finishAndClose(err error) error {
 
 // DecryptData decrypts the data stream
 func (c *Cipher) DecryptData(rc io.ReadCloser) (io.ReadCloser, error) {
-	out, err := c.newDecrypter(rc, nil)
+	out, err := c.newDecrypter(rc, nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -1305,8 +1309,8 @@ func (c *Cipher) DecryptData(rc io.ReadCloser) (io.ReadCloser, error) {
 // The open function must return a ReadCloser opened to the offset supplied.
 //
 // You must use this form of DecryptData if you might want to Seek the file handle
-func (c *Cipher) DecryptDataSeek(ctx context.Context, open OpenRangeSeek, offset, limit int64, customCek *cek) (ReadSeekCloser, error) {
-	out, err := c.newDecrypterSeek(ctx, open, offset, limit, customCek)
+func (c *Cipher) DecryptDataSeek(ctx context.Context, o *Object, open OpenRangeSeek, offset, limit int64, customCek *cek) (ReadSeekCloser, error) {
+	out, err := c.newDecrypterSeek(ctx, o, open, offset, limit, customCek)
 	if err != nil {
 		return nil, err
 	}
diff --git a/backend/crypt/cipher_test.go b/backend/crypt/cipher_test.go
index cb54182c8..caf73135e 100644
--- a/backend/crypt/cipher_test.go
+++ b/backend/crypt/cipher_test.go
@@ -1087,7 +1087,7 @@ func testEncryptDecrypt(t *testing.T, bufSize int, copySize int64) {
 	source := newRandomSource(copySize)
 	encrypted, err := c.newEncrypter(source, nil, nil)
 	assert.NoError(t, err)
-	decrypted, err := c.newDecrypter(io.NopCloser(encrypted), nil)
+	decrypted, err := c.newDecrypter(io.NopCloser(encrypted), nil, nil)
 	assert.NoError(t, err)
 	sink := newRandomSource(copySize)
 	n, err := io.CopyBuffer(sink, decrypted, buf)
@@ -1300,7 +1300,7 @@ func TestNewDecrypter(t *testing.T) {
 	c.cryptoRand = newRandomSource(1e8) // nodge the crypto rand generator
 
 	cd := newCloseDetector(bytes.NewBuffer(file0))
-	fh, err := c.newDecrypter(cd, nil)
+	fh, err := c.newDecrypter(cd, nil, nil)
 	assert.NoError(t, err)
 	// check nonce is in place
 	assert.Equal(t, file0[8:32], fh.nonce[:])
@@ -1309,7 +1309,7 @@ func TestNewDecrypter(t *testing.T) {
 	// Test error paths
 	for i := range file0 {
 		cd := newCloseDetector(bytes.NewBuffer(file0[:i]))
-		fh, err = c.newDecrypter(cd, nil)
+		fh, err = c.newDecrypter(cd, nil, nil)
 		assert.Nil(t, fh)
 		assert.EqualError(t, err, ErrorEncryptedFileTooShort.Error())
 		assert.Equal(t, 1, cd.closed)
@@ -1317,7 +1317,7 @@ func TestNewDecrypter(t *testing.T) {
 
 	er := &readers.ErrorReader{Err: errors.New("potato")}
 	cd = newCloseDetector(er)
-	fh, err = c.newDecrypter(cd, nil)
+	fh, err = c.newDecrypter(cd, nil, nil)
 	assert.Nil(t, fh)
 	assert.EqualError(t, err, "potato")
 	assert.Equal(t, 1, cd.closed)
@@ -1328,7 +1328,7 @@ func TestNewDecrypter(t *testing.T) {
 	for i := range fileMagic {
 		file0copy[i] ^= 0x1
 		cd := newCloseDetector(bytes.NewBuffer(file0copy))
-		fh, err := c.newDecrypter(cd, nil)
+		fh, err := c.newDecrypter(cd, nil, nil)
 		assert.Nil(t, fh)
 		if i == 7 { // This test accidentally swaps last byte and converts `fileMagic` (RCLONE\x00\x00") into `fileMagicV2` ("RCLONE\x00\x01") resulting in a different than: "ErrorEncryptedBadMagic" error.
 			assert.EqualError(t, err, ErrorEncryptedFileTooShort.Error())
@@ -1349,7 +1349,7 @@ func TestNewDecrypterErrUnexpectedEOF(t *testing.T) {
 	in1 := bytes.NewBuffer(file16)
 	in := io.NopCloser(io.MultiReader(in1, in2))
 
-	fh, err := c.newDecrypter(in, nil)
+	fh, err := c.newDecrypter(in, nil, nil)
 	assert.NoError(t, err)
 
 	n, err := io.CopyN(io.Discard, fh, 1e6)
@@ -1423,7 +1423,7 @@ func TestNewDecrypterSeekLimit(t *testing.T) {
 			if offset+limit > len(plaintext) {
 				continue
 			}
-			rc, err := c.DecryptDataSeek(context.Background(), open, int64(offset), int64(limit), nil)
+			rc, err := c.DecryptDataSeek(context.Background(), nil, open, int64(offset), int64(limit), nil)
 			assert.NoError(t, err)
 
 			check(rc, offset, limit)
@@ -1431,7 +1431,7 @@ func TestNewDecrypterSeekLimit(t *testing.T) {
 	}
 
 	// Try decoding it with a single open and lots of seeks
-	fh, err := c.DecryptDataSeek(context.Background(), open, 0, -1, nil)
+	fh, err := c.DecryptDataSeek(context.Background(), nil, open, 0, -1, nil)
 	assert.NoError(t, err)
 	for _, offset := range trials {
 		for _, limit := range limits {
@@ -1503,7 +1503,7 @@ func TestNewDecrypterSeekLimit(t *testing.T) {
 			callCount++
 			return open(ctx, underlyingOffset, underlyingLimit)
 		}
-		fh, err := c.DecryptDataSeek(context.Background(), testOpen, 0, -1, nil)
+		fh, err := c.DecryptDataSeek(context.Background(), nil, testOpen, 0, -1, nil)
 		assert.NoError(t, err)
 		gotOffset, err := fh.RangeSeek(context.Background(), test.offset, io.SeekStart, test.limit)
 		assert.NoError(t, err)
@@ -1571,7 +1571,7 @@ func TestDecrypterRead(t *testing.T) {
 	for i := 0; i < len(file16)-1; i++ {
 		what := fmt.Sprintf("truncating to %d/%d", i, len(file16))
 		cd := newCloseDetector(bytes.NewBuffer(file16[:i]))
-		fh, err := c.newDecrypter(cd, nil)
+		fh, err := c.newDecrypter(cd, nil, nil)
 		if i < fileHeaderSize {
 			assert.EqualError(t, err, ErrorEncryptedFileTooShort.Error(), what)
 			continue
@@ -1604,7 +1604,7 @@ func TestDecrypterRead(t *testing.T) {
 	in2 := &readers.ErrorReader{Err: errors.New("potato")}
 	in := io.MultiReader(in1, in2)
 	cd := newCloseDetector(in)
-	fh, err := c.newDecrypter(cd, nil)
+	fh, err := c.newDecrypter(cd, nil, nil)
 	assert.NoError(t, err)
 	_, err = io.ReadAll(fh)
 	assert.EqualError(t, err, "potato")
@@ -1616,7 +1616,7 @@ func TestDecrypterRead(t *testing.T) {
 	copy(file16copy, file16)
 	for i := range file16copy {
 		file16copy[i] ^= 0xFF
-		fh, err := c.newDecrypter(io.NopCloser(bytes.NewBuffer(file16copy)), nil)
+		fh, err := c.newDecrypter(io.NopCloser(bytes.NewBuffer(file16copy)), nil, nil)
 		if i < fileMagicSize {
 			assert.EqualError(t, err, ErrorEncryptedBadMagic.Error())
 			assert.Nil(t, fh)
@@ -1633,7 +1633,7 @@ func TestDecrypterRead(t *testing.T) {
 	copy(file16copy, file16)
 	file16copy[len(file16copy)-1] ^= 0xFF
 	c.passBadBlocks = true
-	fh, err = c.newDecrypter(io.NopCloser(bytes.NewBuffer(file16copy)), nil)
+	fh, err = c.newDecrypter(io.NopCloser(bytes.NewBuffer(file16copy)), nil, nil)
 	assert.NoError(t, err)
 	buf, err := io.ReadAll(fh)
 	assert.NoError(t, err)
@@ -1645,7 +1645,7 @@ func TestDecrypterClose(t *testing.T) {
 	assert.NoError(t, err)
 
 	cd := newCloseDetector(bytes.NewBuffer(file16))
-	fh, err := c.newDecrypter(cd, nil)
+	fh, err := c.newDecrypter(cd, nil, nil)
 	assert.NoError(t, err)
 	assert.Equal(t, 0, cd.closed)
 
@@ -1663,7 +1663,7 @@ func TestDecrypterClose(t *testing.T) {
 
 	// try again reading the file this time
 	cd = newCloseDetector(bytes.NewBuffer(file1))
-	fh, err = c.newDecrypter(cd, nil)
+	fh, err = c.newDecrypter(cd, nil, nil)
 	assert.NoError(t, err)
 	assert.Equal(t, 0, cd.closed)
 
diff --git a/backend/crypt/crypt.go b/backend/crypt/crypt.go
index b45ec265e..647b54998 100644
--- a/backend/crypt/crypt.go
+++ b/backend/crypt/crypt.go
@@ -916,7 +916,7 @@ func (f *Fs) getDecrypter(ctx context.Context, o *Object, overrideCek *cek) (*de
 	if err != nil {
 		return nil, fmt.Errorf("failed to open object to read nonce: %w", err)
 	}
-	d, err := f.cipher.newDecrypter(in, overrideCek)
+	d, err := f.cipher.newDecrypter(in, overrideCek, o)
 	if err != nil {
 		_ = in.Close()
 		return nil, fmt.Errorf("failed to open object to read nonce: %w", err)
@@ -1146,6 +1146,7 @@ type Object struct {
 	fs.Object
 	f             *Fs
 	decryptedSize int64
+	cipherVersion string
 }
 
 func (f *Fs) newObject(o fs.Object) *Object {
@@ -1153,6 +1154,7 @@ func (f *Fs) newObject(o fs.Object) *Object {
 		Object:        o,
 		f:             f,
 		decryptedSize: -1,
+		cipherVersion: "",
 	}
 }
 
@@ -1185,10 +1187,18 @@ func (o *Object) Size() int64 {
 	size := o.Object.Size()
 	if !o.f.opt.NoDataEncryption {
 		var err error
-		if o.f.opt.ExactSize {
+		if o.f.opt.ExactSize && o.cipherVersion == "" { // Use `ExactSize` setting if cipherVersion isn't explicitly set on the object level. If cipherVersion is explicitly set, we deduce size correctly without reading file header.
 			size, err = o.f.cipher.DecryptedSizeExact(o)
 		} else {
-			size, err = o.f.cipher.DecryptedSize(size, o.f.cipher.version)
+
+			var cipherVersion string
+			if o.cipherVersion != "" { // Explicit cipher version (set by newDecrypter) detected from magic bytes
+				cipherVersion = o.cipherVersion
+			} else { // Assume cipher version based on config. Might not be correct if existing object was encrypted using different cipher version than currently configured.
+				cipherVersion = o.f.cipher.version
+			}
+
+			size, err = o.f.cipher.DecryptedSize(size, cipherVersion)
 		}
 
 		if err != nil {
@@ -1296,7 +1306,8 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (rc io.Read
 			openOptions = append(openOptions, option)
 		}
 	}
-	rc, err = o.f.cipher.DecryptDataSeek(ctx, func(ctx context.Context, underlyingOffset, underlyingLimit int64) (io.ReadCloser, error) {
+
+	rc, err = o.f.cipher.DecryptDataSeek(ctx, o, func(ctx context.Context, underlyingOffset, underlyingLimit int64) (io.ReadCloser, error) {
 		if underlyingOffset == 0 && underlyingLimit < 0 {
 			// Open with no seek
 			return o.Object.Open(ctx, openOptions...)