Nemrég egy betűket és számokat tartalmazó kódolt szöveget kaptam azzal a kéréssel, hogy próbáljam megfejteni. A titkosított szöveget a következő formátumban kaptam: 88.222.666.333.444.888.33.333. Azt is lehetett róla tudni, hogy a megfejtés csak az angol ábécé betűit és számokat tartalmazhat. Ilyen és ehhez hasonló kódok megfejtéseit az Ingress nevű AR (augmented reality) játékban lehet felhasználni (Android és iOS platformon is elérhető), ahol a játék fejlesztői mindig valamilyen egyszerűbb kódolással juttatják el a játékosok egy csoportja számára a kódokat, amiért a játékban extra felszereléshez lehet jutni. Az előző fordulókban már találkoztam Base64 és Morse-kódolással is, így gyanítottam, hogy a mostani feladvány megfejtése sem lehet nehéz feladat. Úgy gondoltam, hogy a számok közötti pontok egy-egy karakter elválasztását jelenthetik, míg a számjegyek darabszáma is hordozhat hasznos információt, nem csak az értékük. Informatikus lévén rögtön az ASCII tábla jutott eszembe, de bárhogy próbáltam valamilyen leképező függvényt alkotni, nem sikerült a számokat leszűkíteni a betűk és számok tartományára. A végső megoldást egy csapattársam segített megtalálni, aki a jobb oldalon látható képet küldte el nekem.
Például kódoljuk a SZOFTVERFEJLESZTES szöveget és ezt kapjuk: 7777.9999.666.333.8.888.33.777.333.33.5.555.33.7777.9999.8.33.7777, amit dekódolva természetesen visszakapjuk az eredeti szöveget. Hogyan működik mindez?
Tegyük fel, hogy a kódolás és dekódolás során csak az angol ábécé nagybetűit és a szóközt fogjuk használni. Hasznos néhány konstans deklarációja: a nyomógombok feliratai szövegként ( TABLE1) és tömbben ( TABLE2), szeparátorok nélküli ábécé ( TABLE3) a kódolás elvégzéséhez, valamint a dekódoláshoz szükséges szöveg ( TABLE4):
1 2 3 4 5 6 7 8 |
private static final String TABLE1=" ;;ABC;DEF;GHI;JKL;MNO;PQRS;TUV;WXYZ"; private static final String[] TABLE2=TABLE1.split(";"); private static final String TABLE3=String.join("", TABLE2); //" ABCDEFGHIJKLMNOPQRSTUVWXYZ" private static final String TABLE4="023456789"; |
A kódolás (titkosítás) lépései
A kódolás elvégzését ellenőrzésnek kell megelőznie, hiszen a paraméterként átvett szöveg (
text) nem kódolható ha üres (
isEmpty()) vagy érvénytelen karaktert tartalmaz (olyat, ami nem szerepel a telefon nyomógombjain: ékezetes vagy írásjel). Bármilyen probléma esetén a kódoló metódus kivételt dob. A kódolás során a szöveget automatikusan nagybetűsként értelmezzük.
A kódolás során minden karakter (pl.:
E) esetén ki kell választani, hogy a
TABLE2 tömb melyik elemében szerepel (pl.:
j=3, a nyomógomb felirata
DEF) és a
j-edik elemben tárolt szöveg hányadik pozícióján található (pl.:
index=1). Tehát tudjuk, hogy a
C karakter kódja
33, azaz ehhez a 3-as gombot kétszer (
index+1) kell lenyomni. A Java nyelvben tömbök indexelése és a szövegben lévő karakterek pozíciója is nulla bázisú sorszámmal történik. A karakterek 1-4 (változó) hosszú kódjai közé pont kerül (
coded).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public static String coding(String text) throws IllegalArgumentException { //ellenőrzés if(text.isEmpty()) throw new IllegalArgumentException("Empty string for coding."); text=text.toUpperCase(); if(!isValidText(text, TABLE3)) throw new IllegalArgumentException( "Invalid character found in coding string."); //kódolás String coded=""; for(int i=0; i<text.length(); i++) { int j=0; while(!TABLE2[j].contains(""+text.charAt(i))) j++; int index=TABLE2[j].indexOf(""+text.charAt(i)); for(int k=0; k<=index; k++) coded+=j; coded+='.'; } return coded.substring(0, coded.length()-1); } |
A dekódolás (visszafejtés) lépései
A dekódolás elvégzését is ellenőrzésnek kell megelőznie, hiszen a paraméterként átvett szöveg (
text) nem dekódolható ha üres (
isEmpty()) vagy érvénytelen karaktert tartalmaz (olyat, ami nem feleltethető meg a telefon nyomógombjain található karakterek egyikének). Bármilyen probléma esetén a dekódoló metódus is kivételt dob.
A dekódolás során minden karakter kódja (pl.:
33) esetén szükség van annak hosszára (
length=2) és első karakterére számként (
index=3). Ezek alapján tudjuk, hogy a
TABLE2 tömb
index-edik (
DEF) elemének
length-1-edik eleme a dekódolt karakter (
E). A dekódoló metódus nem tesz szeparátort a dekódolt karakterek (
decoded) összefűzése során. A változó hosszúságú kódolt szöveg elemeiből egykarakteres dekódolt szövegdarabok keletkeznek.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public static String decoding(String text) throws IllegalArgumentException { //ellenőrzés if(text.isEmpty()) throw new IllegalArgumentException("Empty string for decoding."); String[] text2=text.split("\\."); String text3=String.join("", text2); if(!isValidText(text3, TABLE4)) throw new IllegalArgumentException( "Invalid character found in decoding string."); //dekódolás String decoded=""; for(int i=0; i<text2.length; i++) { int index=Integer.parseInt(""+text2[i].charAt(0)); int length=text2[i].length(); decoded+=TABLE2[index].charAt(length-1); } return decoded; } |
Az ellenőrzés lépései
A logikai értékkel visszatérő ellenőrző függvény ( isValidText()) feladata eldönteni, hogy a kódolás/dekódolás során használandó szöveg ( text) minden karaktere feldolgozható, azaz a folyamat során értelmezhető (másképpen: a validCharacters szöveg tartalmazza). Optimális esetben a text hossza megegyezik a benne lévő feldolgozható/értelmezhető karakterek számával (végighalad a ciklus a text-en), egyébként leáll a ciklus az első problémás karakternél.
1 2 3 4 5 6 7 |
private static boolean isValidText( String text, String validCharacters) { int i=0; while(i<text.length() && validCharacters.contains(""+text.charAt(i))) i++; return (i==text.length()); } |
A bejegyzéshez tartozó teljes forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.
Ez a feladat a Java SE szoftverfejlesztő tanfolyam szakmai moduljának 21-24. óra: Objektumorientált programozás, 2. rész alkalmához, valamint minden tanfolyamunk orientáló moduljának 1-4. óra: Programozási tételek alkalmához kapcsolódik.
A kódolást olvasva legelőször a Caesar-féle kódolás jut eszembe, ami hasonló módon működik. Csak ez esetben a számok száma jelöl egy betűt, míg a Caesar esetében a betűket toljuk el valamilyen ábécében. Például: https://www.rejtjelezo.hu/monoalfabetikus-abc-eltolas-ismerteto
No mit kódoltam? 2019-ben érdemes megfejteni.
2.55.444.0.6.33.4.0.33.22.22.33.66.0.2.9999.0.33.888.22.33.66.0.5.33.555.33.66.8.55.33.9999.444.55.0.888.2.555.2.6.33.555.999.444.55.0.8.2.66.333.666.555.999.2.6.88.66.55.777.2.0.33.7777.0.2.0.5.33.555.33.66.8.55.33.9999.33.7777.0.88.777.555.2.7.666.66.0.2.0.66.33.888.33.0.6.33.555.555.33.0.55.33.777.33.55.0.9999.2.777.666.5.33.555.22.33.66.0.22.33.444.777.5.2.0.2.0.8.2.66.333.55.33.3.888.8.33.555.55.666.3.0.55.666.3.666.8.0.2.9999.0.55.2.7.0.8.444.9999.0.7777.9999.2.9999.2.555.33.55.0.55.33.9999.3.888.33.9999.6.33.66.999.8.0.2.0.8.2.66.333.666.555.999.2.6.0.3.444.5.2.22.666.555
A kódolás/dekódolás témában egy másik blog bejegyzésünk: Rácsrejtjelezés.