Seifs / Sinetti v4.0

← Manalasdungeon.lv

Ylläpito: Manalas Dungeon  ·  Alusta: Android (Kotlin/Compose) + PHP/MySQL (nano.lv)

⇩  Lataa APK (Android)

Voit lähettää testiviestin nimimerkille Gauja.

Päästä päähän -salattu 1:1-pikaviestin, rakennettu tyhjästä oppimisprojektina. Käyttää Signal-protokollaa (X3DH + Double Ratchet) ja PHP-postilaatikkoa välitykseen. Palvelin ei missään vaiheessa näe viestien, kuvien tai tiedostojen sisältöä — vain läpinäkymättömiä salattuja tavuja.

Uutta v4.0:ssaryhmäviestit. Toteutettu mallilla jossa laite pitää jäsenlistan, ei palvelin: jäsenmuutokset kulkevat E2EE-ohjausviesteinä, ja lähettäjä levittää viestin jokaiselle jäsenelle erikseen olemassa olevan 1:1-session yli. Palvelin ei tiedä ryhmistä mitään eikä voi lisätä ketään huomaamatta — siksi myös aiemmat (allekirjoittamattomat) ryhmäendpointit poistettiin kokonaan.

Uutta v3.5:ssä — kaksi perusteellista tietoturvakatsausta ja niiden korjaukset: kaikki allekirjoitetut toiminnot sitovat nyt pyynnön sisällön (ei enää pelkkää toiminto+nimi+aikaleima) ja käyttävät nonce-toistosuojaa; vastapuolikohtainen lukko Signal-session tilalle; liiteblobi poistetaan vasta kuittauksen jälkeen; biometrinen lukko pysäyttää nyt myös taustatyön; identiteettivaroitus säilyy uudelleenkäynnistyksen yli; muistiturva- ja pysyvyyskorjauksia. Lisäksi kehittäjän oma hallintatyökalu.

Uutta v3.4:ssa — kolmas kieli (englanti, Vault) lisätty kielivalitsimeen, ja tälle sivulle lisätty suora latauslinkki APK:lle.

Uutta v3.3:ssa — korjattu neljä tarkkuuspuutetta luotettavuuslupauksissa, ja korjattu aito bugi push-mekanismissa: FCM lähetti aiemmin notification+data-yhdistelmäviestin, jolloin onMessageReceived ei Androidin dokumentoidun käytöksen mukaan käynnistynyt appin ollessa taustalla — tausta-pollaus ei siis koskaan laukennut pushista, vain appin avaamisesta. Push on nyt data-only; appi rakentaa ilmoituksen itse.

Uutta v3.2:ssä — allekirjoitettu send-pyyntö (nonce-pohjainen toistosuoja, sitoo lähettäjän, vastaanottajan ja sisällön tiivisteen) + palvelinpuolen esto, monikäyttäjätuki (jokaisella paikallisella nimimerkillä oma salattu kanta), WorkManager-pohjainen taustatyö (kestää prosessin sammumisen), ja liitteiden automaattinen uudelleenyritys.

v3.1 — epätoivotun lähettäjän esto (client-puolella, purun jälkeen).

v3.0 — kaksikielisyys (latvia/suomi), uusi nimi (Seifs/Sinetti), ilmoitusten äänimerkki ja värinä, hyökkäyspinnan kovennukset.

1. Yleiskuvaus

Keskeinen periaate: kryptografia on siirtotie-riippumaton. Palvelin välittää vain läpinäkymättömiä tavuja.

2. Ominaisuudet

