Reduce Fernet per-operation overhead (#15357)

* Reduce Fernet per-operation overhead

- Hoist the PKCS7(128) and SHA256() singletons to module level and cache
  the AES algorithm object on the Fernet instance instead of rebuilding
  all three (plus the deprecated-module attribute lookups they involve)
  on every encrypt/decrypt.
- MultiFernet.decrypt/decrypt_at_time/extract_timestamp now base64-decode
  and parse the token once, then try each key against the parsed data
  (mirroring what rotate() already did) instead of re-decoding the whole
  token for every key.
- Use memoryview slices for the HMAC region and ciphertext instead of
  copying (data[:-32] alone is a full copy per verify).

Benchmarks (Linux x86-64, CPython 3.11, release build, min of 7 timeit
repeats):

    Fernet.encrypt (64B):  15712 ns -> 12364 ns
    Fernet.decrypt (64B):  14245 ns -> 11221 ns
    MultiFernet (3 keys, oldest matches) decrypt of 1MiB token:
        12.76 ms -> 6.61 ms (1.9x)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C5bsKENedjVD9gcLApf1x4

* Cover MultiFernet.decrypt ttl path and extract_timestamp bad-signature path

MultiFernet.decrypt now computes time_info itself, and
extract_timestamp's terminal InvalidToken is only reachable with a
well-framed token whose signature matches no key (framing-invalid tokens
now raise during the initial parse). Neither path had a test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C5bsKENedjVD9gcLApf1x4

---------

Co-authored-by: Claude <noreply@anthropic.com>
2 files changed