Skip to content

[Bug]: Encryption: AES-256-CTR legacy files fail signature verification on final block - Bad Signature #64078

Description

@kongomongo

⚠️ This issue respects the following points: ⚠️

  • This is not a troubleshooting question, general support matter, or webserver/proxy problem, but likely a bug (if unsure, ask the Community Help Forum).
  • This issue is not already reported on Github OR solved at the Community Help Forum (I've searched!).
  • I'm using a maintained major version of Nextcloud Server and tested against the latest patch level. (Supported major versions and current patch levels).
  • I agree to follow Nextcloud's Code of Conduct.
  • I've tried my best to provide clear reproduction steps that someone unfamiliar with this bug could use to reproduce it.

Bug description

Alright, time to possibly give something back to the community.

How this came to be: I have by accident identified a few files that would not download from my nextcloud, log said "Bad Signature". So I went looking and found a huge number of files that had this issue. Needless to say that my backups do not go far enough for "correct" files so I needed to dig in.

My Nextcloud instance is at least 10 major versions old, so it was always upgraded along the way.

I have encountered an apparent backwards-compatibility issue in Nextcloud's encryption handling of older AES-256-CTR encrypted files.

The files are fully decryptable and structurally intact, but current Nextcloud rejects the final block with Bad Signature.

For the affected files:

Encryption version: 1
Cipher: AES-256-CTR
Encoding: legacy Base64
Header: signed:true
Every physical block decrypts successfully.
Blocks before the final block have signatures matching the legacy signature calculation.
The final block has a signature matching legacy + end.
The current signature calculation does not match the stored signatures.
The failure therefore appears deterministic and related to the legacy block-signature format/position handling rather than damaged ciphertext.

The files were originally encrypted years ago and have subsequently been accessed through newer Nextcloud versions. I do not know the exact Nextcloud version that originally created them.

Reproduction

One affected file:

/user/files/trading/onvista/025-agb.pdf

Physical encrypted file:

545832 bytes

Encrypted data after the 8192-byte header:

537640 bytes

Physical blocks:

66

Nextcloud file version:

1

Cipher:

AES-256-CTR

Header:

HBEGIN:oc_encryption_module:OC_DEFAULT_MODULE:cipher:AES-256-CTR:signed:true:HEND

The corresponding 32-byte file key is:

89f40a506cdca288d83045d73b957a4073ac9cb7cc1e95d06006bbe1f388bac4

Key SHA-256:

cd1eb792f1922ae9542337ad340f418b58e9c7691287766a55ff6d377eaeead5

The key is included here solely to make this particular encrypted test file reproducible. It is a file-specific encryption key, not a server/master secret.

Observed signature behaviour

For blocks 0–64:

stored signature == legacy calculation
stored signature != current calculation
stored signature != legacy + end calculation

For final block 65:

stored signature == legacy + end calculation
stored signature != current calculation
stored signature != current + end calculation
stored signature != legacy calculation

Example, block 65:

Stored:
459984b3925e06f951c137c022e1e8c6595e10c417453e754890dde4d099accf

Current:
4b6a1588ac4e20c394589fb9e3203ff14eef68bd487e134b654dbb91eb84c870

Current + end:
3b8b7606ad056f9ab527b2897c4ebfd0d46061888d2e495c6856daed0ffc5daa

Legacy:
0b111673b7f4a6f4df03afe77cf45c7cdf330315e3854664d692383a604404d0

Legacy + end:
459984b3925e06f951c137c022e1e8c6595e10c417453e754890dde4d099accf

This is an exact match for legacy_end.

Decryption result

All 66 physical blocks decrypt successfully using the file key and AES-256-CTR:

Blocks: 66
Current signature OK: 0
Current signature BAD: 66
Legacy signature OK: 65
Legacy+end signature OK: 1
Decrypt OK: 66
Decrypt FAILED: 0
Plaintext recovered: 398477 bytes

The recovered plaintext size is exactly the expected file size from Nextcloud's file metadata:

398477 bytes

The recovered PDF is structurally valid and opens correctly.

Second/third affected files

This is not isolated to one file.

ZIP
/user/files/Homeday_immocation_Preisatlas.zip

File key SHA-256:
5679a7d008ab95702e1f23f23d4b9664c4bbd46d89f95e86a81aa2cffdd0b6ee

Version: 1
Cipher: AES-256-CTR
Blocks: 552

Legacy signatures:
551 OK
Legacy+end:
1 OK

Current signatures:
0 OK
552 BAD

Decrypt:
552/552 OK

The recovered ZIP passes ZipArchive validation and contains 8 entries.

JPEG
/user/files/AT6/2017_06_10/2017.06.10-10.06.21-iPhone SE-IMG_9874.JPG

File key SHA-256:
674d4b5ca5c391ddcb16b24162d4642e01be765073eecf2f3e2759d3bf621f10

Version: 1
Cipher: AES-256-CTR
Blocks: 487

Legacy signatures:
486 OK
Legacy+end:
1 OK

Current signatures:
0 OK
487 BAD

Decrypt:
487/487 OK

The recovered JPEG is also valid.

Why I think this is a compatibility issue

The important observation is the consistency across unrelated files:

normal blocks -> legacy signature
final block -> legacy + "end" signature

while the current Nextcloud implementation expects a different signature calculation.

This occurs regardless of whether the file is a PDF, ZIP, or JPEG.

The encrypted data itself appears intact because every block decrypts successfully and the recovered plaintext has the expected size/content.

This makes random corruption of the final block unlikely.

It also appears unlikely that the problem is simply an incorrect file key: the legacy signatures reproduce exactly for the blocks.

Potential impact

This could affect files encrypted with an older version of the encryption app and subsequently accessed/decrypted after an encryption-format/signature implementation change.

In my case, Nextcloud's normal decryption fails with:

OCP\Encryption\Exceptions\GenericEncryptionException
Bad Signature

and stops at the final block.

With signature verification bypassed, Nextcloud's own Crypt::symmetricDecryptFileContent() successfully decrypts every block.

Therefore the underlying encrypted data remains recoverable, but normal Nextcloud decryption fails.

Environment

Current Nextcloud installation:

php occ status

  • installed: true
  • version: 34.0.3.2
  • versionstring: 34.0.3
  • edition:
  • maintenance: true
  • needsDbUpgrade: false
  • productname: Nextcloud
  • extendedSupport: false

Encryption app version:

  • encryption: 2.22.0

PHP version:

8.3.29

OpenSSL version:

OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024)

