Skip to content
This repository was archived by the owner on Apr 4, 2026. It is now read-only.

Commit 1125017

Browse files
committed
v3.4.1: One-shot photos, restore redesign, QR fingerprint, UI improvements
Features: - One-shot ephemeral photos with 2-phase secure deletion - Restore screen redesign: 24-word BIP-39 autocomplete grid - QR code fingerprint verification (SHA-256 hex + scanner) - Send confirmation dialog, progress bar, retry button - PIN forgot recovery via mnemonic phrase - Send indicator icon in sent bubbles Security: - Anti-navigation bypass: immediate DB flag on one-shot open - QR fingerprint uses hex encoding (no Unicode issues) - Room v17: added oneShotOpened column UI: - Protocol display updated to PQXDH in contact profile - Timestamp and maxWidth fixes in message bubbles - 29 layout audit with fixes - Custom scanner with torch toggle Docs: - All documentation updated (README FR/EN, SECURITY, CHANGELOG FR/EN, ARCHITECTURE FR/EN, CRYPTO FR/EN, STRUCTURE FR/EN) - Version 3.4.1 (versionCode 6)
1 parent 182c837 commit 1125017

41 files changed

Lines changed: 1566 additions & 216 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README-en.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@
5757

5858
- **PQXDH**: X25519 + **ML-KEM-768** (post-quantum)
5959
- **AES-256-GCM** + **Double Ratchet** with PFS + healing
60-
- **Fingerprint emojis** 96-bit anti-MITM
60+
- **Fingerprint emojis** 96-bit anti-MITM + **QR code scanner**
6161
- **Independent verification** per user + system messages
62-
- **BIP-39** backup (24 words)
62+
- **BIP-39** backup (24 words) + autocomplete grid
63+
- **One-shot photos** — view once, 2-phase secure deletion
6364
- Private key in **Android Keystore** (StrongBox when available)
6465
- Encrypted local DB **SQLCipher**
6566
- **Message padding** fixed-size (256/1K/4K/16K)
@@ -83,6 +84,7 @@
8384
- **Dynamic bubbles** colored by theme
8485
- **App Lock** PIN + biometrics
8586
- **Disappearing messages** (30s → 1 month)
87+
- **One-shot photos** view once 🔥
8688

8789
</td>
8890
</tr>
@@ -107,7 +109,7 @@
107109
|---|---------|---------|
108110
| 🔐 | **E2E Encryption** | PQXDH: X25519 + ML-KEM-768 + AES-256-GCM |
109111
| 🔄 | **Perfect Forward Secrecy** | Double Ratchet (DH + KDF chains) |
110-
| 🔏 | **Fingerprint emojis** | 96-bit, 16 emojis, anti-MITM |
112+
| 🔏 | **Fingerprint emojis + QR** | 96-bit, 16 emojis + QR code SHA-256, built-in scanner |
111113
|| **Independent verification** | Each user verifies separately, system message + clickable link |
112114
| 🛡️ | **DeviceSecurityManager** | StrongBox detection, MAXIMUM/STANDARD level |
113115
| 🕵️ | **Metadata hardening** | senderUid HMAC-hashed + messageIndex encrypted |
@@ -121,6 +123,7 @@
121123
| 📎 | **E2E file sharing** | Per-file AES-256-GCM via Firebase Storage |
122124
| 🔒 | **PBKDF2 PIN** | 600K iterations + salt (replaces SHA-256) |
123125
| ✍️ | **Ed25519 Signatures** | Every message signed, ✅/⚠️ badge anti-forgery |
126+
| 📸 | **One-shot photos** | View once (sender + receiver), 2-phase secure deletion |
124127

125128
</details>
126129

@@ -168,7 +171,7 @@
168171
| 🔒 | **App Lock** | 6-digit PIN + opt-in biometrics |
169172
|| **Auto-lock** | Configurable timeout (5s → 5min) |
170173
| 🔑 | **BIP-39 Backup** | 24 words to backup identity key |
171-
| ♻️ | **Restore** | Recover on a new device via mnemonic |
174+
| ♻️ | **Restore** | Autocomplete 24-word grid + recover on new device |
172175
| 🗑️ | **Full deletion** | Cleans Firebase (profile, inbox, convos, signing keys) |
173176
| 📵 | **Anonymous** | Zero number, zero email, zero tracking |
174177