OminaisuusTila
Päästä päähän -salaus (X3DH + Double Ratchet)valmis
Viestihistoria + keskustelulistavalmis
Lähetystila + kuittaukset + aikaleimatvalmis
Viestin poisto (paikallinen) + kopiointivalmis
Keskustelun poisto listaltavalmis
Lähettäjän esto (client- ja palvelinpuolella)valmis
Kuvaliitteet (E2EE) + automaattinen uudelleenyritysvalmis
Tiedostoliitteet (PDF, doc, zip…) + jako/tallennusvalmis
Safety number + identiteetin vaihtumisen varoitusvalmis
Kannan salaus levossa (SQLCipher + Keystore)valmis
Biometrinen sovelluslukitusvalmis
Push + äänimerkki + värinä (lokalisoitu)valmis
Nimimerkin omistus + postilaatikon suoja (Ed25519)valmis
Allekirjoitettu send + toistosuoja (nonce)valmis
Monikäyttäjätuki (per-nimimerkki oma kanta)valmis
Taustatyö WorkManagerillavalmis
Kolmikielisyys (LV/FI/EN) + kielivalitsinvalmis
Hallintatyökalu (vain kehittäjälle, ei julkinen)valmis
Allekirjoitettu release-APKvalmis
Ryhmäviestit (pareittainen levitys, laite pitää jäsenlistan)valmis

3. Arkkitehtuuri ja teknologiat

+-------------------+ HTTPS (polling + push) +----------------------+ | Laite | salatut viestit+liitteet | PHP-postilaatikko | | Kotlin/Compose | <-------------------------> | api.php + fcm.php | | libsignal | julkiset avaimet | MySQL | | BouncyCastle | <---- FCM notification --- | (avaimet, viestit, | | Room (SQLCipher) | | tiliavaimet, blobit,| | BiometricPrompt | | nonce, esto) | | WorkManager | | | +-------------------+ +----------------------+
KerrosTeknologia
UIJetpack Compose, Material 3 (vaalea latvialaisteema)
KieletAndroid-resurssit (values/, values-fi/, values-en/) + LocaleManager
Sovelluslukitusandroidx.biometric (biometria + laite-PIN)
Taustatyöandroidx.work (WorkManager, kestää prosessin sammumisen)
Viestisalausorg.whispersystems:signal-protocol-java:2.8.1
TiliavainBouncyCastle Ed25519 (bcprov-jdk18on)
LiitesalausAES/GCM (oma avain per liite)
TallennusRoom ×2 per nimimerkki, SQLCipher-salattuna
Avaimen suojausAndroid Keystore
VerkkoRetrofit + OkHttp
PushFirebase Cloud Messaging (HTTP v1)
PalvelinPHP 8 (PDO, sodium, openssl), MySQL