Original encryption/Nextcloud version:

Unknown; files may originate from a significantly older Nextcloud installation.
Relevant source behaviour

The investigation was performed against Nextcloud's OCA\Encryption\Crypto\Crypt implementation.

The important distinction appears to be the construction of the signature input for:

normal blocks
final blocks
legacy format
current format

The reproducibility above suggests that an older signature format was used when these files were written, but the current implementation does not recognize that format correctly.

Request

Could someone familiar with the encryption format confirm whether this is an expected legacy-format compatibility case or a regression?

In particular, I would like to establish:

Which historical Nextcloud/encryption-app version generated the legacy / legacy_end signature format.
Whether version 1 files using that format are expected to remain readable by current Nextcloud.
Whether the current implementation intentionally dropped support for this signature variant.
If not, whether the final-block handling should recognize the legacy ...end signature.
Whether existing files can be migrated/re-signed safely without decrypting and re-encrypting the entire file.

I can provide the complete forensic audit output, including per-block signatures, calculated values, block lengths, plaintext hashes, and the exact test scripts used to reproduce the behaviour

audit-test4.php

Steps to reproduce

Unsure how to repro as this has happened automatically without my doing, but I can provide the files and database entries that make decryption unsuccessful.

Expected behavior

files decrypt normally.

Nextcloud Server version

34

Operating system

Other

PHP engine version

PHP 8.3

Web server

Apache (supported)

Database engine version

MySQL

Is this bug present after an update or on a fresh install?

None

Are you using the Nextcloud Server Encryption module?

Encryption is Enabled

What user-backends are you using?

  • Default user-backend (database)
  • LDAP/ Active Directory
  • SSO - SAML
  • Other

Configuration report