@@ -245,7 +248,7 @@ cd SecureChat
245248
| E2E file sharing (AES-256-GCM + Firebase Storage) ||
246249
| PBKDF2 PIN (600K iterations + salt) ||
247250
| R8/ProGuard obfuscation + complete log stripping (d/v/i/w/e/wtf) ||
248-
| Fingerprint emojis 96-bit anti-MITM ||
251+
| Fingerprint emojis 96-bit anti-MITM + QR code SHA-256 scanner ||
249252
| App Lock (PIN + biometrics) ||
250253
| Restrictive Firebase security rules ||
251254
| BIP-39 backup/restore (24 words) ||
@@ -271,6 +274,10 @@ cd SecureChat
271274
| Delete-after-failure (cleanup failed messages from Firebase) ||
272275
| Atomic dual-listener deduplication (ConcurrentHashMap) ||
273276
| Signing key cleanup on account deletion ||
277+
| One-shot photos (view once, 2-phase secure deletion) ||
278+
| QR code fingerprint scanner (SHA-256 hex, CustomScannerActivity) ||
279+
| BIP-39 autocomplete 24-word grid (restore redesign) ||
280+
| Forgot PIN (recovery via mnemonic phrase) ||
274281

275282
> 📖 **Full Analysis**[`SECURITY.md`](SECURITY.md) · [Crypto Protocol](docs/en/CRYPTO.md)
276283
@@ -293,6 +300,7 @@ cd SecureChat
293300
| **V3.2** | Ed25519 Signing — Per-message signatures, ✅/⚠️ badge, Firebase rules hardening, signing key cleanup | ✅ Done |
294301
| **V3.3** | Material 3 + Tor + Attachment UX — M3 migration, full Tor integration, Session-style inline icons, Android 13+ permissions, log hardening | ✅ Done |
295302
| **V3.4** | PQXDH + Security — Post-quantum ML-KEM-768, deep link v2, QR name auto-fill, displayName hidden from Firebase, DeviceSecurityManager StrongBox, independent fingerprint verification, system messages, PQXDH desync fix, dual-listener fix, lastDeliveredAt | ✅ Done |
303+
| **V3.4.1** | One-Shot + UX — One-shot ephemeral photos, BIP-39 autocomplete grid, QR fingerprint scanner, send confirmation, progress bar, retry, 29 layout audit, forgot PIN | ✅ Done |
296304
| **V3.5** | Planned — App disguise + cover screen, Dual PIN, panic button, FLAG_SECURE, E2E voice messages, sealed sender, reply/quote | 🔜 |
297305

298306
> 📖 **Details**[Full Changelog](docs/en/CHANGELOG.md)
@@ -325,7 +333,7 @@ cd SecureChat
325333
| [**Crypto Protocol**](docs/en/CRYPTO.md) | X25519, Double Ratchet, fingerprint, threat model |
326334
| [**Setup**](docs/en/SETUP.md) | Prerequisites, Firebase, build, dependencies |
327335
| [**Structure**](docs/en/STRUCTURE.md) | Full project tree |
328-
| [**Changelog**](docs/en/CHANGELOG.md) | V1 → V3.4 history |
336+
| [**Changelog**](docs/en/CHANGELOG.md) | V1 → V3.4.1 history |
329337
| [**Security**](SECURITY.md) | Full audit, known limitations |
330338

331339
</div>
@@ -344,7 +352,7 @@ Provided for **educational** purposes. Use it as a definitive base to understand
344352
345353
<br/>
346354

347-
<img src="https://img.shields.io/badge/SecureChat-V3.4-7c3aed?style=for-the-badge&logo=android&logoColor=white" />
355+
<img src="https://img.shields.io/badge/SecureChat-V3.4.1-7c3aed?style=for-the-badge&logo=android&logoColor=white" />
348356

349357
<br/><br/>
350358

README.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@
5757

