Ohjelmistotekniikan seminaarityö
Markku-Juhani O. Saarinen
mjos@math.jyu.fi
1. Suurten kokonaislukujen aritmetiikkaa
2. RSA:n tarvitsemia algoritmeja
2.1 Modulaarieksponentaatio
2.2 Suurimman yhteisen tekijän ja multiplikatiivisten käänteislukujen
laskeminen
2.3 Satunnaisluvut
2.4 Suurten satunnaisten alkulukujen luominen
3. RSA-järjestelmän toiminta
3.1 Julkisen ja salaisen avaimen luominen
3.2 RSA:n perustoiminnot
3.3 Hybridi-RSA
3.4 RSA:n turvallisuus
4. Esimerkki ZenRSA:n toiminnasta
A. Suurten luonnolisten lukujen luokka kbn
- kbn.h
- kbn.cxx
B. Kryptoalgoritmit RC4 ja SHA
- rc4.h
- rc4.cxx
- sha.h
- sha.cxx
C. ZenRSA:n lähdekoodi
- zrsa.cxx
- Makefile
Disclaimer:
Lähdekoodit eivät ole hirvittävän kauniissa kunnossa nyt. Valmistelen julkaisevani koko jutun "oikeasti" lähiaikoina. Never the less: Jos löydät näistä koodeista jonkun puutteen tai bugin tms niin ole hyvä ja ilmoita siitä minulle! ( Siellä nimittäin 100% todennäköisyydellä on yhtä sun toista bugia/thinkoa, kuten kaikissa upouusissa ohjelmissa aina. Älä kuitenkaan pelästy; juttu on todistettavasti toiminut oikein ainakin minun koneessani! )
Aluksi käsitellään hieman RSA-järjestelmän vaatimien suurten lukujen käsittelyä.
Peruskoulussa esitettävät yhteen- ja vähennyslasku ovat lineaarisia pidemmän luvun pituuden suhteen, ja ne ovatkin (kompleksisuudeltaan) näin ollen varsin edullisia.
Tietokonesovellutuksessa on tietenkin järkevää käyttää mahdollisimman
suurikantaista sisäistä esitystä, jotta operaatioiden määrä saadaan
mahdollisimman pieneksi. Tyypillinen kanta onkin 256 tai suurempi, omassa
toteutuksessani 4096 = 12 bittiä. Tätä kovin paljon suurempia kantalukuja
ei ole järkevä käyttää, sillä silloin välitulokset saattavat kasvaa
suuremmiksi kuin koneen prosessorin rekisterityyppi (esim. unsigned int)
pystyy pitämään sisällään.
Klassiset algoritmit ovat yksinkertaisuutensa takia nopeimpia menetelmiä
nykyisillä avaimen pituuksilla vaadittavassa aritmetiikassa, mutta
avainten pituuksien kasvaessa (ei edes kovin huomattavasti)
kerto- ja jakolaskun neliöllinen kompleksisuus käy liian epäedulliseksi.
Oletetaan, että halutaan kertoa kaksi 2n-bittistä lukua u ja v
s.e :
Nähdään, että 2n-bittisen luvun kertolaskuun tarvittiin vain kolme
kertolaskua ja lisäksi muutama nopea bitin pyöritys
ja yhteenlasku. Menetelmää voi kayttää
myös rekursiivisesti, jolloin päästään kompleksisuusluokkaan
O (nlog2 3) eli likimain O (n1.5850).
Menetelmä ei ole sidottu binäärijärjestelmään. Esimerkiksi kertolasku
3142*2718 (desimaalijärjestelmässä) muuttuu muotoon:
Liukulukujen (double-tyypin) tarkkuus loppuu kesken vasta satojen tuhansien
desimaalien pituisia lukuja käsiteltäessä. FFT voidaan toteuttaa myös käyttäen
modulaariaritmetiikkaa, jolloin päästään eroon aproksimatiivisesta
liukulukumaailmasta vielä tätäkin suuremman tarkkuuden piiriin.
[PRE92, ss. 918-925] [KNU81, ss. 290-295] [COR90 s. 799]
Yli neliöllisellä nopeudella toimiva jakolaskualgoritmi saadaan
aikaiseksi Karatsuban- tai FFT-menetelmistä käyttäen vanhaa kunnon
Newtonin menetelmää. Iteraatio luvun V käänteisluvun
Un = 1/V laskemiseksi voidaan kirjoittaa:
Tässä M, C, d ja n ovat kukin esimerkiksi 1024-bittisiä. On selvää, että
Tässä esimerkissä kbn on oma suuren tarkkuuden luonnollisten lukujen käsittelyyn
kirjoittamani luokka. Käytännössä siis tarkastellaan eksponentin bittejä
vähemmän merkitsevästä enemmän merkitsevään päin. Jos bitti on eksponentissa päällä,
muuttuja r kerrotaan mod n välituloksella a. Muuttuja a neliöidään mod n joka
kierroksella. Nähdään että tämä menetelmä tarvitsee keskimäärin O ( log b )
neliöintiä ja kertolaskua (mod n). Itse kompleksisuusluokka ajan suhteen
riippuu siitä, miten nämä operaatiot on toteutettu.
[DEN82, s. 39]
Nopeampiakin menetelmiä on esitetty, mutta ne soveltuvat lähinnä
tapauksiin, joissa eksponentti pysyy vakiona (esim. optimoiviin kääntäjiin).
[KNU81, s. 441-458]
RSA-järjestelmän avainten generoinnissa on laskettava multiplikatiivisiä
käänteislukuja. Tämä tarkoittaa positiivisen kokonaisluvun a-1
etsimistä, joka toteuttaa yhtälön
Eräs tyylikkäimmistä moderneista satunnaislukugeneraattoreista on MIT:n
Ronald Rivestin kehittämä RC4. RC4:n siemenluvun (tai stream-cipher
terminologialla avaimen) pituus on maksimissaan jopa 2048 bittiä (256 tavua).
RC4:n sisäinen tila koostuu 256:n 8 bittisen luvun taulukosta S ja
kahdesta 8-bittisestä luvusta rc4_i ja rc4_j. Näiden alustus siemenluvun key
perusteella tapahtuu seuraavasti:
Satunnaislukublokin tuottaminen tapahtuu seuraavasti:
Yksi perusongelmista on satunnaissiemenen alustus. Itse käytän
satunnaissiemenen alustukseen seuraavia systeemimuuttujia, jotka
toivottavasti tuovat alustukseen riittävän entropian (hyökkääjän olisi
tunnettava kaikkien näiden arvot tarkasti, jotta satunnaisgeneraattorin
alustuksen jäljittely onnistuisi):
Nähdään, että todennäköisyys 512-bittisen satunnaisen luvun olemiseen
alkuluku on siis noin 1/355. Mitään pelkoa ei alkulukujen loppumisesta
ole, sillä esimerkiksi 512-bittisiä alkulukuja on yli 10150
( luku, jota on kaiketi mahdoton ilmaista sanallisesti ). Koska alkulukujen
jakautumisessa ei ole todettu hyödyllisiä ominaisuuksia, joudutaan
yksinkertaisesti kokeilemaan peräkkäisiä parittomia lukuja alkulukuja
etsittäessä.
Näin pitkien lukujen alkuluvuksi todistamiseen ei ole löydetty riittävän
nopeita algoritmeja jos luvuilla ei ole mitään erikoisominaisuuksia.
Adleman on esittänyt yleistettyyn Riemannin hypoteesiin pohjaavan
menetelmän, jolla voidaan todistaa (olettaen em. hypoteesi pitää
paikkansa) kohtuullisen suuria lukuja. Mersennen lukujen todistamiseen
on olemassa tehokas ns. Lucas-Lehmer-testi, jonka ansiosta suurimmat
tunnetut alkuluvut ovat viimeiset sata vuotta ollet Mersennen lukuja
(eli tyyppiä 2a-1, binäärimuodossa kaikki bitit ykkösiä).
[KNU81, ss. 380, 391-392]
Sen sijaan on olemassa joukko tehokkaita propabilistisiä testausmenetelmiä,
joilla voidaan päätyä tähtitieteellisiin todennäköisyyksiin tietyn yleisen
luvun primaliteetistä tai todistaa luvun olevan ei-alkuluku. Tälläisiä
menetelmiä ovat esimerkiksi:
(funktiosta rsa_keygen)
fii(n) on niiden positiivisten lukujen määrä, jotka eivät ole suurempia
kuin n ja ovat n:n kanssa suhteellisia alkulukuja. Koska kaikki n:ää
pienemmät luvut paitsi p:n ja q:n monikerrat ovat n:n kanssa suhteellisia
alkulukuja, voidaan kirjoittaa:
Lisäksi on valittava julkinen eksponentti e, joka on fii(n):n kanssa
suhteellinen alkuluku eli syt(e, fii(n)) = 1. e on hyvä valita luvuksi,
jossa on mahdollisimman vähän 1-bittejä, jotta modulaarieksponentaatio
sujuu nopeasti. Yleisesti käytettyjä lukuja ovat 3, 17, ja 65537, joissa
kussakin on 2 bittiä päällä. Näillä luvuilla modulaarieksponentaatio
sujuu siis 2 kertolaskulla ja 1, 4 ja 16 neliöinnillä. Itse aloitan
luvusta 17 ja etsin sen jälkeen parillisia e:n arvoja:
Fii:n ja e:n avulla lasketaan salainen eksponentti e, jolle pätee:
Eli koodina:
[DEN82, ss. 104-109] [COR90, ss. 831-837]
[SCH96, ss. 466-470] [SAL90, ss. 125-134]
Salaus:
Purku:
Tällöin M on viesti (käytännössä luku 2 .. n-2) joka halutaan salata, ja
C on vastaava salattu viesti.
Koska modulaarieksponentaatiot ovat toistensa käänteisoperaatioita, voidaan
näitä käyttää digitaalisten allekirjoitusten luomiseen. Olkoon M viesti,
joka halutaan allekirjoittaa, ja S vastaava digitaalinen allekirjoitus.
Kuten aiemminkin (e, n) on julkinen avain ja (d, n) on salainen avain.
Allekirjoituksen luominen:
Allekirjoituksen tarkistus:
Näiden perusoperaatioiden avulla voidaan toteuttaa joukko mitä erilaisimpia
protokollia, mm. erilaisia salaisuuksien vaihdon menetelmiä.
[DEN82, ss. 104-109] [COR90, ss. 831-837]
[SCH96, ss. 466-470] [SAL90, ss. 125-134]
Vastaanottavassa päässä:
Toteutuksessani käytän SHA:ta (Secure Hash Algorithm), joka on yksi harvoista
standardoiduista (Yhdysvallat) turvallisista hash-funktioista. SHA tuottaa
160-bittisen hash-arvon. SHA:sta ei tunneta kryptografisia heikkouksia.
[BRO95] [SCH96, ss. 442-445 (puutteellinen kuvaus)]
Yleisesti esiintyy käsityksiä, että tehokkain menetelmä RSA:n murtamiseksi
olisi ns. raaka voima (brute-force) hyökkäys, jossa kokeillaan yksinkertaisesti
eri avaimia läpi. Tämä ei pidä paikkaansa. Paras tunnettu
tekijöihinjakoalgitmi ( lukukunta seula - algoritmi ) on kompleksisuudeltaan:
Voidaan arvioida, että tällä algoritmilla 1024-bittisen luvun tekijöihin
jakoon kuluu (toteutuksesta riippuen) noin 3 * 107 mips-vuotta
(vuotta tietokoneaikaa kuvitteellisella tietokoneella, joka suorittaa
1 000 000 käskyä sekunnissa. Todelliset tietokoneet ovat nopeudeltaan nykyään
jopa satoja mipsejä ). [SCH96 s. 161, 255-258]
Yksikään nykyään tunnettu tekijöihinjakoalgortmi ei kuulu polynomiseen
kompleksisuusluokkaan (luvun pituuden suhteen), ja epäilläänkin että
tekijöihinjaon ongelma kuuluu pohjimmiltaan exponentiaaliseen joukkoon.
Tätä ei kuitenkaan ole pystytty todistamaan. Alkulukujen generoinnin ja
salauksen algoritmit puolestaan kuuluvat alieksponentiaaliseen
kompleksisuusluokaan, joten tietokoneiden nopeutuessa voidaankin ehkä
aina siirtyä suurempiin avaimen pituuksiin ilman (suhteellisen)
turvallisuuden heikkenemistä.
1.1 Klassiset kerto- ja jakolaskualgoritmit
Suoritetaan esimerkin vuoksi perinteinen kertolasku luvuille 3142 * 2718:
3*2 3*7 3*1 3*8
1*2 1*7 1*1 1*8
4*2 4*7 4*1 4*8
2*2 2*7 2*1 2*8
6 23 18 57 26 34 16
Muistilukujen kantamisen jälkeen:
8 5 3 9 9 5 6
Nähdään, että kahden n-numeroisen luvun kertomiseen tarvitaan n2
kertolaskua (1-numeroisilla luvuilla), n2 yhteenlaskua (2 -
numeroisilla luvuilla) ja lisäksi 2n yhteenlaskua muistilukujen kantamisen
vuoksi. Perinteinen kertolasku on siis kompleksisuudeltaan tyyppiä
O( n2 ).
On vastaavalla tavalla todettavissa, että klassinen jakokulma on myöskin
tyyppiä O( n2 ). [CER25, ss. 1-30] [KNU81, ss. 250-265]
1.2 Karatsuban kertolasku
Klassisissa menetelmissä lukujen pituuden tuplaaminen johti operaatioiden
määrän nelinkertaistumiseen. Ensimmäisen kerran kompleksisuudeltaan tätä
nopeampi menetelmä esitettiin vuonna 1962, kun A. Karatsuba esitti
menetelmän, jossa lukujen pituuden tuplaaminen johti operaatioiden määrän
kolminkertaistumiseen.
v = V1 2n + V0,
V0 < 2n
tällöin U1 on u:n ylemmät n bittiä ja U0 alemmat n
bittiä. Vastaavasti V1 on v:n ylimmät n bittiä ja V0
alemmat n bittiä. Tulo uv voidaankin kirjoittaa seuraavasti:
[KNU81, s. 278]
Tämä voidaan ilmaista C:n syntaksilla seuraavasti (t1, t2 ja t3 väliaikaisia
muuttujia, uv tulos) :
t1 = U0 * V0;
t2 = (U1-U0) * (V0-V1);
t3 = U1 * V1;
uv = ( ( (t3 << n) + t3 + t2 + t1) << n ) + t1;
n = 2
u = 2718 : U1 = 27, U0 = 18
v = 3142 : V1 = 31, V0 = 42
t1 = 18 * 42 = 756
t2 = (42-31) * (27-18) = 11 * 9 = 99
t3 = 27 * 31 = 837
uv = (837 102 + 837 + 99 + 756) 102 + 756
uv = 8 539 956
Karatsuban menetelmä voidaan myöskin yleistää ns. Tom-Cook menetelmiksi,
joilla voidaan päästä vielä edullisempiin kompleksisuusluokkiin. Nämä
menetelmät ovat tosin järkeviä vasta erittäin pitkillä luvuilla.
[KNU81, ss. 279-286]
1.3 Nopeaan Fourier-muunnokseen perustuva menetelmä
Kertolasku voidaan toteuttaa myös ns. nopean fourier-muunnoksen (FFT) avulla.
Kertolaskun n = pq vaiheet tällä menetelmällä ovat seuraavat:
Menetelmä käyttää siis hyväkseen Fourier-muunnettujen funktioiden
konvoluutio-ominaisuutta. Koska itse kompleksilukujen päittäinen
kertolasku on lineaari-nopeuksinen, on koko operaation kompleksisuus
sama kuin FFT:n, eli O ( n log n ).
1.4 Osamäärän ja modulon laskeminen nopeiden kertolaskualgoritmien
avulla
RSA-järjestelmä perustuu täysin modulaariaritmetiikkaan, joten pelkällä
kertolaskulla ei päästä pitkälle. Tarvitaan myöskin nopea menetelmä
jakolaskun (ja näin ollen myös jakojäänöksen, joka on mainittu modulo)
saamiseksi.
Osamäärä saadaan laskettua käänteisluvusta yhdellä ylimääräisellä
kertolaskulla, ja tästä saadaan edelleen jakojäännös yhdellä kertolaskulla
ja yhdellä vähennyslaskulla.
Tällä menetelmällä on (hyvällä alkuarvauksella) kvadranttinen konvergenssi,
joten käänteisluku ja sitä myöden myös jako- ja jakojäännös ovat tyyppiä
O( n log n ). Tämä kompleksisuusluokka on kuitenkin harhaanjohtava, sillä
menetelmä ohittaa nopeudeltaan klassisen neliöllisen jakolaskun
vasta kymmenien tuhansien desimaalien lukuja laskettaessa.
[PRE92, ss. 918-921]
2. RSA:n tarvitsemia algoritmeja
Käsittelen RSA:n toteutuksessa ilmenevien osaongelmien ratkaisuun
tarvittavien algoritmien valintaa ja ohjelmointia.
2.1 Modulaarieksponentaatio
RSA:n purkuoperaatio on seuraava:
Modulaarieksponentaatio voidaan toteuttaa nopeammin seuraavasti:
// b
// laskee a mod n
kbn& modexp (kbn a, kbn b, kbn n)
{
static kbn r;
int i, explen;
r = 1;
explen = b.log2();
for(i=0; i<=explen; i++)
{
if ( b.testbit(i) )
r = (r*a) % n;
a = a.sqr() % n;
}
return r;
}
2.2 Suurimman yhteisen tekijän ja multiplikatiivisten käänteislukujen
laskeminen
Suurimman yhteisen tekijän laskeminen on triviaali operaatio, joka hoituu
nopeasti Euklideen algoritmilla. Seuraava aliohjelma laskee annettujen
lukujen a ja b suurimman yhteisen tekijän:
// laskee syt(a, b)
kbn gcd(kbn a, kbn b)
{
static kbn g0, g1, g2, zero;
g0 = a;
g1 = b;
zero = 0;
while ( g1 != zero )
{
g2 = g0 % g1;
g0 = g1;
g1 = g2;
}
return g0;
}
// laskee b:n joka toteuttaa ab mod n = 1 (jos mahdollista)
kbn& inverse (kbn a, kbn n)
{
// nämä on pidettävä staattisina, sillä muuten ne söisivät koko
// pinon ja kaataisivat ohjelman
static kbn u1, u3, v1, v3, t1, t3, q, w, zero;
int sign;
// alustetaan nollavakio ja kiertomuuttajat
zero = 0;
// alustetaan muuttujat
u1 = 1;
u3 = a;
v1 = 0;
v3 = n;
// laajennettu euklideen algoritmi
// (muunnettu negatiivisten lukujen välttämiseksi)
sign = 0;
while( v3 != zero )
{
q = u3 / v3;
t3 = u3 % v3;
w = q * v1;
t1 = u1 + w;
u1 = v1;
v1 = t1;
u3 = v3;
v3 = t3;
sign = !sign;
}
// käännetään merkin mukaisesti
if (sign)
u1 = n - u1;
return u1;
}
2.3 Satunnaisluvut
RSA-avainten generoinnissa tarvitaan hyvää satunnaislukugeneraattoria.
Satunnaislukujen tulee toteuttaa ainakin seuraavat ehdot:
C:n standardikirjastoissa olevat satunnaislukugeneraattorit eivät täytä
kaikkia mainittuja ominaisuuksia, varsinkaan siemenluvun pituuden osalta.
// alustetaan RC4 annetulla avaimella
void rc4::init (unsigned char *key, int keylen)
{
int i, j;
unsigned char t;
for(i=0; i<0x100; i++)
S[i] = i;
for(i=0, j=0; i<0x100; i++)
{
j = (j + S[i] + key[i % keylen]) & 0xff;
t = S[i];
S[i] = S[j];
S[j] = t;
}
rc4_i = 0;
rc4_j = 0;
}
// tuottaa satunnaistavuja l kpl muistiosoitteeseen pt
void rc4::rand(unsigned char *pt, size_t l)
{
register unsigned char Si, Sj;
register unsigned long i;
for(i=0; i < l; i++)
{
rc4_i = (rc4_i+1) & 0xff;
Si = S[rc4_i];
rc4_j = (rc4_j+Si) & 0xff;
Sj = S[rc4_j];
S[rc4_i] = Sj;
S[rc4_j] = Si;
pt[i] = S[(Si + Sj) & 0xff];
}
}
Havaitaan, että operaatio on erittäin yksinkertainen ja nopea, mutta
nykyisen näkemyksen mukaan erittäin turvallinen. Hyvää
satunnaislukugeneraattoria voidaan käyttää myös tiedon salaukseen: tiedon
salaamiseksi salattavan muistilohkon ja satunnaisluvun välille suoritetaan
xor-operaatio. Xor on oma käänteisoperaationsa; salatun lohkon
purkaminen tapahtuu samalla tavalla kuin salaaminen. Samaa avainta
ei luonnollisestikaan saa käyttää useamman kuin yhden viestin
salaamiseen, sillä kahden samalla avaimella salatun viestin xor on
itse satunnaisluku.Tälläistä konstuktiota kutsutaan virta (stream) -
tyyppiseksi kryptoalgoritmiksi. [SCH96, ss. 397-398]
Kaikki nämä välitetään mahdollisimman suurella tarkkuudella siemeneksi
satunnaislukugeneraattorille. Mikäli kone on monen käyttäjän kone (unix)
ja hyökkääjä pystyy tarkkailemaan tai manipuloimaan suurta osaa näistä
muuttujista on tietyn tyyppinen rajattu brute-force hyökkäys mahdollinen.
Tämän riskin minimoimiseksi onkin avaimet hyvä luoda erillisessä,
turvallisessa koneessa, mieluiten ei aivan heti koneen käynnistämisen
jälkeen (jotta em. parametrit olisivat hieman muuttuneet oletusarvoista).
Jotkut järjestelmät, kuten PGP, pyytävät käyttäjää syöttämään satunnaisia
merkkejä näppäimistöltä, mutta myös tämä menetelmä on altis vastaavanlaiselle
tarkkailulle.
// konstruktori; alustetaan RC4 satunnaisella avaimella
rc4::rc4()
{
static struct {
unsigned long counter;
clock_t clock;
struct timeval tv;
struct timezone tz;
struct sysinfo info;
char *memadr;
} randseed;
// alustetaan satunnaissiemenluku
randseed.counter++;
gettimeofday(&randseed.tv, &randseed.tz);
randseed.clock = clock();
sysinfo( &randseed.info );
randseed.memadr = new char[123];
delete randseed.memadr;
init( (unsigned char *) &randseed, sizeof(randseed) );
// nollataan mahdolliset jäänteet muistista
bzero((void *) &randseed, sizeof(randseed) );
}
2.4 Suurten satunnaisten alkulukujen luominen
Todennäköisyys P(n), että annettu luku n on alkuluku on alkulukuteoreeman
mukaan noin 1 / ln n. Tämä voidaan suhteuttaa myös luvun n pituuteen
(bitteinä) seuraavasti:
Esitän seuraavassa käyttämäni (Knuth-variantin) Rabin-Miller - menetelmästä:
// testaa, onko annettu luku (todennäköisesti) alkuluku
int prob_primetest (kbn *n)
{
static kbn q, y, one;
int j, k;
one = 1;
j = 0;
// k
// (p0) hajoitetaan n luvuiksi (k, q) s.e. 2 q + 1 = n, q mod 2 = 1
for(k=1; !(n->testbit(k)); k++);
q = *n >> k;
y = rc4rand( n ); // y satunnaisluku välillä 0..n-1
// q
// (p2) j <- 0, y <- y mod n
y = modexp( y, q, *n );
// jos y = 1, luku saattaa olla alkuluku
if ( y == one )
return 1;
do
{
// jos y = n-1, luku saattaa olla alkuluku
if ( y == (*n-one) )
return 1;
// 2
// asetetaan y <- y mod n
y = y.sqr() % *n;
// y = 1, luku EI ole alkuluku
if ( y == one )
return 0;
j++;
} while ( j < k );
return 0;
}
Rabin-Miller -menetelmä ei siis ikinä erehdy väittämään alkulukua
ei-alkuluvuksi, mutta todennäköisyydellä P=0.25 se saattaa väittää
ei-alkulukua alkuluvuksi. Testi erehtyminen ei ole kuitenkaan riippuvainen
luvusta n, vaan satunnaissiemenestä y. Tämän takia on mahdollista testata
alkulukukandinaatti uudestaan n kertaa ja näin päästä varmuuteen
P=0.25n. Seuraavaksi esitettävä aliohjelma satunnaisten
alkulukujen tuottamiseen tekee 25 testiä, ja näin ollen
sen erehtymistodennäköisyys on
astronomisen pieni: P < 8.9 * 10-16.
// tuottaa satunnaisen alkuluvun, jossa bits bittiä
kbn& random_prime (int bits)
{
static kbn n;
int i, primefound;
// luodaan aloituspiste, jonka 2 ylintä bittiä ja alin on päällä
// (luku on pariton)
n = 0;
n.setbit( bits-1, 1 );
n = rc4rand( &n );
n.setbit( bits-1, 1 );
n.setbit( bits-2, 1 );
n.setbit( 0, 1 );
do {
++n; ++n; // siirrytään seuraavaan parittomaan lukuun
primefound = 1;
for(i=0; i<25 && primefound; i++) // testataan 25 kertaa
{
primefound = prob_primetest(&n);
// PGP-mäinen edistymisindikaattori
cout << (primefound ? '*' : '.') << flush;
}
} while (!primefound);
cout << " prime!" << endl;
// jos 25 testin mukaan n on alkuluku, palautetaan se
return n;
}
3. RSA-järjestelmän toiminta
RSA-Järjestelmä on ns. julkisen avaimen (public key) järjestelmä.
Tämä tarkoittaa sitä, että on olemassa kaksi toisistaan riippumatonta
avainta (e, n) ja (d, n). Salaista avainta käytetään viestin purkamiseen
tai digitaalisten allekirjoitusten luomiseen. Julkisella avaimella voidaan
salata viesti tai tarkistaa allekirjoitus.
3.1 Julkisen ja salaisen avaimen luominen
Avainten generointiin tarvitaan kaksi satunnaisesti valittua suurta
(vaikkapa 512-bittistä) alkulukua p ja q.
p = random_prime( bits / 2 );
q = random_prime( bits / 2 );
// tuotetaan julkinen eksponentti e >= 17, jolle syt(e, phi(n)) = 1
e = 17;
while ( gcd( e, phi ) != one )
{ ++e; ++e; }
// tuotetaan salainen eksponentti d, jolle ed mod phi = 1
d = inverse( e, phi );
3.2 RSA:n perustoiminnot
Salaus- ja purkuoperaatiot ovat periaatteessa seuraavat:
3.3 Hybridi-RSA
Käytännössä edellä esitettyssä yksinkertaistetussa mallissa on lukuisia
ongelmia:
Yleinen ratkaisu näihin ongelmiin on generoida ja salata RSA:lla
kertakäyttöinen symmetrinen avain. Itse viestin salaamiseen käytetään sitten
nopeaa symmetristä algoritmia tällä avaimella. Lisäksi alkuperäisestä
viestistä lasketaan "tarkistussumma" (joka luonnollisestikaan ei ole summa,
vaan kryptografisesti varma hash-funktio) joka myöskin salataan RSA:lla
samalla kertaa kuin satunnainen avainkin.
Vastaavasti digitaalisia allekirjoituksia luotaessa salaisella avaimella
kryptataan ainoastaa viestistä laskettu turvallinen hash.
[SCH96, ss. 32-34]
3.4 RSA:n turvallisuus
RSA on erittäin levinnyt ja laajalti luotettu algoritmi. RSA-järjestelmän
murtaminen ei ole vaikeampaa kuin seuraavien osaprobleemien ratkaisu:
On todistettavissa, että mikä tahansa algoritmi jolla voidaan löytää
luvut fii(n) tai d on muunnettavissa n:n tekijöihinjako-algoritmiksi.
[SAL91, ss. 143-146]