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:ssa — ryhmä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
- Android-client (Kotlin, Jetpack Compose): kryptografia, käyttöliittymä, paikallinen tallennus, liitteet, push-vastaanotto, sovelluslukitus. Salaus ja purku tapahtuvat yksinomaan laitteella.
- PHP-relepalvelin (
api.php+fcm.php+ MySQL): "tyhmä postilaatikko". Liikuttaa julkisia avaimia, salattuja viesti- ja liiteblobeja, varmentaa nimimerkkien omistuksen ja lähettää herätyspushit. Ei salauslogiikkaa, ei pääsyä sisältöön.
Keskeinen periaate: kryptografia on siirtotie-riippumaton. Palvelin välittää vain läpinäkymättömiä tavuja.
2. Ominaisuudet
| Ominaisuus | Tila |
|---|---|
| Päästä päähän -salaus (X3DH + Double Ratchet) | valmis |
| Viestihistoria + keskustelulista | valmis |
| Lähetystila + kuittaukset + aikaleimat | valmis |
| Viestin poisto (paikallinen) + kopiointi | valmis |
| Keskustelun poisto listalta | valmis |
| Lähettäjän esto (client- ja palvelinpuolella) | valmis |
| Kuvaliitteet (E2EE) + automaattinen uudelleenyritys | valmis |
| Tiedostoliitteet (PDF, doc, zip…) + jako/tallennus | valmis |
| Safety number + identiteetin vaihtumisen varoitus | valmis |
| Kannan salaus levossa (SQLCipher + Keystore) | valmis |
| Biometrinen sovelluslukitus | valmis |
| 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ö WorkManagerilla | valmis |
| Kolmikielisyys (LV/FI/EN) + kielivalitsin | valmis |
| Hallintatyökalu (vain kehittäjälle, ei julkinen) | valmis |
| Allekirjoitettu release-APK | valmis |
| Ryhmäviestit (pareittainen levitys, laite pitää jäsenlistan) | valmis |
3. Arkkitehtuuri ja teknologiat
| Kerros | Teknologia |
|---|---|
| UI | Jetpack Compose, Material 3 (vaalea latvialaisteema) |
| Kielet | Android-resurssit (values/, values-fi/, values-en/) + LocaleManager |
| Sovelluslukitus | androidx.biometric (biometria + laite-PIN) |
| Taustatyö | androidx.work (WorkManager, kestää prosessin sammumisen) |
| Viestisalaus | org.whispersystems:signal-protocol-java:2.8.1 |
| Tiliavain | BouncyCastle Ed25519 (bcprov-jdk18on) |
| Liitesalaus | AES/GCM (oma avain per liite) |
| Tallennus | Room ×2 per nimimerkki, SQLCipher-salattuna |
| Avaimen suojaus | Android Keystore |
| Verkko | Retrofit + OkHttp |
| Push | Firebase Cloud Messaging (HTTP v1) |
| Palvelin | PHP 8 (PDO, sodium, openssl), MySQL |
4. Turvallisuusmalli — kuusi kerrosta
- 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ä. - 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ä.
- Nimimerkin omistus (Ed25519 + sodium) — allekirjoitus sitoo
sisällön.
register,token,addprekeys,ack,block,unblockallekirjoittavat 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.pollon ainoa poikkeus — puhdas lukuoperaatio, jolla ei ole sidottavaa sisältöä. Palvelin varmentaa sodiumilla (±120 s ikkuna). TOFU ensirekisteröinnissä. - 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ää. - Päästä päähän -salaus. X3DH + Double Ratchet → forward secrecy + post-compromise security. Palvelin näkee vain julkiset avaimet ja salatut blobit.
- 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:
- Saapuvan blobin kokoraja ennen purkua (
MAX_INCOMING_BYTES10 MB) — laite ei luota palvelimen rajaan sokeasti. - Suoritettavat tiedostotyypit estetty molempiin suuntiin
(
isBlockedType: apk, exe, sh, bat, dex, so, jar, msi, vbs…). - Dekompressiopommi-suoja: kuvat dekoodataan rajatusti
(
decodeBounded, max 2048 px). - Rajattu luku: lähtevät tiedostot ja kuvat luetaan kokorajan kanssa, ei rajattomasti muistiin ennen tarkistusta.
- SQL-jokerimerkkien suojaus: käyttäjänimen
%ja_pakotetaan kirjaimellisiksi session-hauissa, jottei nimi voi laajentaa haun kattamaa joukkoa. - Appi ei suorita saapuvaa koodia; luvat minimissä (INTERNET, POST_NOTIFICATIONS).
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
| Tiedosto | Rooli |
|---|---|
MainActivity.kt | Navigointi, kirjautuminen, pollaus, biometrinen lukitus (+ valitsin-suppressio), ilmoituskanava |
AppContainer.kt | DI-singleton, per-nimimerkki salatut kannat, migraatiot, Retrofit |
DbKey.kt | Keystore-suojattu SQLCipher-salasana |
Session.kt | Muistaa kirjautuneen nimen |
Attachments.kt | Liitteet: valmistelu, salaus, purku, kokorajat, tyyppiesto, per-käyttäjä-hakemisto |
MessageRepository.kt | Krypta + verkko + historia + liitteet + poistot + liitteen uudelleenyritys |
MailboxWorker.kt | WorkManager-työ: pollaus, FCM-token (rekisteröinti + poisto uloskirjautuessa), kestää prosessin sammumisen |
PushService.kt | FCM-vastaanotto, jonottaa työn MailboxWorkerille |
crypto/SignalStores.kt | Signal-storet + tiliavain + pysyvä identiteettiliputus |
crypto/CryptoManager.kt | libsignal + Ed25519 (BouncyCastle) |
net/MailboxApi.kt | Retrofit-rajapinta + DTO:t |
history/ChatHistory.kt | Viestihistoria + keskustelulista + poistot + estolista + uudelleenyrityksen tila |
ui/ChatViewModel.kt | AndroidViewModel (resurssit) — Compose-tila |
ui/ChatScreen.kt | UI: setup, lista, chat, kuplavalikot, liitteet, kielivalitsin |
AdminRepository.kt | Hallintakutsujen allekirjoitus — ei julkinen |
ui/AdminScreen.kt | Hallintanäkymä: käyttäjät, tilastot, estot — ei julkinen |
Palvelin
| Tiedosto | Rooli |
|---|---|
api.php | Postilaatikko-API, tiliavainten + send-allekirjoituksen varmennus, blob-varasto |
fcm.php | FCM-lähetin (JWT → OAuth → notification+data, ääni, loc_key) |
config.php | Tunnukset (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).
- Kielivalitsin (
LV | FI | EN) setup-ruudussa ja keskustelulistan headerissa. LocaleManager.applicationLocales = LocaleList.forLanguageTags("fi")→ Android muistaa valinnan itse ja näyttää sen myös puhelimen asetuksissa ("Sovellusten kielet").- Sovelluksen nimi vaihtuu kielen mukaan: Seifs / Sinetti / Vault.
- Ilmoitusteksti lokalisoidaan
loc_key-mekanismilla → Seifs / Jauna ziņa · Sinetti / Uusi viesti · Vault / New message. - Tilateksti seuraa metaforaa: seifs atvērts · sinetti murrettu · vault unlocked.
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
- Rekisteröinti/kirjautuminen: setup kysyy oman nimen →
allekirjoitettu
register→ TOFU tai omistuksen varmennus → keskustelulista. Pollaus kirjautumistasolla → lista päivittyy live. - Lähetys: tallenna
…→ salaa → allekirjoitettusend(nonce + tiiviste) + push →✓→ kuittaus →✓✓. - Vastaanotto: allekirjoitettu
poll→ pura → tallenna → kuittaa. Purun epäonnistuessa uudelleenyritys (max 5), ei viestihäviötä. - Liite: salaa omalla AES-avaimella →
blobupload→ metadata E2EE-viestinä → vastaanottajablobdownload→ purkaa → näyttää →blobdelete. Epäonnistuessa metadata säilyy ja uudelleenyritys tapahtuu automaattisesti (5 s → 6 h backoff, luovutus 8 yrityksen jälkeen). - Uloskirjautuminen: appi yrittää poistaa FCM-tokenin
palvelimelta heti (allekirjoitettu
token-kutsu tyhjällä arvolla). Jos se epäonnistuu (verkkokatko), pysyvä WorkManager-työ yrittää myöhemmin — mutta vain jos sama käyttäjä ei ole ehtinyt kirjautua takaisin sillä välin.
Kirjekuoret: {t:"msg"}, {t:"rcpt"},
{t:"img"}, {t:"file", name}.
9. Palvelimen API
| Action | Allekirjoitus | Kuvaus |
|---|---|---|
POST register | kyllä (sisältö + nonce) | Laite + bundle + tiliavain |
POST token | kyllä (sisältö + nonce) | FCM-token |
POST addprekeys | kyllä (sisältö + nonce) | Lisää prekeytä |
GET poll | kyllä | Hae omat viestit (vain omistaja) |
POST ack | kyllä (sisältö + nonce) | Poista omat viestit (vain omistaja) |
POST send | kyllä (nonce + tiiviste) | Tallenna blob + push; palvelinpuolen esto |
POST block / unblock | kyllä (sisältö + nonce) | Synkronoi estolista palvelimelle |
admin_* (5 kpl) | kyllä + admin-lista | Kehittäjän hallintatyökalu — ei julkinen |
GET poolinfo / bundle | ei | Poolin tila / hae bundle |
POST blobupload / blobdownload / blobdelete | ei | Liiteblobit |
creategroup / groupmembers / addmember | ei | Ryhmä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
- Ei viestihäviötä ohimenevissä virheissä: epäonnistuneet purut yritetään uudelleen (max 5). Rajoitus: jos purku epäonnistuu 5 kertaa peräkkäin, viesti luovutetaan ja poistetaan palvelimelta purkamattomana — tietoinen kompromissi joka estää yhtä viestiä tukkimasta koko jonoa, mutta voi harvinaisissa tapauksissa tarkoittaa pysyvää sisällön menetystä. Yrityslaskuri on vain muistissa ja nollautuu prosessin uudelleenkäynnistyessä.
- Sarjallistettu pollaus JA vastapuolikohtainen session-lukko: prosessinlaajuinen lukko estää rinnakkaisen purun, ja erillinen osoitekohtainen lukko sarjallistaa kaiken saman vastapuolen session tilaa muuttavan työn (muodostus, salaus, purku) — myös lähetyksen ja vastaanoton keskenään.
- Ei vuotavia silmukoita: pollaussilmukka peruuntuu uloskirjautuessa.
- Jumiviestien nollaus istunnon alussa.
- WorkManager-pohjainen kestävyys: säilyy prosessin sammumisen ja verkkokatkon yli, sisäänrakennettu uudelleenyritys.
- Liitteen automaattinen uudelleenyritys — yksisuuntainen:
koskee vain vastaanottajan epäonnistunutta lataamista. Lähettäjän
epäonnistunutta lähetystä (
blobupload) ei yritetä automaattisesti uudelleen — viesti jää tilaan epäonnistunut, ja käyttäjän on yritettävä itse. - Liiteblobi poistetaan vasta kuittauksen jälkeen: aiemmin blobi poistettiin palvelimelta heti purun jälkeen, mutta postilaatikon kuittaus tehtiin vasta koko erän lopuksi — kaatuminen niiden välissä olisi voinut jättää viestin uudelleen jonoon ilman että liite olisi enää saatavilla.
- Kriittinen tila kirjoitetaan synkronisesti: kannan salasana
ja kirjautumistila tallennetaan
commit()illa, eiapply()illa — muuten prosessin kuolema väärällä hetkellä olisi voinut jättää kannan pysyvästi avaamattomaksi tai palauttaa jo uloskirjatun istunnon takaisin. - Virheet luokitellaan taustatyössä: pysyviä virheitä (esim. 400/401) ei yritetä turhaan uudelleen; vain ohimenevät (verkko, 5xx, 408, 429) menevät uudelleenyritykseen.
- Prekey-taulu ei kasva epäonnistumisista: jos avainten lähetys palvelimelle epäonnistuu, seuraava yritys lähettää samat jo talletetut avaimet uudelleen sen sijaan että generoisi lisää päälle.
- Blobien siivous: poisto haun jälkeen + 30 vrk TTL.
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)
- Yksi laite per käyttäjä, mutta useampi nimimerkki samalla laitteella onnistuu (erilliset kannat).
- Menetetty tiliavain = menetetty nimi.
- Ryhmät eivät toimi — vain perusta.
- Push ei saavu swaipatulle appille.
- Kellon oltava ±120 s tarkka — koskee pollausta ja lähetystä.
- Puretut liitteet selväkielisenä levyllä.
- Liitteen raja 8 MB (saapuva 10 MB).
- Yhteys vaatii nimimerkin tietämisen — ei hakemistoa (tietoinen yksityisyysvalinta).
- Poisto on paikallinen.
- Optimistinen lähetys; vastaanottoaikaleima; TOFU-luottamus.
Tietoturvan jäännösriskit
Kohdat jotka on tietoisesti jätetty ratkaisematta, ja miksi ne eivät vaadi toimenpiteitä:
- API-token on APK:ssa selkokielisenä. Sitä ei voi salata
mielekkäästi — appi joutuisi purkamaan salauksen itse ajon aikana, jolloin
purkuavain olisi yhtä lailla APK:ssa (turvallisuusteatteria). Vuotanut token
mahdollistaa postilaatikoiden roskatäytön, mutta ei sisällön lukemista —
poll/ack/block/unblockvaativat tiliavaimen allekirjoituksen. - Reverse engineering paljastaa rakenteen, ei avaimia. APK on aina purettavissa; se paljastaa logiikan ja endpointit mutta ei käyttäjäkohtaisia salaisia avaimia, jotka syntyvät vasta laitteella ajon aikana ja elävät Keystore-suojatussa kannassa.
- Esto on ensisijaisesti client-puolinen. Palvelinpuolen
blocks-taulu on kovennus (säästää kaistaa), mutta totuudenmukainen esto tapahtuu aina purun jälkeen laitteella — tätä ei voi kiertää väärentämälläfrom-kenttää, koska Double Ratchet on jo siinä vaiheessa todistanut lähettäjän. - Tiedostotyyppiesto perustuu päätteeseen. Muutettu tiedostopääte kiertäisi eston teknisesti, mutta ei hyödytä hyökkääjää — käyttöjärjestelmä tarkistaa todellisen sisällön avatessaan.
- Purettu sisältö on selväkielisenä levyllä. Kannan salaus ja biometrinen lukitus kattavat pääsyn appin kautta; suurin jäljellä oleva riski on fyysinen pääsy juurioikeuksin avattuun laitteeseen.
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-ktxtäytyy pakottaa uudempaan versioon. VanhaFragmentActivityvalidoi requestCoden 16 bittiin, mikä on yhteensopimaton moderninActivityResultRegistryn 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 palvelinvirheeseen
→ blocks-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
| Termi | Selitys |
|---|---|
| X3DH / Double Ratchet | Avaintenvaihto + viestiavainten kierrätys |
| Safety number | Avaimista johdettu MITM-suojaluku |
| Tiliavain (Ed25519) | Erillinen avain nimimerkin omistukseen + API-suojaan |
| Nonce | Kertakäyttöinen tunniste joka estää pyynnön toiston |
| Kanoninen muoto | Tavu tavulta sama merkkijonoesitys molemmilla puolilla — allekirjoituksen perusta |
| TOFU | Trust On First Use — luota ensin, varoita muutoksesta |
| WorkManager | Androidin taustatyökehys — kestää prosessin sammumisen |
| storageId | Nimimerkistä johdettu tiivistetunniste per-käyttäjä-kannoille |
| Dekompressiopommi | Pieni tiedosto joka purkautuu valtavaksi → muistinsyönti |
| Postilaatikko | Palvelimen store-and-forward salatuille viesteille |