5858
- **PQXDH** : X25519 + **ML-KEM-768** (post-quantique)
5959
- **AES-256-GCM** + **Double Ratchet** avec PFS + healing
60-
- **Fingerprint emojis** 96-bit anti-MITM
60+
- **Fingerprint emojis** 96-bit anti-MITM + **QR code scanner**
6161
- **Vérification indépendante** par utilisateur + messages système
62-
- **BIP-39** backup (24 mots)
62+
- **BIP-39** backup (24 mots) + grille autocomplete
63+
- **Photos one-shot** — vue unique, suppression sécurisée 2 phases
6364
- Clé privée dans **Android Keystore** (StrongBox si dispo)
6465
- Base locale chiffrée **SQLCipher**
6566
- **Message padding** taille fixe (256/1K/4K/16K)
@@ -83,6 +84,7 @@
8384
- **Bulles dynamiques** colorées par thème
8485
- **App Lock** PIN + biométrie
8586
- **Messages éphémères** (30s → 1 mois)
87+
- **Photos one-shot** vue unique 🔥
8688

8789
</td>
8890
</tr>
@@ -107,7 +109,7 @@
107109
|---|---------|--------|
108110
| 🔐 | **Chiffrement E2E** | PQXDH : X25519 + ML-KEM-768 + AES-256-GCM |
109111
| 🔄 | **Perfect Forward Secrecy** | Double Ratchet (DH + KDF chains) |
110-
| 🔏 | **Fingerprint emojis** | 96-bit, 16 emojis, anti-MITM |
112+
| 🔏 | **Fingerprint emojis + QR** | 96-bit, 16 emojis + QR code SHA-256, scanner intégré |
111113
|| **Vérification indépendante** | Chacun vérifie de son côté, message système + lien cliquable |
112114
| 🛡️ | **DeviceSecurityManager** | Détection StrongBox, niveau MAXIMUM/STANDARD |
113115
| 🕵️ | **Metadata hardening** | senderUid hashé HMAC + messageIndex chiffré |
@@ -121,6 +123,7 @@
121123
| 📎 | **Fichiers E2E** | Chiffrement AES-256-GCM par fichier via Firebase Storage |
122124
| 🔒 | **PBKDF2 PIN** | 600K itérations + salt (remplace SHA-256) |
123125
| ✍️ | **Signature Ed25519** | Chaque message signé, badge ✅/⚠️ anti-falsification |
126+
| 📸 | **Photos one-shot** | Vue unique (sender + receiver), suppression sécurisée 2 phases |
124127

125128
</details>
126129

@@ -168,7 +171,7 @@
168171
| 🔒 | **App Lock** | PIN 6 chiffres + biométrie opt-in |
169172
|| **Auto-lock** | Timeout configurable (5s → 5min) |
170173
| 🔑 | **Backup BIP-39** | 24 mots pour sauvegarder la clé d'identité |
171-
| ♻️ | **Restauration** | Restaurer sur un nouvel appareil via phrase |
174+
| ♻️ | **Restauration** | Grille autocomplete 24 mots + restaurer sur nouvel appareil |
172175
| 🗑️ | **Suppression complète** | Nettoie Firebase (profil, inbox, convos, clés de signature) |
173176
| 📵 | **Anonyme** | Zéro numéro, zéro email, zéro tracking |
174177

@@ -245,7 +248,7 @@ cd SecureChat
245248
| Fichiers E2E (AES-256-GCM + Firebase Storage) ||
246249
| PBKDF2 PIN (600K itérations + salt) ||
247250
| R8/ProGuard obfuscation + log stripping complet (d/v/i/w/e/wtf) ||
248-
| Fingerprint emojis 96-bit anti-MITM ||
251+
| Fingerprint emojis 96-bit anti-MITM + QR code SHA-256 scanner ||
249252
| App Lock (PIN + biométrie) ||
250253
| Firebase security rules restrictives ||
251254
| BIP-39 backup/restore (24 mots) ||
@@ -293,6 +296,7 @@ cd SecureChat
293296
| **V3.2** | Ed25519 Signing — Signature par message, badge ✅/⚠️, durcissement Firebase rules, nettoyage clés | ✅ Done |
294297
| **V3.3** | Material 3 + Tor + Attachment UX — Migration M3, intégration Tor complète, icônes inline Session, permissions Android 13+, durcissement logs | ✅ Done |
295298
| **V3.4** | PQXDH + Security — ML-KEM-768 post-quantique, deep link v2, QR auto-fill nom, displayName masqué Firebase, DeviceSecurityManager StrongBox, vérification empreinte indépendante, messages système, fix désync PQXDH, fix dual-listener, lastDeliveredAt | ✅ Done |
299+
| **V3.4.1** | One-Shot + UX — Photos éphémères one-shot, grille BIP-39 autocomplete, QR fingerprint scanner, confirmation d'envoi, barre de progression, retry, audit 29 layouts, PIN oublié | ✅ Done |
296300
| **V3.5** | Planned — Camouflage app + faux écran, Dual PIN, panic button, FLAG_SECURE, messages vocaux E2E, sealed sender, reply/quote | 🔜 |
297301