Nextcloud is in maintenance mode, only AppAPI commands are loaded.
Commands provided by other apps are unavailable.
{
    "system": {
        "instanceid": "***REMOVED SENSITIVE VALUE***",
        "passwordsalt": "***REMOVED SENSITIVE VALUE***",
        "secret": "***REMOVED SENSITIVE VALUE***",
        "trusted_domains": [
            ***REMOVED SENSITIVE VALUE***"
        ],
        "datadirectory": "***REMOVED SENSITIVE VALUE***",
        "dbtype": "mysql",
        "version": "34.0.3.2",
        "overwrite.cli.url": "http:\/\/***REMOVED SENSITIVE VALUE***"",
        "dbname": "***REMOVED SENSITIVE VALUE***",
        "dbhost": "***REMOVED SENSITIVE VALUE***",
        "dbport": "",
        "dbtableprefix": "oc_",
        "mysql.utf8mb4": true,
        "dbuser": "***REMOVED SENSITIVE VALUE***",
        "dbpassword": "***REMOVED SENSITIVE VALUE***",
        "installed": true,
        "tempdirectory": "\/www\/htdocs\/***REMOVED SENSITIVE VALUE***"_extra\/tmp",
        "mail_smtpmode": "smtp",
        "mail_sendmailmode": "smtp",
        "mail_smtptimeout": 30,
        "maintenance": true,
        "theme": "",
        "loglevel": 2,
        "encryption.legacy_format_support": false,
        "encryption.key_storage_migrated": false,
        "encryption_skip_signature_check": false,
        "mail_from_address": "***REMOVED SENSITIVE VALUE***",
        "mail_domain": "***REMOVED SENSITIVE VALUE***",
        "mail_smtpauth": 1,
        "mail_smtphost": "***REMOVED SENSITIVE VALUE***",
        "mail_smtpport": "587",
        "mail_smtpname": "***REMOVED SENSITIVE VALUE***",
        "mail_smtppassword": "***REMOVED SENSITIVE VALUE***",
        "mail_smtpsecure": "tls",
        "default_phone_region": "DE",
        "maintenance_window_start": 1,
        "app_install_overwrite": []
    }
}

List of activated Apps

Nextcloud is in maintenance mode, only AppAPI commands are loaded.
Commands provided by other apps are unavailable.
Enabled:
  - activity: 7.0.0
  - admin_audit: 1.24.0
  - app_api: 34.0.0
  - appstore: 1.0.0
  - bruteforcesettings: 7.0.0
  - circles: 34.0.0
  - cloud_federation_api: 1.18.0
  - comments: 1.24.0
  - contactsinteraction: 1.15.0
  - dav: 1.40.0
  - encryption: 2.22.0
  - federatedfilesharing: 1.24.0
  - federation: 1.24.0
  - files: 2.6.0
  - files_accesscontrol: 5.0.0
  - files_downloadlimit: 5.2.0
  - files_lock: 34.0.1
  - files_pdfviewer: 7.0.0-dev.0
  - files_reminders: 1.7.0
  - files_sharing: 1.26.0
  - files_versions: 1.27.0
  - firstrunwizard: 7.0.0-dev.0
  - forms: 5.3.5
  - logreader: 7.0.0
  - lookup_server_connector: 1.22.0
  - nextcloud_announcements: 6.0.0
  - notifications: 7.0.0-dev.1
  - oauth2: 1.22.0
  - office: 1.0.0
  - password_policy: 6.0.0-dev.0
  - photos: 7.0.0
  - privacy: 6.0.0-dev.1
  - profile: 1.3.0
  - provisioning_api: 1.24.0
  - related_resources: 5.0.0-dev.0
  - serverinfo: 6.0.0
  - settings: 1.17.0
  - sharebymail: 1.24.0
  - support: 6.0.0
  - survey_client: 6.0.0-dev.0
  - suspicious_login: 12.0.0-dev.0
  - systemtags: 1.24.0
  - text: 8.0.0
  - theming: 2.9.0
  - twofactor_backupcodes: 1.23.0
  - twofactor_totp: 16.0.0
  - updatenotification: 1.24.0
  - user_status: 1.14.0
  - viewer: 7.0.0-dev.0
  - weather_status: 1.14.0
  - webhook_listeners: 1.6.0
  - workflowengine: 2.16.0