4. Turvallisuusmalli — kuusi kerrosta

  1. Sovelluslukitus. BiometricPrompt (BIOMETRIC_STRONG tai DEVICE_CREDENTIAL) joka kerta etualalle tullessa. Järjestelmä hoitaa varmennuksen; appi ei näe biometriaa eikä PIN-koodia, eikä omaa PIN-koodia tallenneta. Kuva-/ tiedostovalitsimen avaaminen ei laukaise lukitusta. Lukko pysäyttää myös taustatyön: automaattinen kirjautuminen, pollaussilmukka ja FCM-tokenin rekisteröinti eivät käynnisty ennen ensimmäistä onnistunutta tunnistautumista — aiemmin ne pyörivät lukitusnäytön takana. Vastaavasti sovellus lasketaan "etualalla olevaksi" (ilmoituslogiikkaa varten) vasta kun lukitus on aidosti avattu, ei jo Activityn käynnistyessä.
  2. Salaus levossa. Room-kannat SQLCipherilla (AES-256), avain kääritty Keystoreen. Jokaisella paikallisella nimimerkillä oma kanta (SHA-256-hajautettu tiedostonimi) — uloskirjautuminen tai nimen vaihto ei koskaan sekoita eri käyttäjien avaimia, sessioita tai historiaa keskenään. Rajoitus: puretut liitteet filesDirissä selväkielisenä.
  3. Nimimerkin omistus (Ed25519 + sodium) — allekirjoitus sitoo sisällön. register, token, addprekeys, ack, block, unblock allekirjoittavat nyt itse pyynnön sisällön (tiiviste identiteetistä, tokenista, estettävästä nimestä, kuitattavista viesti-ID:istä tai prekeyistä) yhdessä aikaleiman ja kertakäyttöisen noncen kanssa — ei enää pelkkää "toiminto|nimi|ts"-muotoa. Aiemmin voimassa oleva allekirjoitus olisi teoriassa kelvannut mille tahansa saman toiminnon sisällölle (esim. yhden käyttäjän eston allekirjoitus kenen tahansa estoon); nyt allekirjoitus pätee vain täsmälleen sille sisällölle joka allekirjoitettiin. poll on ainoa poikkeus — puhdas lukuoperaatio, jolla ei ole sidottavaa sisältöä. Palvelin varmentaa sodiumilla (±120 s ikkuna). TOFU ensirekisteröinnissä.
  4. Allekirjoitettu send + toistosuoja. Jokainen lähetys allekirjoitetaan erillisellä kanonisella muodolla, joka sitoo lähettäjän, vastaanottajan, Signal-viestityypin ja salatun rungon SHA-256-tiivisteen aikaleimaan ja kertakäyttöiseen nonceen (UUID v4). Palvelin tallentaa käytetyt noncet (send_nonces, PRIMARY KEY) — sama pyyntö ei koskaan hyväksytä kahdesti, eikä lähettäjää voi enää tekeytyä esiin väärentämällä from-kenttää.
  5. Päästä päähän -salaus. X3DH + Double Ratchet → forward secrecy + post-compromise security. Palvelin näkee vain julkiset avaimet ja salatut blobit.
  6. Identiteetin varmennus. Safety number ([#]). Jos vastapuolen avain vaihtuu, se hyväksytään (TOFU) mutta varoitus näytetään chatin otsikossa.

Push on data-only (tarkoituksella). Aiemmin FCM lähetti notification+data-yhdistelmäviestin, mutta Androidin dokumentoitu käytös taustalla on: jos viestissä on molemmat lohkot, onMessageReceived() ei käynnisty — data päätyy vain käynnistävän Activityn intent-extroihin. Tausta-pollaus ei siis koskaan laukennut itse pushista, vain siitä kun käyttäjä avasi appin käsin napauttamalla ilmoitusta. Push on nyt puhtaasti data-tyyppinen, jolloin onMessageReceived() käynnistyy aina, myös taustalla — appi rakentaa ilmoituksen itse (PushService, sama viestit_v2-kanava, ääni+värinä kanavatasolta) vain jos appi ei ole juuri sillä hetkellä etualalla. Sisältö ei koskaan kulje Googlen kautta — ilmoitusteksti on aina geneerinen, haetaan laitteen omista resursseista. (Ks. Firebase: Receive messages in Android apps.)

Lähettäjän esto — kaksi kerrosta. Client-puoli (ensisijainen): esto tarkistetaan purun jälkeen — Double Ratchet on siinä vaiheessa jo kryptografisesti todistanut lähettäjän henkilöllisyyden, joten estoa ei voi kiertää väärentämällä from-kenttää. Estetyn viesti puretaan, pudotetaan (ei tallenneta eikä näytetä) eikä kuittausta lähetetä → estetty näkee vain , ei koskaan ✓✓, eikä saa tietää estosta. Palvelinpuoli (kovennus): koska send on nyt allekirjoitettu, blocks-taulun tarkastus on turvallinen eikä kierrettävissä — estetyn viesti pudotetaan jo ovella, kaistaa säästäen, eikä push-herätystäkään lähetetä.

Vastapuolikohtainen lukko Signal-session tilalle. Kaikki saman vastapuolen session tilaa muuttavat operaatiot (session muodostus, salaus, purku) sarjallistetaan osoitekohtaisella lukolla. Aiempi pollauslukko suojasi vain vastaanottoa toista vastaanottoa vastaan — kaksi yhtäaikaista lähetystä, tai lähetys ja saapuvan viestin purku samaan aikaan, saattoivat molemmat lukea saman vanhan sessiotilan ja kirjoittaa päällekkäin, jolloin viimeisenä tallennettu voitti ja Double Ratchet saattoi mennä epäsynkkaan.

Hallintatyökalu (vain kehittäjälle). Sovelluksessa on sisäänrakennettu hallintanäkymä (käyttäjät, postilaatikon ja liitevaraston tila, estosuhteet). Se ei ole julkinen ominaisuus, mutta sen suoja ei myöskään perustu piilottamiseen: jokainen hallintakutsu allekirjoitetaan tilin omalla Ed25519-avaimella, ja palvelin tarkistaa lisäksi erikseen että allekirjoittaja on sallittujen listalla. Ilman oikeaa yksityisavainta jokainen kutsu hylätään, vaikka käyttöliittymäpolku löytyisikin.

Hyökkäyspinnan kovennukset:

Miksi erillinen Ed25519-tiliavain? Signal-identiteettiavain käyttää XEdDSA:ta, jota PHP:n libsodium ei varmenna. Android 15 vaatii BouncyCastlen.

Miksi bundle allekirjoittamatta? bundlen hakee vastapuoli (ei omistaja) — session aloittamiseksi tämä on oltava avoin.

5. Komponentit

Client

TiedostoRooli
MainActivity.ktNavigointi, kirjautuminen, pollaus, biometrinen lukitus (+ valitsin-suppressio), ilmoituskanava
AppContainer.ktDI-singleton, per-nimimerkki salatut kannat, migraatiot, Retrofit
DbKey.ktKeystore-suojattu SQLCipher-salasana
Session.ktMuistaa kirjautuneen nimen
Attachments.ktLiitteet: valmistelu, salaus, purku, kokorajat, tyyppiesto, per-käyttäjä-hakemisto
MessageRepository.ktKrypta + verkko + historia + liitteet + poistot + liitteen uudelleenyritys
MailboxWorker.ktWorkManager-työ: pollaus, FCM-token (rekisteröinti + poisto uloskirjautuessa), kestää prosessin sammumisen
PushService.ktFCM-vastaanotto, jonottaa työn MailboxWorkerille
crypto/SignalStores.ktSignal-storet + tiliavain + pysyvä identiteettiliputus
crypto/CryptoManager.ktlibsignal + Ed25519 (BouncyCastle)
net/MailboxApi.ktRetrofit-rajapinta + DTO:t
history/ChatHistory.ktViestihistoria + keskustelulista + poistot + estolista + uudelleenyrityksen tila
ui/ChatViewModel.ktAndroidViewModel (resurssit) — Compose-tila
ui/ChatScreen.ktUI: setup, lista, chat, kuplavalikot, liitteet, kielivalitsin
AdminRepository.ktHallintakutsujen allekirjoitus — ei julkinen
ui/AdminScreen.ktHallintanäkymä: käyttäjät, tilastot, estot — ei julkinen

Palvelin

TiedostoRooli
api.phpPostilaatikko-API, tiliavainten + send-allekirjoituksen varmennus, blob-varasto
fcm.phpFCM-lähetin (JWT → OAuth → notification+data, ääni, loc_key)
config.phpTunnukset (account-juuressa)
blobs/Salatut liiteblobit (.htaccess, siivotaan haun jälkeen + 30 vrk TTL)

6. Kolmikielisyys

Toteutus: Android-resurssit + per-app-kieli (LocaleManager, API 33+; minSdk = 35 tekee tästä natiivin ilman appcompatia).

7. Monikäyttäjätuki

Jokainen paikallisesti kirjauduttu nimimerkki saa oman Signal-kannan, chat-kannan ja liitehakemiston — ei jaettua tilaa. Kantojen tiedostonimet johdetaan nimimerkin SHA-256-tiivisteestä (storageId), joten uloskirjautuminen ja uuden nimen käyttöönotto eivät koskaan sekoita avaimia, sessioita, estolistoja tai historiaa keskenään.

Vanhojen kantojen siirto: ensimmäisellä käynnistyksellä päivityksen jälkeen appi siirtää edellisen version yhteiset kannat automaattisesti sen hetkisen kirjautuneen käyttäjän nimiavaruuteen. Jos tallennettua käyttäjää ei ole, vanhaa kantaa ei arvata kenenkään omaksi.

8. Tietovirrat

Kirjekuoret: {t:"msg"}, {t:"rcpt"}, {t:"img"}, {t:"file", name}.

9. Palvelimen API

ActionAllekirjoitusKuvaus
POST registerkyllä (sisältö + nonce)Laite + bundle + tiliavain
POST tokenkyllä (sisältö + nonce)FCM-token
POST addprekeyskyllä (sisältö + nonce)Lisää prekeytä
GET pollkylläHae omat viestit (vain omistaja)
POST ackkyllä (sisältö + nonce)Poista omat viestit (vain omistaja)
POST sendkyllä (nonce + tiiviste)Tallenna blob + push; palvelinpuolen esto
POST block / unblockkyllä (sisältö + nonce)Synkronoi estolista palvelimelle
admin_* (5 kpl)kyllä + admin-listaKehittäjän hallintatyökalu — ei julkinen
GET poolinfo / bundleeiPoolin tila / hae bundle
POST blobupload / blobdownload / blobdeleteeiLiiteblobit
creategroup / groupmembers / addmembereiRyhmät (nukkuu)

Virheet: 401 not_owner/stale/key_mismatch/invalid_send_signature, 403 not_admin, 400 missing_*/bad_blob, 404 no_blob, 409 replay_detected.

10. Tietokannat

Palvelin (MySQL, prefix manalasd_)

devices (+ fcm_token, account_key), one_time_prekeys, mailbox, send_nonces (toistosuoja), blocks (palvelinpuolen esto), chat_groups, group_members. Blobit tiedostoina.

pikaviesti.db — SQLCipher, v4, per nimimerkki

own_identity, prekeys, signed_prekeys, sessions, trusted_identities, sender_keys, account_key, identity_warnings (identiteetin vaihtumisen pysyvä merkintä — varoitus ei enää katoa sovelluksen uudelleenkäynnistyessä ennen kuin käyttäjä on kuitannut sen).

chat.db — SQLCipher, v7, per nimimerkki

messages (msgId, peer, fromMe, text, status, type, imagePath, fileName, ts, blobId, attachmentKey, attachmentIv, attachmentAttempts, attachmentNextRetryAt, senderName) · blocked (peer) · groups (groupId, nimi, luoja) · group_members (paikallinen jäsenlista — ryhmien totuudenlähde).

MIGRAATIOSÄÄNTÖ. Uusi entiteetti/sarake vaatii kolme muutosta yhdessä: (1) version nousee, (2) uusi migraatio määritellään, (3) se kytketään kannan rakentajaan. Versionumero = migraatioiden määrä + 1. Älä koskaan käytä tuhoavaa fallback-migraatiota — se hävittää käyttäjän datan hiljaa.

11. Luotettavuus

12. Käyttöliittymä

Vaalea latvialainen villakuvio-teema. Lukitusnäyttö, kirjautuminen kielivalitsimineen, keskustelulista (napautus avaa, pitkä painallus → Estä/Poista), ja chat-nakyma kuplineen, aikaleimoineen, tilamerkkeineen ja turvanumeroineen. Kuplan pitkä painallus avaa valikon (Kopet/Dzest tekstille, Kopigot/Dzest liitteelle).

13. Rajoitukset (tietoiset)

Tietoturvan jäännösriskit

Kohdat jotka on tietoisesti jätetty ratkaisematta, ja miksi ne eivät vaadi toimenpiteitä:

14. Rakennus ja käyttöönotto

Keskeiset riippuvuudet: signal-protocol-java, Room + KSP, Retrofit + OkHttp, coroutines, sqlcipher-android, firebase-messaging, bcprov-jdk18on, androidx.biometric, androidx.work, androidx.fragment 1.8.9+.

Kriittinen riippuvuus: fragment-ktx täytyy pakottaa uudempaan versioon. Vanha FragmentActivity validoi requestCoden 16 bittiin, mikä on yhteensopimaton modernin ActivityResultRegistryn kanssa → liitevalitsin kaataa appin.

Palvelin: huomaa erityisesti send_nonces (luodaan automaattisesti) ja blocks (luotava käsin, InnoDB, VARCHAR(191) yhtenäisyyden vuoksi). PHP:ssä oltava sodium-laajennus.

Release-APK: sama keystore joka kerta — muuten data haviaa päivityksessä. Pakettinimeä ei koskaan muuteta.

15. Vianetsintä

Kaikki send-pyynnöt epäonnistuvat palvelinvirheeseenblocks-taulua ei ole luotu palvelimelle.

Liitevalitsin kaataa appin → vanha androidx.fragment. Pakota uudempi versio.

Liitevalitsin vaatii uuden tunnistautumisen joka kerta → lukitus-suppressio ei ole kytkettynä loppuun asti.

Ilmoituksesta ei kuulu ääntä → ilmoituskanavan asetukset lukittuvat ensimmäisellä luonnilla; tarvitaan uusi kanava-id.

mutta ei ✓✓tarkista ensin yleisempi syy ennen nollausta. Kuittauksen lähetyksellä ei ole uudelleenyritystä — yksittäinen ohimenevä verkkovirhe kuittausta lähetettäessä riittää jättämään lähettäjän pysyvästi :hen, vaikka viesti purettiin ja näytettiin onnistuneesti vastaanottajalle. Tämä on yleisin syy eikä vaadi korjausta — sisältö on jo perillä. Nollaa tila kokonaan vasta jos useampi peräkkäinen viesti jää pysyvästi :hen usean istunnon yli — silloin kyse on aidosta Signal-session epäsynkasta.

Ominaisuus toimii vain toisella laitteella → toista ei ole päivitetty; tämä on projektin yleisin harhautus.

Nimimerkkiä kysytään joka avauksella → automaattinen kirjautuminen ei käynnisty. Tarkista että lukituksen tila välitetään käyttöliittymälle tilaoliona, ei sen hetkisenä arvona — muuten odotus jää ikuisesti jumiin eikä havaitse lukituksen avautumista.

Rekisteröinti epäonnistuu 401:llä uudelleenasennuksen jälkeen → odotettua. Tiliavain syntyy laitteella, joten uusi asennus ei voi todistaa vanhan nimen omistusta ("menetetty avain = menetetty nimi"). Nimi vapautetaan hallintatyökalun käyttäjänpoistolla.

16. Sanasto

TermiSelitys
X3DH / Double RatchetAvaintenvaihto + viestiavainten kierrätys
Safety numberAvaimista johdettu MITM-suojaluku
Tiliavain (Ed25519)Erillinen avain nimimerkin omistukseen + API-suojaan
NonceKertakäyttöinen tunniste joka estää pyynnön toiston
Kanoninen muotoTavu tavulta sama merkkijonoesitys molemmilla puolilla — allekirjoituksen perusta
TOFUTrust On First Use — luota ensin, varoita muutoksesta
WorkManagerAndroidin taustatyökehys — kestää prosessin sammumisen
storageIdNimimerkistä johdettu tiivistetunniste per-käyttäjä-kannoille
DekompressiopommiPieni tiedosto joka purkautuu valtavaksi → muistinsyönti
PostilaatikkoPalvelimen store-and-forward salatuille viesteille