298302
> 📖 **Détails**[Changelog complet](docs/fr/CHANGELOG.md)
@@ -325,7 +329,7 @@ cd SecureChat
325329
| [**Protocole Crypto**](docs/fr/CRYPTO.md) | X25519, Double Ratchet, fingerprint, modèle de menace |
326330
| [**Installation**](docs/fr/SETUP.md) | Prérequis, Firebase, build, dépendances |
327331
| [**Structure**](docs/fr/STRUCTURE.md) | Arbre complet du projet |
328-
| [**Changelog**](docs/fr/CHANGELOG.md) | Historique V1 → V3.4 |
332+
| [**Changelog**](docs/fr/CHANGELOG.md) | Historique V1 → V3.4.1 |
329333
| [**Sécurité**](SECURITY.md) | Audit complet, limites connues |
330334

331335
</div>
@@ -344,7 +348,7 @@ Fourni à des fins **éducatives**. Utilisez-le comme base pour comprendre le ch
344348
345349
<br/>
346350

347-
<img src="https://img.shields.io/badge/SecureChat-V3.4-7c3aed?style=for-the-badge&logo=android&logoColor=white" />
351+
<img src="https://img.shields.io/badge/SecureChat-V3.4.1-7c3aed?style=for-the-badge&logo=android&logoColor=white" />
348352

349353
<br/><br/>
350354

SECURITY.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
| Version | Supported |
66
|---------|-----------|
7-
| 3.4.x | ✅ Current |
7+
| 3.4.1 | ✅ Current |
8+
| 3.4.x | ⚠️ Outdated |
89
| 3.3.x | ⚠️ Outdated |
910
| 3.2.x | ⚠️ Outdated |
1011
| 3.1.x | ⚠️ Outdated |
@@ -38,6 +39,7 @@ SecureChat uses the following cryptographic primitives:
3839
| Message padding | 256 / 1024 / 4096 / 16384 bytes | 2-byte big-endian length header + random fill |
3940
| Conversation ID | SHA-256 | Hash of sorted public keys |
4041
| Fingerprint emojis | SHA-256 → 64-palette × 16 | 96-bit entropy, anti-MITM, independent verification per user |
42+
| Fingerprint QR code | SHA-256 hex (64 ASCII chars) | Hex encoding for QR scanner compatibility; `getSharedFingerprintHex()` |
4143
| Fingerprint events | Firebase notification (`verified:<ts>`) | Event-based notification only; verification state is strictly local per device |
4244
| senderUid hashing | HMAC-SHA256 | Keyed by conversationId, truncated to 128 bits |
4345
| PIN hashing | PBKDF2-HMAC-SHA256 | 600,000 iterations, 16-byte random salt |
@@ -181,3 +183,16 @@ SecureChat uses the following cryptographic primitives:
181183
-**Independent fingerprint verification**: each user's verified/unverified state is strictly local; Firebase event is a notification only (`fingerprintEvent: "verified:<timestamp>"`), never modifies the other user's state
182184
-**Fingerprint un-verify**: users can toggle verification status; button switches between "Marquer comme vérifié" / "Retirer la vérification"
183185
-**DB version 16**: added `lastDeliveredAt` column to Conversation entity (fallbackToDestructiveMigration)
186+
187+
### V3.4.1 One-Shot Photos, Restore Redesign & QR Fingerprint
188+
189+
-**One-shot ephemeral photos**: view-once images for both sender and receiver; 2-phase secure deletion (immediate DB flag + delayed file deletion)
190+
-**Anti-navigation bypass**: `flagOneShotOpened()` DAO flags DB immediately on click; physical file deleted after 5s coroutine delay; no `Handler.postDelayed` vulnerability
191+
-**One-shot file metadata**: `FILE|url|key|iv|fileName|fileSize|1` format; `oneShotOpened` column in Room DB
192+
-**QR code fingerprint**: fingerprint encoded as SHA-256 hex (64 ASCII chars) for QR; avoids Unicode emoji encoding mismatches
193+
-**QR fingerprint scanner**: uses `CustomScannerActivity` (same as contact invitation); hex comparison with `ignoreCase`; auto-verify on match
194+
-**`getSharedFingerprintHex()` method**: raw SHA-256 hex of sorted concatenated public keys (deterministic, encoding-safe)
195+
-**Restore screen BIP-39 grid**: 24 `AutoCompleteTextView` cells with BIP-39 autocomplete (2048 words); replaces single text field
196+
-**PIN recovery**: forgot PIN flow via mnemonic phrase verification
197+
-**Send confirmation dialog**: user confirms before sending files
198+
-**DB version 17**: added `oneShotOpened` column to MessageLocal entity (fallbackToDestructiveMigration)

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ android {
2222
applicationId = "com.securechat"
2323
minSdk = 33
2424
targetSdk = 35
25-
versionCode = 5
26-
versionName = "3.4.0"
25+
versionCode = 6
26+
versionName = "3.4.1"
2727

2828
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2929
}