Disabled:
  - dashboard: 7.14.0 (installed 7.1.0)
  - files_external: 1.26.0
  - files_rightclick: 0.15.1 (installed 1.6.0)
  - files_trackdownloads: 1.11.0 (installed 1.11.0)
  - files_trashbin: 1.24.0 (installed 1.24.0)
  - recommendations: 7.0.0 (installed 4.0.0)
  - testing: 1.23.0
  - twofactor_nextcloud_notification: 8.0.0
  - user_ldap: 1.25.0

Nextcloud Signing status

No errors have been found.

Nextcloud Logs

{"reqId":"apuYjRlhhobM3WHNm_0scQAAxw4","level":3,"time":"2026-09-05T04:20:29+00:00","remoteAddr":"94.31.114.15","user":"user","app":"webdav","method":"GET","url":"/remote.php/dav/files/user/trading/onvista/025-agb.pdf","scriptName":"/remote.php","message":"Bad Signature","userAgent":"Mozilla/5.0 (iOS) Nextcloud-iOS/34.1.4","version":"34.0.3.2","exception":{"Exception":"OCP\\Encryption\\Exceptions\\GenericEncryptionException","Message":"Bad Signature","Code":0,"Trace":[{"file":"/www/htdocs/wxxxxxxxx/cloud/apps/encryption/lib/Crypto/Crypt.php","line":433,"function":"checkSignature","class":"OCA\\Encryption\\Crypto\\Crypt","type":"->","args":["*** sensitive parameters replaced ***"]},{"file":"/www/htdocs/wxxxxxxxx/cloud/apps/encryption/lib/Crypto/Encryption.php","line":324,"function":"symmetricDecryptFileContent","class":"OCA\\Encryption\\Crypto\\Crypt","type":"->","args":["*** sensitive parameters replaced ***"]},{"file":"/www/htdocs/wxxxxxxxx/cloud/lib/private/Files/Stream/Encryption.php","line":470,"function":"decrypt","class":"OCA\\Encryption\\Crypto\\Encryption","type":"->","args":["*** sensitive parameters replaced ***"]},{"file":"/www/htdocs/wxxxxxxxx/cloud/lib/private/Files/Stream/Encryption.php","line":265,"function":"readCache","class":"OC\\Files\\Stream\\Encryption","type":"->","args":[]},{"function":"stream_read","class":"OC\\Files\\Stream\\Encryption","type":"->","args":[6728]},{"file":"/www/htdocs/wxxxxxxxx/cloud/3rdparty/icewind/streams/src/Wrapper.php","line":54,"function":"fread","args":[null,8192]},{"file":"/www/htdocs/wxxxxxxxx/cloud/3rdparty/icewind/streams/src/CallbackWrapper.php","line":94,"function":"stream_read","class":"Icewind\\Streams\\Wrapper","type":"->","args":[8192]},{"function":"stream_read","class":"Icewind\\Streams\\CallbackWrapper","type":"->","args":[8192]},{"file":"/www/htdocs/wxxxxxxxx/cloud/3rdparty/sabre/http/lib/Sapi.php","line":108,"function":"stream_copy_to_stream","args":[null,null,530037]},{"file":"/www/htdocs/wxxxxxxxx/cloud/3rdparty/sabre/dav/lib/DAV/Server.php","line":490,"function":"sendResponse","class":"Sabre\\HTTP\\Sapi","type":"::","args":[{"__class__":"Sabre\\HTTP\\Response"}]},{"file":"/www/htdocs/wxxxxxxxx/cloud/apps/dav/lib/Connector/Sabre/Server.php","line":215,"function":"invokeMethod","class":"Sabre\\DAV\\Server","type":"->","args":[{"__class__":"Sabre\\HTTP\\Request"},{"__class__":"Sabre\\HTTP\\Response"}]},{"file":"/www/htdocs/wxxxxxxxx/cloud/apps/dav/lib/Server.php","line":433,"function":"start","class":"OCA\\DAV\\Connector\\Sabre\\Server","type":"->","args":[]},{"file":"/www/htdocs/wxxxxxxxx/cloud/apps/dav/appinfo/v2/remote.php","line":25,"function":"exec","class":"OCA\\DAV\\Server","type":"->","args":[]},{"file":"/www/htdocs/wxxxxxxxx/cloud/remote.php","line":152,"args":["/www/htdocs/wxxxxxxxx/cloud/apps/dav/appinfo/v2/remote.php"],"function":"require_once"}],"File":"/www/htdocs/wxxxxxxxx/cloud/apps/encryption/lib/Crypto/Crypt.php","Line":457,"Hint":"Bad Signature","message":"Bad Signature","exception":"{\"class\":\"OCP\\Encryption\\Exceptions\\GenericEncryptionException\",\"message\":\"Bad Signature\",\"code\":0,\"file\":\"/www/htdocs/wxxxxxxxx/cloud/apps/encryption/lib/Crypto/Crypt.php:457\",\"trace\":\"#0 /www/htdocs/wxxxxxxxx/cloud/apps/encryption/lib/Crypto/Crypt.php(433): OCA\\Encryption\\Crypto\\Crypt->checkSignature('phtgkvtFdMDcExF...', '\\x89\\xF4\\nPl\\xDC\\xA2\\x88\\xD80E\\xD7;\\x95z...', '459984b3925e06f...')\\n#1 /www/htdocs/wxxxxxxxx/cloud/apps/encryption/lib/Crypto/Encryption.php(324): OCA\\Encryption\\Crypto\\Crypt->symmetricDecryptFileContent('phtgkvtFdMDcExF...', '\\x89\\xF4\\nPl\\xDC\\xA2\\x88\\xD80E\\xD7;\\x95z...', 'AES-256-CTR', 1, 65, false)\\n#2 /www/htdocs/wxxxxxxxx/cloud/lib/private/Files/Stream/Encryption.php(470): OCA\\Encryption\\Crypto\\Encryption->decrypt('phtgkvtFdMDcExF...', 65)\\n#3 /www/htdocs/wxxxxxxxx/cloud/lib/private/Files/Stream/Encryption.php(265): OC\\Files\\Stream\\Encryption->readCache()\\n#4 [internal function]: OC\\Files\\Stream\\Encryption->stream_read(6728)\\n#5 /www/htdocs/wxxxxxxxx/cloud/3rdparty/icewind/streams/src/Wrapper.php(54): fread(Resource id #1148, 8192)\\n#6 /www/htdocs/wxxxxxxxx/cloud/3rdparty/icewind/streams/src/CallbackWrapper.php(94): Icewind\\Streams\\Wrapper->stream_read(8192)\\n#7 [internal function]: Icewind\\Streams\\CallbackWrapper->stream_read(8192)\\n#8 /www/htdocs/wxxxxxxxx/cloud/3rdparty/sabre/http/lib/Sapi.php(108): stream_copy_to_stream(Resource id #1152, Resource id #1159, 530037)\\n#9 /www/htdocs/wxxxxxxxx/cloud/3rdparty/sabre/dav/lib/DAV/Server.php(490): Sabre\\HTTP\\Sapi::sendResponse(Object(Sabre\\HTTP\\Response))\\n#10 /www/htdocs/wxxxxxxxx/cloud/apps/dav/lib/Connector/Sabre/Server.php(215): Sabre\\DAV\\Server->invokeMethod(Object(Sabre\\HTTP\\Request), Object(Sabre\\HTTP\\Response))\\n#11 /www/htdocs/wxxxxxxxx/cloud/apps/dav/lib/Server.php(433): OCA\\DAV\\Connector\\Sabre\\Server->start()\\n#12 /www/htdocs/wxxxxxxxx/cloud/apps/dav/appinfo/v2/remote.php(25): OCA\\DAV\\Server->exec()\\n#13 /www/htdocs/wxxxxxxxx/cloud/remote.php(152): require_once('/www/htdocs/w00...')\\n#14 {main}\"}","CustomMessage":"Bad Signature"}}

Additional info

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    0. Needs triagePending check for reproducibility or if it fits our roadmap34-feedbackbug

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions