Encryption Architecture
Encryption Architecture
This page documents the complete security architecture of cloud-share, including every cryptographic operation performed on items in transit.
End-to-end encryption model
Cloud Share encrypts content at the application layer on the sender before it leaves the process memory. The Dev Tunnel and HTTPS add a second layer of transport encryption. The receiver decrypts at the application layer after downloading.
sequenceDiagram
participant SP as Sender Process
participant DT as Dev Tunnel / HTTPS
participant RP as Receiver Process
SP->>SP: SHA-512(plaintext) → stored
SP->>SP: AES-256-GCM encrypt<br/>(key=SHA-256(secret), nonce=random 12B)
SP->>SP: Base64(nonce ‖ cipher ‖ tag)
SP->>DT: Send over HTTPS/TLS
DT->>RP: Encrypted payload
RP->>RP: Base64 decode
RP->>RP: AES-256-GCM decrypt
RP->>RP: SHA-512 verify
RP->>RP: ✅ Plaintext content delivered
Key derivation
Secret generation
On each sender start, a 12-character alphanumeric secret is generated using a cryptographically secure random number generator. It is:
- Never written to disk
- Never logged
- Invalidated when the sender process stops
Key derivation function
flowchart LR
A["secret\n12-char alphanumeric"] --> B["SHA-256\nUTF-8 bytes of secret"]
B --> C["32-byte key\n256 bits → AES-256 key"]
| Parameter | Value |
|---|---|
| Input | 12-char alphanumeric secret (e.g. aB3kP9mQ2rTs) |
| Function | SHA-256 |
| Output | 32 bytes (256 bits) — used directly as AES-256 key |
This is a one-way transformation: given the 32-byte key, it is computationally infeasible to recover the secret.
AES-256-GCM encryption
Each queue item is individually encrypted with a unique nonce:
Encryption
flowchart LR
A["SecureRandom\n12 bytes"] --> B[nonce]
C["SHA-256(secret)"] --> D[key 256-bit]
B --> E["AES-256-GCM.Encrypt\nkey, nonce, plaintext"]
D --> E
F[plaintext] --> E
E --> G["(ciphertext, auth_tag)"]
G --> H["Base64(nonce ‖ ciphertext ‖ auth_tag)"]
H --> I[payload stored in queue]
Decryption
flowchart TD
A["Base64Decode(payload)"] --> B[bytes]
B --> C["nonce = bytes[0..11]"]
B --> D["auth_tag = bytes[last 16B]"]
B --> E["cipher = bytes[middle]"]
C --> F["AES-256-GCM.Decrypt\nkey, nonce, cipher, auth_tag"]
D --> F
E --> F
F --> G{Auth tag valid?}
G -- Yes --> H[plaintext]
G -- No --> I[❌ Reject — tampered]
If the auth_tag does not match (ciphertext was tampered), decryption throws and the item is rejected.
| Parameter | Value |
|---|---|
| Algorithm | AES (Advanced Encryption Standard) |
| Key size | 256 bits |
| Mode | GCM (Galois/Counter Mode) |
| Nonce | 12 bytes, randomly generated per item |
| Auth tag | 16 bytes |
| Encoding | Base64 |
ℹ️ Info: GCM mode is an authenticated encryption scheme. The authentication tag protects both the ciphertext and any associated data. Any modification to the ciphertext — even a single flipped bit — causes tag validation to fail.
SHA-512 integrity hash
Why both GCM auth tag and SHA-512?
- The GCM auth tag verifies the ciphertext was not tampered with in transit
- The SHA-512 hash is computed on the plaintext before encryption, providing an independent integrity check at the content level
This gives defense-in-depth: even if decryption succeeds, an additional application-level hash comparison confirms the content is exactly what the sender intended to share.
Hash computation (sender)
hash = SHA-512( plaintext_bytes )
The hex-encoded hash string is stored alongside the encrypted item in the queue and returned to the receiver in the item metadata JSON.
Hash verification (receiver)
flowchart TD
A["AES-256-GCM.Decrypt(...)"] --> B[decrypted_bytes]
B --> C["SHA-512(decrypted_bytes)\n= computed_hash"]
D[item_metadata.hash\n= expected_hash] --> E
C --> E{"FixedTimeEquals\ncomputed_hash == expected_hash?"}
E -- Yes --> F[✅ Accept item]
E -- No --> G["❌ DELETE /item/{id}\nDiscard content, show error"]
Constant-time comparison
The SHA-512 hash comparison uses constant-time equality (CryptographicOperations.FixedTimeEquals in .NET). This prevents timing side-channel attacks where an attacker could infer the correct hash byte-by-byte by measuring how long the comparison takes.
In standard string comparison, the function returns early when it finds the first differing byte — the time taken leaks information about how many bytes match. Constant-time comparison always takes the same amount of time regardless of where the difference occurs.
What happens on hash mismatch
A hash mismatch is treated as a serious integrity failure:
- Receiver UI shows a red error modal: “Hash mismatch — content may have been tampered with”
- Receiver sends
DELETE /item/{id}?secret=Xto the sender - Sender permanently removes the item from the queue
- Decrypted content is discarded in memory — never written to disk
- The item cannot be received again from the same sender session
Additional security controls
Filename sanitization
File and zip item filenames are sanitized on the sender to prevent path traversal attacks. Characters like ../, ..\\, and null bytes are stripped or rejected before the filename is stored.
File size limits
| Item type | Limit |
|---|---|
| Text | 10 MB |
| File | 100 MB |
| Zip bundle | 100 MB |
Requests exceeding these limits are rejected with 400 Bad Request before any content is processed.
Request authentication
Every endpoint except GET / requires the ?secret=<token> query parameter. Requests with a missing or incorrect secret receive 401 Unauthorized. The secret is compared in a way that avoids early-exit to prevent timing attacks on the secret itself.
HTTPS transport
All traffic traverses the Dev Tunnel over HTTPS (TLS 1.2/1.3). The tunnel URL uses a valid certificate from *.devtunnels.ms. This provides:
- Encryption of the HTTP layer (including the secret in query parameters)
- Server authentication (the tunnel URL is genuine DevTunnels infrastructure)
- Protection against man-in-the-middle attacks at the transport layer
Security summary
| Threat | Mitigation |
|---|---|
| Eavesdropping in transit | AES-256-GCM + HTTPS/TLS |
| Content tampering in transit | GCM authentication tag + SHA-512 hash |
| Unauthorized access to queue | Secret token on every API call |
| Timing attacks on secret | Constant-time comparison |
| Timing attacks on hash | FixedTimeEquals constant-time comparison |
| Path traversal via filename | Filename sanitization |
| Oversized payload DoS | 10 MB / 100 MB limits |
| Persistent access after session | Secret invalidated on sender stop |
| Zip content interception | Optional AES-256 zip-level encryption |