app/src/main/java/com/securechat/crypto/CryptoManager.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,14 @@ object CryptoManager {
518518
return emojis.chunked(4).joinToString(" ") { it.joinToString("") }
519519
}
520520

521+
/** Returns the raw SHA-256 hex of the shared fingerprint (deterministic, safe for QR). */
522+
fun getSharedFingerprintHex(myPubKeyBase64: String, contactPubKeyBase64: String): String {
523+
val sorted = listOf(myPubKeyBase64, contactPubKeyBase64).sorted()
524+
val combined = (sorted[0] + sorted[1]).toByteArray(Charsets.UTF_8)
525+
val hash = MessageDigest.getInstance("SHA-256").digest(combined)
526+
return hash.joinToString("") { "%02x".format(it) }
527+
}
528+
521529
fun hmacSha256(key: ByteArray, data: ByteArray): ByteArray {
522530
val mac = javax.crypto.Mac.getInstance("HmacSHA256")
523531
mac.init(SecretKeySpec(key, "HmacSHA256"))

app/src/main/java/com/securechat/crypto/MnemonicManager.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ object MnemonicManager {
2828

2929
private var wordList: List<String> = emptyList()
3030

31+
/** Expose the loaded BIP-39 wordlist for autocomplete UI. */
32+
fun getWordList(): List<String> = wordList
33+
3134
fun init(context: Context) {
3235
if (wordList.isNotEmpty()) return
3336
wordList = context.resources.openRawResource(

app/src/main/java/com/securechat/data/local/MessageLocalDao.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ interface MessageLocalDao {
5656
@Query("UPDATE messages SET expiresAt = :expiresAt WHERE localId = :messageId")
5757
suspend fun setExpiresAt(messageId: String, expiresAt: Long)
5858

59+
@Query("UPDATE messages SET oneShotOpened = 1, localFilePath = NULL WHERE localId = :messageId")
60+
suspend fun markOneShotOpened(messageId: String)
61+
62+
@Query("UPDATE messages SET oneShotOpened = 1 WHERE localId = :messageId")
63+
suspend fun flagOneShotOpened(messageId: String)
64+
5965
@Query("SELECT * FROM messages WHERE localId = :messageId LIMIT 1")
6066
suspend fun getMessageById(messageId: String): MessageLocal?
6167

app/src/main/java/com/securechat/data/local/SecureChatDatabase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ import java.security.SecureRandom
5151
MessageLocal::class,
5252
RatchetState::class
5353
],
54-
version = 16,
54+
version = 17,
5555
exportSchema = false
5656
)
5757
abstract class SecureChatDatabase : RoomDatabase() {

app/src/main/java/com/securechat/data/model/MessageLocal.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,7 @@ data class MessageLocal(
4747
val fileName: String? = null, // Original file name
4848
val fileSize: Long = 0, // File size in bytes
4949
val localFilePath: String? = null, // Path to decrypted file on device
50-
val signatureValid: Boolean? = null // null = no signature, true = valid, false = invalid
50+
val signatureValid: Boolean? = null, // null = no signature, true = valid, false = invalid
51+
val isOneShot: Boolean = false, // true = image visible once then deleted
52+
val oneShotOpened: Boolean = false // true = one-shot image was already viewed
5153
)

0 commit comments

Comments
 (0)