Podręcznik
Strona: | SEZAM - System Edukacyjnych Zasobów Akademickich i Multimedialnych |
Kurs: | Kody i szyfry |
Książka: | Podręcznik |
Wydrukowane przez użytkownika: | Gość |
Data: | piątek, 4 kwietnia 2025, 20:54 |
Spis treści
- 1. Pojęcia podstawowe
- 2. Kody numeryczne i arytmetyka cyfrowa
- 2.1. Kody numeryczne
- 2.2. Naturalne kody wagowe dla liczb naturalnych i zera
- 2.3. Naturalne kody wagowe o stałej długości słowa kodowego
- 2.4. Konwersja naturalnych zapisów wagowych
- 2.5. Uzupełnienie do W-1
- 2.6. Uzupełnienie do W
- 2.7. Zapis moduł znak
- 2.8. Zapis uzupełnień do W i zapis uzupełnień do 2
- 2.9. Zapis uzupełnień do W–1 i zapis uzupełnień do 1
- 2.10. Zapis stałoprzecinkowy
- 2.11. Zapis zmiennoprzecinkowy
- 2.12. Kody BCD
- 2.13. Kody Graya
- 2.14. Kody m z n
- 2.15. Zapisy resztowe
- 2.16. Kody wagowe ze zmienną wagą
- 3. Kody alfanumeryczne
- 4. Kody wykrywające i korygujące błędy
- 5. Szyfry i kryptografia
- 5.1. Główne cele kryptografii
- 5.2. System kryptograficzny
- 5.3. Podział szyfrów
- 5.4. Bloki, blokowa funkcja szyfrująca i szyfry blokowe
- 5.5. Funkcja jednokierunkowa
- 5.6. Szyfry przestawieniowe czyli szyfry permutacyjne
- 5.7. Szyfry podstawieniowe
- 5.8. Szyfr produktowy lub złożeniowy
- 5.9. Szyfry strumieniowe
- 5.10. Szyfry idealne
- 5.11. Algorytm szyfrowania szyfrem RSA
- 5.12. Funkcje skrótu
- 5.13. Uwagi końcowe
- 6. Kompresja informacji
1. Pojęcia podstawowe
Pojęcia podstawowe
1.1. Alfabet
Alfabet to dowolny niepusty zbiór skończony V (niekiedy dodajemy zbiór symboli lub zbiór liter). Elementy alfabetu nazywamy literami lub symbolami. Alfabet tak jak zbiór oznaczamy najczęściej dużymi literami alfabetu łacińskiego np. V, A itd.
V = {a, b, c, ..., x, y, z, A, B, C, 0, 1, 2, ..., 9}
Warto zwrócić uwagę, na fakt że to co nazywamy symbolem lub literą jest kwestią umowy. W systemach cyfrowych symbole często utożsamiamy z pewnymi łatwo rozróżnialnymi sytuacjami fizycznymi, stanami układu fizycznego np. stanami układu elektronicznego zwanego przerzutnikiem bistabilnym (układ przerzutnika bistabilnego ma 2 łatwo rozróżnialne stabilne stany).
Dla nas symbolem będzie np. wartość napięcia w określonym węźle układu cyfrowego (H - wyższe napięcie np. 5 V lub L - niższe napięcie np. 0 V) a alfabetem zbiór V = {L, H}. Typowe wartości napięcia H i L w systemach cyfrowych to H = 5 V i L = 0 V (np. tak jest w tzw. układach TTL i CMOS o małej skali scalenia) ale wewnątrz układu scalonego o dużej skali scalenia stosuje się z reguły napięcia niższe np. H = 3,3 V, L = 0 V. Oczywiście rozsądek techniczny każe uznawać za napięcie H wszystkie wartości napięć z pewnego niewielkiego przedziału IH zawierającego nominalną wartość napięcia H (czyli napięcia zbliżone do H) i podobnie za napięcie L przyjmujemy wszystkie napięcia z niewielkiego przedziału IL zawierającego nominalną wartość napięcia L (czyli napięcia zbliżone do L). Musimy przy tym spełnić oczywiście warunek by IH ∩ LH = 0.
Symbolem może być również przemagnesowanie określonego miejsca nośnika magnetycznego lub brak takiego przemagnesowania. Uzyskujemy w tej sytuacji 2 symbole, które łatwo możemy zapisywać na powierzchni magnetycznej za pomocą tzw. głowicy magnetycznej.
Symbolem może być też drobne wgłębienie na powierzchni CD-ROM-u (tzw. pit) rozpraszający światło promienia lasera głowicy odczytującej lub brak pit-u na powierzchni CD-ROM-u powodujący odbicie promienia laserowego. Odbity promień lasera trafia do detektora (diody detekcyjnej) wywołując impuls elektryczny odpowiadający 1.
Symbolem może być również stan przełącznika mechanicznego o 2 (czy ogólnie n pozycjach stabilnych).
Cechą charakterystyczną symboli czyli liter wchodzących w skład alfabetu jest łatwość ich zapisywania (czyli zapamiętywania) i odczytywania bez pomyłek. Stąd zresztą popularność w praktyce alfabetu binarnego. Układ fizyczny mający tylko dwa dobrze wyodrębnione stany utożsamiane odpowiednio z 0 lub 1 okazuje się w praktyce pewniejszy, bardziej niezawodny.
Reasumując, powyżej opisaliśmy 4 najczęściej stosowane sposoby reprezentacji w systemach cyfrowych symboli 0 i 1 :
- sposób elektryczny wykorzystujący 2 poziomy napięcia L i H w określonym węźle układu elektronicznego (jest to tzw. kodowanie potencjałowe „0” i „1”)
- sposób magnetyczny wykorzystujący przemagnesowanie nośnika magnetycznego
- sposób optyczny wykorzystujący zmianę własności optycznych powierzchni odbijającej światło laserowe
- sposób mechaniczny wykorzystujący stan przełącznika mechanicznego.
1.2. Słowo
Słowo nad alfabetem V (ang. word) to dowolny ciąg skończony o wartościach w zbiorze V. Czasami słowo nad ustalonym alfabetem nazywamy tekstem lub napisem (a w językach programowania również łańcuchem lub stringiem). Słowo a1, a2, ..., an zapisujemy z reguły bez przecinków tzn. jako: a1a2...an. Upraszcza to notację nie prowadząc na ogół do nieporozumień.
Ilość wyrazów ciągu a1a2...an nazywamy długością słowa a1a2...an np. słowo abccd nad alfabetem V = { a, b, c, d } ma długość 5 a słowo ala długość 3.
Jeśli V = { 0, 1 } to słowa nad tym alfabetem nazywamy słowami binarnymi. Słowo binarne o długości 8 bitów np. 10101010 nazywamy bajtem (ang. byte).
Zamiast słowa bajt (lub byte) używamy skrótu B. Właśnie w bajtach podajemy najczęściej pojemność różnego typu pamięci. Każdy adept inżynierii komputerowej wie, że typowa pojemność tzw. pamięci operacyjnej komputera klasy PC jest rzędu jednego gigabyte'a (1 GB czyli jeden gigabyte to 109 B), a typowa pojemność napędu twardego dysku jest rzędu 100 GB . Pojemności tzw. macierzy dyskowych czy streamerów mierzymy w terabajtach (1 TB czyli jeden terabyte to 1012 B).
Reasumując: typowe jednostki, w których mierzymy pojemność pamięci to
- B (bajt, 1 B)
- kB (kilobajt 103 B)
- MB (megabajt 106 B)
- GB (gigabajt 109 B)
- TB (terabajt 1012 B)
Pewnego komentarza wymaga jeszcze pojęcie bitu. Bit to symbol (litera) 0 lub 1. Mówimy, też, że bit to cyfra dwójkowa. Często jednak pojęcie bitu wiążemy z ustalonym słowem binarnym np. a1a2...an. Możemy wówczas mówić o k-tym bicie słowa binarnego, gdzie k ≤ n, którym z definicji jest k-ty wyraz ciągu a1a2...an czyli ak. W innym jeszcze znaczeniu słowo "bit" oznacza jednostkę ilości informacji definiowaną w teorii informacji.
1.3. Język
Zbiór wszystkich słów nad ustalonym alfabetem V oznaczamy symbolem V* a dowolny niepusty podzbiór L tego zbioru nazywamy językiem. Do zbioru V* zaliczamy również słowo puste (oznaczane na ogół symbolem ε). Słowo puste ma długość 0. Zbiór V* jest oczywiście nieskończony ale przeliczalny, mamy bowiem
V* = {ε} ∪ V ∪ V2 ∪ ... ∪ Vn ∪ ...
W zbiorze V* definiujemy działanie dwuargumentowe V* × V* → V* tzw. konkatenację (ang. concatenation). Jeśli α, β ∈ V* i α = a1a2...an β = b1b2...bn to z definicji mamy
a b = a1a2...an
b1b2...bn
a1a2...anb1b2...bn
Jak widać konkatenacja jest zestawianiem słów np. ala konkatenowane z makota daje alamakota, podobnie kot z let daje kotlet.
Jak łatwo sprawdzić działanie konkatenacji jest łączne, tzn. dla każdego α, β, γ ∈ V* mamy (α β)
γ = α
(β
γ). Mamy ponadto dla każdego α ∈ V*; α
ε = ε
α = α zatem słowo puste ε jest jedynką działania
: V* × V* → V*. Inaczej mówiąc konkatenacja ze słowem pustym dowolnego słowa α ∈ V* nie zmienia tego słowa. Zbiór ∈ V* z działaniem konkatenacji
: V* × V* → V jest więc monoidem (czyli półgrupą z jednością).
Widać natychmiast, że konkatenacja na ogół nie jest działaniem przemiennym w V*, ale zawsze jest działaniem łącznym, tzn. na ogół x y ≠ y
x dla x, y ∈ V*, ale zawsze tzn. dla każdego x, y, z ∈ V*, mamy:
Język to dowolny podzbiór zbioru V*. Podzbiór ten można definiować na wiele sposobów np. za pomocą wyrażeń regularnych, notacją Backusa-Naura, gramatyką, automatem skończonym itd.. Wszystkie te sposoby poznamy w dalszym ciągu. Istnieje również wiele typów języków m.in. języki regularne, języki bezkontekstowe itd.
Złożeniem dwóch języków K i L, gdzie K, L ⊂ V* nazywamy język
KL { x, y ∈ <em>V<sup>*</sup></em>;<em> </em>x, <em>y</em> ∈ <em>K</em> oraz <em>y</em> ∈ <em>L</em>}
Potęga języka. Potęgę L* języka definiujemy indukcyjnie następująco
L0 {ε}, L1
L, L2
LL, Ln+1
LLn, ...
Potęga liter. Potęgę liter definiujemy indukcyjnie następująco. Niech a będzie dowolną ustaloną literą a ∈ V* wówczas a0 {ε}, a1
a, a2
aa, ..., an+1
aan, ...
Potęga słów. Potęgę słów definiujemy indukcyjnie następująco. Niech w będzie dowolnym ustalonym słowem, w ∈ V*, wówczas w0 {ε}, w1
w, w2
ww, ..., wn+1
wwn, ...
Domknięcie języka (gwiazdeczka Kleen'a) L*. Niech V będzie ustalonym alfabetem a L językiem L ⊂ V*. Domknięciem języka L nazywamy zbiór L* ⊂ V*, gdzie
1.4. Metryka Hamminga
Niech będzie dowolnym ustalonym alfabetem. Wprowadźmy w tym alfabecie metrykę dyskretną ρd: V × V → R+. Z twierdzenia 1 z Dodatku (p. Metryka Hamminga) wynika, że funkcja ρH: Vn × Vn → R+ zdefiniowana wzorem: dla każdego y, x ∈ Vn
jest metryką. Nazywamy ją metryką lub odległością Hamminga w przestrzeni Vn (Vn jest to przestrzeń wszystkich słów o długości n nad alfabetem V). Najczęściej rozważamy metrykę Hamminga dla Vn = {0, 1}. Oczywiście ρH: Vn × Vn → N ∪ {0} czyli ρH jest funkcją o wartościach w zbiorze liczb całkowitych nieujemnych.

Waga słowa binarnego. Wagą słowa binarnego z przestrzeni {0,1}n (lub ogólniej z {0,1}*) nazywamy liczbą jedynek w tym słowie. W ten sposób definiujemy na {0,1}* funkcję w: {0,1}* → N ∪ {0}. Oczywiście dla a ∈ {0,1}n mamy w(a) = ρH (0, a) gdzie ρH jest metryką Hamminga. Można wyrazić metrykę Hamminga ρH w {0,1}n za pomocą funkcji wagi w.
Niech x, y ∈ {0,1}n, x = (x1, x2, ..., xn), y = (y1, y2, ..., yn), wówczas mamy
gdzie xi - 2 yi oznacza różnicę modulo 2, x − y oznacza różnicę modulo 2 po współrzędnych a x + y sumę modulo 2 po współrzędnych.
Waga słowa 11111111 jest równa 8, tzn. w(11111111)=8, w(00)=0, w(001)=1.
1.5. Kody
Niech V1 oznacza dowolny ustalony zbiór niepusty a V2 dowolny alfabet. Zbiór V1 będziemy nazywać zbiorem obiektów kodowanych. Istnieje kilka definicji pojęcia kodu. Najogólniejsza definicja jest taka. Kod to relacja dwuargumentowa k ⊆ V1 × V*1 spełniająca warunek: dla każdego x1 ∈ V1 czyli dla każdego elementu ze zbioru obiektów kodowanych istnieje takie x2 ∈ V*2 , że (x1, x2) ∈ k. Elementy przeciwdziedziny relacji k czyli elementy zbioru {<em>x</em><sub>2</sub> ∈ <em>V<sup>*</sup></em><sub>2</sub>; (<em>x</em><sub>1 </sub><em>,x</em><sub>2</sub>) ∈ <em>k</em>} nazywamy w tej sytuacji słowami kodowymi. Jeśli V2 = {0, 1} to kod nazywamy kodem binarnym lub kodem dwójkowym. W szczególności dowolne odwzorowanie : V1→V*2 jest oczywiście kodem.
Druga definicja kodu nieco bardziej "wymagająca" jest taka: kod to dowolne odwzorowanie : V1→V*2 żądamy przy tym z reguły by funkcja
była różnowartościowa i nie przyjmowała wartości ε i tak najczęściej rozumiane jest pojęcie kodu. W dalszym ciągu pod pojęciem kodu będziemy rozumieć tę najbardziej restryktywną definicję. Sam proces obliczania czy ustalania wartości funkcji
dla elementów zbioru V nazywamy kodowaniem. Wartość
(x1) ∈ V*2 jest słowem kodowym, kodującym element x1 ∈ V1.
Jeśli istnieje takie n ∈ N, że dla każdego x ∈ V1 słowo kodowe ma stałą długość n to kod nazywamy kodem o stałej długości (ang fixed length code) lub dokładniej kodem o stałej długości n słowa kodowego. Oczywiście mamy wówczas : V1→Vn2. Najczęściej w systemach cyfrowych mamy do czynienia z takimi właśnie kodami. Przykładem takiego kodu jest znany 8 bitowy kod alfanumeryczny ASCII.
Kod redundancyjny (lub nadmiarowy). Niech : V1→V*2 będzie kodem. Oznaczmy przez zbiór obiektów kodowanych słowami o długości n ∈ N tzn. niech . Jeśli istnieje takie 1{;()nnAxVfxV=∈∈2 } nN∈, że V czyli zbiór
fA jest właściwym podzbiorem zbioru V
to kod nazywamy redundancyjnym lub nadmiarowym. Najkrócej: kodem redundancyjnym nazywamy dowolny kod 2n12:fVV∗→ taki, że funkcja różnowartościowa f nie jest „na”.
Istotą rzeczy jest tu istnienie niepustego słowa w zbiorze V2∗, które nie koduje żadnego obiektu. W powyższej definicji nie interesuje nas czy zbiór obiektów kodowanych jest skończony czy nieskończony.
Kod redundancyjny (lub nadmiarowy) o stałej długości słowa kodowego. Jeśli rozważamy kod o stałej długości (implikuje to skończoność zbioru obiektów kodowanych ) i odwzorowanie f nie jest "na" (czyli jest właściwym podzbiorem zbioru ) to kod taki nazywamy kodem redundancyjnym lub nadmiarowym o stałej długości słowa kodowego. W takim kodzie nie wykorzystujemy wszystkich możliwych słów kodowych o długości n do kodowania obiektów kodowanych. nVVf21:→1V)(1VfnV2
Kody redundancyjne umożliwiają
- 1.sprawdzenie czy dane słowo jest poprawnym słowem kodowym (wykrycie błędu)
- 2. ewentualną korekcję błędu jeśli taki błąd wystąpił na którejś pozycji słowa
Kody redundancyjne znajdują więc zastosowanie wszędzie tam gdzie zależy nam na wykrywaniu lub korekcji błędów występujących w słowie kodowym. Błędy takie pojawiają się czasami podczas transmisji lub przechowywania danych a wynikają z nieuniknionych w realnym środowisku fizycznym szumów i zakłóceń a również co może się zdarzyć z uszkodzenia układów elektronicznych przetwarzających informację. Każdy kod wykrywający błędy i każdy kod korygujący błędy jest kodem nadmiarowym.
Zauważmy jeszcze, że istota kodu polega na wiązaniu z obiektem kodowanym pewnego słowa nad ustalonym alfabetem V. Możemy sobie wyobrażać, że to słowo w pewien sposób opisuje wybrany obiekt, daje jego syntetyczną charakterystykę. Jednak ilość wszystkich słów nad dowolnym alfabetem V jest przeliczalna. Zatem zakładając, że kod jest funkcją różnowartościową dostajemy, że zbiór obiektów kodowanych musi być przeliczalny. Nie ma więc np. kodu kodującego wszystkie liczby rzeczywiste (bo zbiór liczb rzeczywistych nie jest przeliczalny) choć jak pokażemy w dalszym ciągu łatwo zdefiniować kod dla zbioru wszystkich liczb wymiernych. 22
1.6. Podział kodów
Jeśli zbiorem obiektów kodowanych V1 jest zbiór znaków to kod nazywamy kodem alfanumerycznym. Jeśli zbiorem obiektów kodowanych V1 są liczby to kod nazywamy kodem numerycznym. Kod numeryczny to inaczej kod liczbowy, kod arytmetyczny, zapis liczbowy, notacja liczbowa, lub system zapisu liczb. 11
Podział kodów:
- Kody α- numeryczne o stałej długości (np. ASCII, Unicode)
- Kody α- numeryczne o zmiennej długości (np. kod Huffmana)
- Kody liczbowe o stałej długości (np. kod NKB, zapis uzupełnień do 2)
- Kody liczbowe o zmiennej długości (np. zwykły zapis dziesiętny)
- Kody wykrywające i korygujące błędy (np. kod Hamminga, kod Reeda-Solomona)
- Szyfry (ściślej są to indeksowane kluczem rodziny kodów) (np. szyfr RSA, szyfr AES)
- Inne kody (np. kody paskowe, kody służące do zapisywania informacji na twardych dyskach, kody używane w transmisji danych itd. ).d
2. Kody numeryczne i arytmetyka cyfrowa
Kody numeryczne i arytmetyka cyfrowa
2.1. Kody numeryczne
Kod numeryczny (lub inaczej kod liczbowy, kod arytmetyczny, zapis liczbowy, notacja liczbowa, lub system zapisu liczb). Jeśli zbiorem obiektów kodowanych V1 są liczby to kod nazywamy kodem numerycznym Typowe alfabety V2 dla kodu numerycznego to V2 = {0,1}, V2 = {1,2,3,4,5,6,7}, V2 = {0,1,2,3,...,9}, V2 = {0,1,...,9, A, B, C, D, E, F} a w kryptografii V2 = {a, b, c, ..., z}.
W tym momencie mogłoby się zrodzić w umyśle Uważnego Czytelnika straszliwe podejrzenie, że na świecie mamy tylko 2 kategorie kodów kody alfanumeryczne i numeryczne. Na szczęście nie grozi nam nuda i mamy jeszcze wiele, wiele różnych rodzajów kodów np. kody paskowe (inaczej kreskowe). W kodach paskowych (są różne rodzaje tych kodów ale wszystkie są kodami o stałej długości słowa kodowego) obiektami kodowanymi są np. towary znajdujące się w handlu a literami alfabetu, w którym zapisujemy słowo kodowe są paski o różnej grubości (grubość paska jest ściśle związana z jedną z cyfr 0,1,2,3,4,5,6,7,8,9 . Przykładem takiego kodu jest np. kod EAN 13 .
O wartości i praktycznej przydatności kodu numerycznego czyli zapisu liczbowego decyduje łatwość wykonywania pięciu elementarnych działań na reprezentowanych danym zapisem liczbach. Konkretnie chodzi o działanie wyznaczania liczby przeciwnej do danej liczby (działanie jednoargumentowe), dodawania, odejmowania, mnożenia i dzielenia liczb (działania dwuargumentowe). Bardzo istotna jest również łatwość porównywania liczb w danym zapisie. Pewnego komentarza wymaga tu sformułowanie "łatwość wykonywania", które oznacza łatwość realizacji algorytmu przez człowieka lub przez hardware albo software komputera.
2.2. Naturalne kody wagowe dla liczb naturalnych i zera
Podstawowy fakt z arytmetyki umożliwiający skonstruowanie dużej klasy kodów liczbowych tzw. naturalnych kodów wagowych jest następujący:

1) dla każdego i = 0,1,...,k, ai ∈ {0,1...<em>m<sub>i </sub></em><sub>-1</sub>}, ak ≠0
2) m = akmk-1mk-2 ⋅ ... ⋅ m0 + ak-1mk-2mk-3 ⋅ ...⋅ m0 + ...a2m1m0 + a1m0 + a0
Dowód: Prosty dowód pozostawiamy Czytelnikowi (por. też D.E.Knuth [5])
Wniosek: Dla dowolnej liczby m ∈ N i ustalonej liczby W ∈ {2,3} istnieje dokładnie jeden ciąg skończony ak,ak-1...a1a0 taki, że
1) ak,ak-1...a1a0 ∈ {0,1...<em>W</em>-1}, ak ≠ {0}
2) m= akWk . ak-1Wk-1 + a1W + a0
Liczba W nosi nazwę wagi lub podstawy zapisu wagowego (ang. radix). W praktyce na oznaczenie liczb {0,1...<i>W </i>- 1} używamy ustalonych symboli z pewnego równolicznego z {0,1...<i>W </i>- 1} alfabetu V, nazywając je cyframi. Wygodnie jest niekiedy te symbole utożsamiać z liczbą ze zbioru {0,1...<i>W </i>- 1} w tym sensie, że raz traktujemy je jak symbole, raz jak liczby. Możemy też wręcz przyjąć, że V = {0,1...<i>W </i>- 1}.
Odwzorowanie K: N ∪ {0} ∋ m → K(m) = akak-1...a1a0 ∈ V* określone na zbiorze N ∪ {0} i zdefiniowane w powyższym wniosku nazywamy kodem wagowym z wagą W ≥ 2 przy czym przyjmujemy K(0) = 0.
Typowe najczęściej używane wagi to 2, 8, 10, 16, 26 (26 to liczba małych liter w alfabecie angielskim). Wag W=26 i W=256 używamy w kryptografii do utożsamienia tekstu z liczbą ze zbioru N ∪ {0}.
Zapisy wagowe dla wag 2, 8, 10, 16, noszą następujące nazwy:
Zapisy wagowe dla wag 2, 8, 10, 16, noszą następujące nazwy:
W=2, kod NKB - naturalny kod binarny lub zapis dwójkowy naturalny (lub zwykły zapis dwójkowy), mamy dwie cyfry: 0, 1
W=8, zapis oktalny, mamy 8 cyfr 0, 1, 2, 3, 4, 5, 6, 7
W=10, zapis dziesiętny, mamy 10 cyfr 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
W=16, zapis hexadecymalny (lub szesnastkowy) , mamy 16 cyfr 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F (stosujemy dodatkowe cyfry A, B, C, D, E, F)
Jeśli mamy wątpliwości, jakiego zapisu używamy, to po zapisie liczby stosujemy znak spoza alfabetu w jakim zapisujemy liczbę, np.: B – dla zapisu binarnego, O - dla zapisu oktalnego, D - dla zapisu dziesiętnego i H - dla zapisu hexadecymalnego. Czasami z kontekstu wynika jakim zapisem operujemy więc dodatkowe wyjaśnienia nie są potrzebne. Zapis dziesiętny będziemy w dalszym ciągu traktować zwyczajowo jako wyróżniony i z reguły literę D będziemy pomijać.
Wygodnie jest też ciąg K(m) = akak-1...a1a0 ∈ V*utożsamiać jeśli trzeba z liczbą m tak jak to zwyczajowo czynimy dla zapisu z wagą W=10 czyli dla dobrze znanego zapisu dziesiętnego.
0→0
1→1
1→10
3→11
4→100
5→101
6→110
7→111
Warto podkreślić, że zbiorem obiektów kodowanych jest dla rozważanych w tym punkcie kodów zbiór liczb naturalnych z zerem tzn. N ∪ {0} a więc rozważane kody są kodami numerycznymi. Jednocześnie słowa kodowe mogą być dowolnie długie. Nie są to więc kody ze stałą długością słowa kodowego. Dla ustalonej liczby a ∈ N ∪ {0} i ustalonej wagi W ∈ {2,3...} łatwo można obliczyć długość słowa kodowego l(a) reprezentującego liczbę a. Jeśli a = 0, to l(a) = 1, jeśli a ≠ to
gdzie R → Z jest tzw. funkcją podłogi (lub jak mówimy krótko podłogą). Jeśli x ∈ R to
jest największą liczbą całkowitą nie większą od
.
gdzie a oznacza słowo binarne reprezentujące liczbę.

Algorytm dodawania w naturalnym zapisie wagowym z wagą W. Algorytm dodawania w naturalnym zapisie wagowym z wagą W jest w zasadzie identyczny jak w przypadku naturalnego zapisu dziesiętnego. Jeśli dodajemy w naturalnym zapisie wagowym z wagą W 2 liczby a i b reprezentowane słowami
a = anan-1...a1a0 i
b = bkbk-1,...b1b0
to żeby uzyskać sumę s = snsn-1...s1s0 postępujemy tak:
1) Obliczamy s0 = a0 + b0 (mod W). Jeśli s0 = a0 + b0 ≥ W to podstawiamy c1 := 1 a jeśli s0 = a0 + b0 < W to podstawiamy c1 := 0
2) Obliczamy s1 = a1 + b1+ c1 (mod W). Jeśli s1 = a1 + b1 + c1 ≥ W to podstawiamy c2 := 1 a jeśli s1 = a1 + b1 + c1 < W to podstawiamy c2 := 0
.....
i) Obliczamy si = ai + bi + ci (mod W). Jeśli si = ai + bi + ci ≥ W to podstawiamy ci+1 := 1 a jeśli si = ai + bi + ci < W to podstawiamy ci+1 := 0
......
n) Obliczamy sn = an + bn + cn (mod W). Jeśli sn = an + bn + cn ≥ W to podstawiamy cn+1 := 1 a jeśli sn = an + bn + cn < W to podstawiamy ci+1 := 0.
Algorytm mnożenia w naturalnym zapisie wagowym z wagą W jest w zasadzie identyczny jak w przypadku naturalnego zapisu dziesiętnego ale używamy „tabelki mnożenia” właściwej dla W tzn. używamy „tabelki mnożenia” dla par liczb <0,W−1>×<0,W−1>.
Przykład: Mamy 2 liczby w zapisie NKB 10101010a=11111111b=. Stosując opisany algorytm otrzymujemy sumę:
10101010
Suma: 11111111
110101001
1010
1111
1010
1010
1010
Iloczyn: 1010
10010110
2.3. Naturalne kody wagowe o stałej długości słowa kodowego
Jeśli operujemy słowami kodowymi o stałej długości n i V2 = {0,1, <span class="equation" style="width:100%;;;;"></span>−1}, gdzie
∈ {2,3...} to różnych słów kodowych jest
. Tyle jest n elementowych wariacji z powtórzeniami ze zbioru
elementowego. Zatem zbiór obiektów kodowanych może zawierać co najwyżej
elementów.
Naturalny kod wagowy o stałej długości słowa kodowego to kod, które dla ustalonego koduje liczby ze zbioru {0,1,2,...,<span class="equation" style="width:100%;;;;">
</span>−1} w taki sposób, że słowa kodowe należą do iloczynu kartezjańskiego
. Algorytm tworzenia słów kodowych dla takiego kodu numerycznego polega na uzupełnianiu słowa utworzonego dla naturalnego zapisu wagowego zerami z lewej strony aż do uzyskania wymaganej długości słowa kodowego n.
Z kodami numerycznymi o stałej długości (w szczególności z kodami wagowymi o stałej długości) wagowymi wiążemy pojęcie zakresu zapisu. Zakres zapisu to zbiór liczb reprezentowanych w tym zapisie. Jeśli rozważany kod jest binarny i słowo kodowe n bitowe ma postać an-1...a1a0 ∈ {0,1}n to bit nazywamy bitem najbardziej znaczący lub bitem MSB (Most Significant Bit) a bit a0 bitem najmniej znaczącym lub bitem LSB (Least Significant Bit).
0 → 000
1 → 001
2 → 010
3 → 011
4 → 100
5 → 101
6 → 110
7 → 111
0→ 0000
1→ 0001
2→ 0010
3→0011
4→ 0100
5→ 0101
6→ 0110
7→ 0111
8→ 1000
9→ 1001
10→1010
11→1011
12→1100
13→1101
14→1110
15→1111
2.4. Konwersja naturalnych zapisów wagowych
Chcemy przejść z zapisu naturalnego wagowego z wagą W1 na zapis naturalny wagowy z wagą W2. Postępujemy według schematu
Zapis akak-1...a0 z wagą W1 wartość m
Zapis a'rar'-1...a'0 z wagą W2
(1) Obliczamy po prostu wartość m zgodnie ze wzorem
m = akW1k + ak-1W1W-1 + ... a1W + a0
Wygodnie w tym celu posłużyć się schematem Hornera, czyli algorytmem obliczenia wartości wielomianu akxk + ak-1xk-1 + ...a0 w punkcie x.
Schemat Hornera: (...((akx + ak-1)x + ak-2)x + ... + a1)x + a0 = akxk + ... + a0
(2) Dzielimy z resztą liczbę m przez W2 otrzymując ciąg reszt a'rar'-1...a'0 , dokładniej:
m = m1⋅ W2 + a'0
m1 = m2 ⋅ W2 + a'1
m2 = m3⋅ W2 + a'2
.
.
.
mr-1 = mr⋅ W2 + a'r-1
itd. aż kolejne mr = 0 co stanowi regułę stopu algorytmu konwersji.
Ostatecznie uzyskujemy zapis a'rar'-1...a'0 z wagą W2
2.5. Uzupełnienie do W-1
Dokładniej uzupełnienie do W-1 (gdzie W ∈ N, W ≥ 2) słowa anan-1an-2...a0 nad alfabetem {0,1...<em>W−</em>1}. Jest to słowo bnbn-1bn-2...b0 takie, że ai + bi = W−1 dla i=0,1,2,...,n, gdzie dodawanie jest zwykłym dodawaniem ze zbioru liczb całkowitych.
Dla ustalonego n ∈ N ∪ {0} uzupełnienie do W-1 jest więc odwzorowaniem:
U1: {0,1,...,W−1}n+1 → {0,1,...,W−1}n+1
Łatwo sprawdzić, że odwzorowanie to jest idempotentne tzn. U1 U1 = id, czyli uzupełnienie do W-1 zastosowane dwa razy do danego słowa daje to samo słowo.
Najważniejsze w praktyce jest uzupełnienie do jedności (ang. 1's complement)
2.6. Uzupełnienie do W
Dokładniej uzupełnienie do W (gdzie W ∈ N, W ≥ 2) słowa anan-1an-2...a0 nad alfabetem {0,1...<em>W−</em>1}. Niech słowo bnbn-1bn-2...b0 będzie uzupełnieniem do W-1 słowa anan-1an-2...a0. Potraktujmy słowo bnbn-1bn-2...b0 jak liczbę w zapisie naturalnym wagowym z wagą W i dodajmy do tej liczby 1 arytmetycznie zaniedbując ewentualne przeniesienie na pozycję n+1 . Uzyskana w ten sposób liczba cncn-1cn-2...c0 (w zapisie naturalnym wagowym z wagą W ) nosi nazwę uzupełnienia do W.
Dla ustalonego n ∈ N ∪ {0} uzupełnienie do W jest więc odwzorowaniem:
U2: {0,1,...,W−1}n+1 → {0,1,...,W−1}n+1
Łatwo sprawdzić, że odwzorowanie to jest idempotentne tzn. Uw Uw = id, czyli uzupełnienie do W zastosowane dwa razy do danego słowa daje to samo słowo.
Najważniejsze w praktyce jest uzupełnienie do dwóch (ang. 2's complement)
2.7. Zapis moduł znak
W powyżej przedstawiony w punktach 1-4 sposób reprezentujemy łatwo słowami nad ustalonym alfabetem liczby ze zbioru N ∪ {0} czyli liczby naturalne i zero. Zajmiemy się teraz sposobami reprezentowania liczb ujemnych. Rozszerzymy więc zbiór V1 liczb reprezentowanych na wszystkie liczby całkowite Z.
Omówimy 3 zasadnicze kody numeryczne służące do tego celu: tzw. zapis moduł znak (ang. sign magnitude notation), zapis uzupełnień do W, gdzie W jest wagą lub jak mówimy podstawą zapisu (w szczególności dla W=2 uzyskujemy zapis uzupełnień do 2), zapis uzupełnień do W-1 i jego szczególny przypadek zapis uzupełnień do 1.
Załóżmy, że mamy ustaloną wagę W ≥ 2 i chcemy zapisać liczbę całkowitą a za pomocą zapisu moduł znak. Postępujemy wówczas tak: zapisujemy za pomocą naturalnego zapisu wagowego z wagą W liczbę |a| uzyskując słowo an-1ak-1...a0, gdzie ai ∈ <0,W−1>.
Słowem reprezentującym liczbę a będzie słowo anan-1ak-1...a0, gdzie an ∈ {0,1} przy czym jeśli a ≥ 0 to an = 0 a jeśli a < 0 to an = 1.
Zauważmy, że zapis moduł znak jest na dobrą sprawę tym samym pomysłem co zapis liczb dziesiętnych ze znakiem jakim posługujemy się na co dzień.
Zapis moduł znak oczywiście może oczywiście tak zmodyfikować by był kodem o stałej długości słowa kodowego. Istotny staje się wówczas zakres zapisu moduł znak. Jeśli używamy n+1 bitów do zapisu słowa kodowego to zakres ten jest równy <−2n + 1,2n−1>.
2.8. Zapis uzupełnień do W i zapis uzupełnień do 2
Dokładniej, omówimy zapis uzupełnień do W (kod numeryczny uzupełnień do W) dla liczb całkowitych n ∈ Z (a więc również ujemnych). Wykorzystamy następujący fakt.
Fakt: Wybierzmy dowolne m ∈ Z, m ≠ 0 i ustalmy wagę W (W ∈ N, W ≥ 2). Wówczas istnieje dokładnie jeden ciąg: anan-1...a0, gdzie n > 0, taki że dla każdego i = 1,2,...n−1 ai ∈ {0,1,...W−1}, an ∈ {0,1,...W−1}, oraz an = 0 i an-1 ≠ 0 i lub an = 1 taki, że:
Dowód: Prosty dowód pozostawiamy Czytelnikowi jako ćwiczenie.
Dla przypomnienia: funkcja sgn: R → {−1,0,1} (tzw. funkcja signum, czyli znak),
zdefiniowana jest dla wzorem: sgn(x) =
Jak widać dla m ∈ N, m ≠ 0 dzięki powyższemu faktowi możemy zdefiniować odwzorowanie : Z \ {0} → <0,W−1>* wzorem
(m) = anak...a0 ∈ <0,W−1>*. Jeśli przyjmiemy dodatkowo, że
(0) = 0 ∈ <0,W−1>*, to określimy odwzorowanie
nazywane kodem uzupełnień do W.
1. Jeśli an = 0 (tzn. słowa kodowe są postaci 0an-1an-2...a0) to liczba jest nieujemna
2. Jeśli an = W−1 (tzn. słowa kodowe są postaci (W−1)an-1an-2...a0) to liczba jest nieujemna
Algorytm obliczania liczby przeciwnej do danej jest w zapisach uzupełnień do W jest następujący. Jeśli m ∈ Z i słowo anan-1...a0 reprezentuje tę liczbę to słowo reprezentujące −m uzyskujemy jako uzupełnienie do W słowa anan-1...a0.
Powyżej zdefiniowany kod uzupełnień do W jest kodem o zmiennej długości słowa kodowego. Zdefiniujemy teraz kod uzupełnień do W o stałej długości słowa kodowego.
Wykorzystamy następujący fakt:
Fakt: Wybierzmy dowolne m ∈ <−Wn,Wn−1>, gdzie n > 0 i ustalmy wagę W (W ∈ N, W ≥ 2). Wówczas istnieje dokładnie jeden ciąg: anan-1...a0, taki że dla każdego i = 1,2...,n, ai ∈ {0,1,...,<i>W</i>−1} taki, że:
Dowód: Prosty dowód pozostawiamy Czytelnikowi jako ćwiczenie.
Odwzorowanie :<−Wn,Wn−1>→<0,W−1>n zdefiniowane w powyższym twierdzeniu nosi nazwę zapisu uzupełnień do W (o stałej długości słowa kodowego).
Zakres tak zdefiniowanego kodu jest równy oczywiście <−Wn,Wn−1>.
Algorytm obliczania liczby przeciwnej do danej jest w zapisach uzupełnień do W (o stałej długości) jest następujący. Jeśli m ∈ <−Wn,Wn−1> i słowo anan-1...a0 reprezentuje tę liczbę to słowo reprezentujące −m uzyskujemy jako uzupełnienie do W słowa anan-1...a0.
W systemach cyfrowych stosujemy głównie zapis uzupełnień do 2 (ang. two`s complement notation), który nazywany jest również zapisem U2. Jest to obok zapisu NKB najczęściej stosowany w technice mikroprocesorowej kod numeryczny. Popularność zapisu U2 wynika m.in. z faktu, że algorytmy dodawania i odejmowania w U2 dają się zrealizować a sposób naturalny w zwykłym, prostym sumatorze sumującym liczby w kodzie NKB.
W kodzie U2 (o stałej długości słowa n+1) słowo binarne anan-1...a0 związane jest z reprezentowaną tym słowem liczbą za pomocą wzoru
a zakres reprezentowanych liczb jest równy <−2n,2n−1>
Bit an nazywamy bitem najbardziej znaczącym lub bitem MSB od ang. Most Significant Bit) a bit a0 bitem najmniej znaczącym lub bitem LSB od ang. Least Significant Bit.
W zapisie U2, żeby obliczyć –m mając kod U2 liczby m (czyli rozwinięcie, jak czasem mówimy) negujemy wszystkie bity i na najmniej znaczącej pozycji dodajemy arytmetycznie 1 (tzn. z uwzględnieniem przeniesień). Uzyskany wynik, tzn. słowo binarne reprezentuje tzn. jest kodem U2 liczby –m. Jest to bardzo wygodny algorytm ale uwaga –m musi należeć do zakresu zapisu.



2.9. Zapis uzupełnień do W–1 i zapis uzupełnień do 1
Zapis uzupełnień do W-1 służy do zapisywania liczb całkowitych. Jeśli liczba m ∈ Z jest nieujemna, tzn. m ≥ 0, to zapisujemy ją tak:
m = 0an-1an-2...a0
gdzie an-1an-2a...a0 jest naturalnym zapisem wagowym z wagą W liczby m, a więc:
Jeśli liczba m ∈ Z jest ujemna (lub równa 0), tzn. L ≤ Z, to wstępnie obliczamy jak wyżej słowo kodowe odpowiadające liczbie |m| zapisujemy je tak: |m| = bnbn-1bn-2...b0 a następnie jako słowo kodowe odpowiadające m przyjmujemy słowo będące uzupełnieniem do W-1 słowa bnbn-1bn-2...b0.
Jeśli w opisanej wyżej procedurze stosujemy zamiast naturalnego zapisu wagowego z wagą W zapis naturalny wagowy z wagą W o stałej długości n słowa kodowego to uzyskujemy zapis uzupełnień do W-1 ze stałą długością słowa kodowego. Zakres takiego zapisu uzupełnień do jedności jest równy <−Wn+1,Wn−1>.
W systemach cyfrowych wykorzystuje się najczęściej zapis uzupełnień do 1 ze słowem kodowym o stałej długości. Dla takiego zapisu przy słowie o długości n+1 zakres zapisu jest równy <−2n+1,2n−1>. Zapis uzupełnień do 1 nazywa się też zapisem U1.


Algorytm obliczania liczby przeciwnej jest w zapisie uzupełnień do W-1 wyjątkowo łatwy. Obliczmy po prostu uzupełnienie do W-1 słowa wejściowego. W przypadku U1 sprowadza się to do zanegowania bitów.
2.10. Zapis stałoprzecinkowy
Już wiemy z poprzednich punktów jak można reprezentować liczby naturalne i 0 w systemie cyfrowym oraz jak można reprezentować liczby całkowite czyli mówiąc niezbyt precyzyjnie liczby ze znakiem. Często jednak chcemy reprezentować w systemie cyfrowym ułamki (liczby wymierne) i do tego celu wykorzystujemy koncepcję zapisu stałoprzecinkowego (ang. fixed point notation).
Oczywiście naturalnym pomysłem jest reprezentowanie liczby wymiernej jako pary uporządkowanej liczb całkowitych (m,n), gdzie m, n ∈ Z. Tak czasem się robi ale algorytmy wykonywania działań arytmetycznych (i porównywanie liczb) okazuje się bardziej skomplikowane niż w zdefiniowanym poniżej zapisie stałoprzecinkowym.
Do uwzględnienia znaku można użyć zapisu uzupełnień do W, uzupełnień do W-1 lub zapisu moduł znak. W szczególności możemy użyć zapisu U2 lub U1. Dla ustalenia uwagi zakładamy, że używamy zapisu uzupełnień do W.
Zapis stałoprzecinkowy (uzupełnień do W) ze stałą długością słowa kodowego definiujemy następująco. Będziemy reprezentować liczby ze zbioru A = {−<em>W<sup>n</sup> + k</em>⋅<em>W</em><sup>−<em>r</em></sup>; <em>k</em> ∈ < 0, <em>W</em><sup><em>n</em>+<em>r</em>+1</sup>>} (gdzie n ≥ 1, r ≥ 0, n, r ∈ Z). Liczbę n ∈ A reprezentujemy słowem anan-1...a0a-1...a-r (słowem nad alfabetem <0, W−1>) tak by był spełniony wzór
Jeśli założymy, że an ∈ {0,<em>W</em>−1} i ai ∈ {0,<em>W</em>−1} to takie słowo anan-1...a0a-1...a-r jest wyznaczone jednoznacznie.
Rys. 1. Rozmieszczenie na osi liczbowej liczb reprezentowanych w zapisie stałoprzecinkowym. Warto zwrócić uwagę na równomierne rozmieszczenie reprezentowanych liczb. ”Skok” czyli odległość sąsiednich liczb wynosi W−r.
Liczba dodatnia reprezentowana jest więc ciągiem 0an-1an-2...a0a-1...a-r i możemy zapisać
a liczba ujemna ciągiem
gdzie an = W −1.
Dla W=2 czyli dla zapisu binarnego mamy 1 bit znaku, n bitów części całkowitej i r bitów części ułamkowej.
Algorytm obliczania liczby przeciwnej do danej jest identyczny jak w przypadku zapisu uzupełnień do W. W celu znalezienia liczby przeciwnej obliczamy uzupełnienie do W słowa wejściowego.
Analogicznie jak wyżej można zdefiniować zapis stałoprzecinkowy o zmiennej długości słowa kodowego.
Oczywiście zapisy U2, U1 i moduł znak tak jak zdefiniowano je w punktach poprzednich można traktować jako zapisy stałoprzecinkowe z odpowiednio umieszczonym przecinkiem.
Rys. 2. Koncepcja zapisu stałoprzecinkowego
2.11. Zapis zmiennoprzecinkowy
Zapis zmiennoprzecinkowy lub notacja zmiennoprzecinkowa (czasem mówimy zapis zmiennopozycyjny) to jeden z najczęściej stosowanych w mikroprocesorach kodów numerycznych. Angielskie nazwy to floating point notation lub scientific notation.
Zasadniczy pomysł jest bardzo prosty. Liczby bardzo duże i bardzo małe wygodnie jest zapisywać w następującej wykładniczej postaci np. x = −1,5 ⋅ 1015 lub ogólniej w postaci x = (−1)s ⋅ ⋅ We. Możemy zakładać, że W ∈ N, W ≥ 2 ale w praktyce wykorzystujemy W = 10 lub W = 2.
W dalszym ciągu będziemy zakładać, że W = 2 i będziemy rozważać zapis zmiennoprzecinkowy binarny. Liczba s ∈ {0,} reprezentuje znak liczby x. Liczbę ≥ 0 nazywamy mantysą (lub częścią ułamkową ang. mantissa lub fraction) a liczbę e wykładnikiem (lub cechą ang. exponent). Tak więc liczbę x = (−1)s ⋅
⋅ We możemy reprezentować jako trójkę uporządkowaną (s,
, e) czyli trójkę (znak,mantysa,wykladnik).
Oczywiście jeśli nie narzucimy dodatkowych warunków na i e to takie przedstawienie nie jest jednoznaczne. Liczby
i e muszą być ponadto zapisane w ustalonych kodach numerycznych. Zakładamy w dalszym ciągu, że są to kody numeryczne binarne.
Przedstawienie liczby staje się jednoznaczne jeśli liczba ma tzw. postać znormalizowaną. Mówimy, że liczba reprezentowana w zapisie ma postać znormalizowaną jeśli mantysa zapisana w zapisie stałoprzecinkowym bez znaku (z przecinkiem za pierwszym bitem) ma postać: 1.ciąg bitów czyli
(jedynka przed przecinkiem jako pierwszy bit, przecinek (czyli kropka), ciąg bitów)
co odpowiada wartości z przedziału [1,2). Ponieważ liczby pamiętamy w postaci znormalizowanej nie ma potrzeba pamiętania pierwszego bitu mantysy
.
Najczęściej zapis zmiennoprzecinkowy używany jest zgodnie ze standardem IEEE 754. Poniżej opiszemy formaty tego standardu dla słów 32 i 64 bitowych.
Rys. 3. Rozmieszczenie na osi liczbowej liczb reprezentowanych w zapisie zmiennoprzecinkowym. Warto zwrócić uwagę na charakterystyczne nierównomierne rozmieszczenie reprezentowanych liczb. Odległość sąsiednich liczb jest najmniejsza w pobliżu zera i rośnie wraz ze wzrostem odległości od 0.
Rys. 4. Format standardu zapisu zmiennoprzecinkowego IEEE 754 dla pojedynczej precyzji (ang. single precision). Słowa są 32 bitowe.
Rys. 5. Format standardu zapisu zmiennoprzecinkowego IEEE 754 dla podwójnej precyzji (ang. double precision). Słowa są 64 bitowe.
Część ułamkowa zgodnie uwagą o postaci znormalizowanej pozbawiona jest w standardzie IEEE 754 pierwszej 1. Chcąc więc odtworzyć rzeczywistą wartość
należy poprzedzić ciąg bitów
jedynką i odczytać (1.
) jako liczbę zapisaną w zapisie stałoprzecinkowym bez znaku.
Z kolei bit znaku s=1 oznacza liczb ujemną a równy 0 liczbę dodatnią.
W dosyć specjalnym kodzie zapisany jest wykładnik e jest to mianowicie tzw. obciążony kod NKB. W ogólnym przypadku dla słowa n bitowego jest to kod numeryczny przyporządkowujący liczbom całkowitym ze zbioru <−2n−1+1,2n−1> kolejne słowa kodowe kodu n bitowego NKB. W standardzie IEEE 754 dla słowa 32 bitowego na zapamiętanie e mamy 8 bitów więc stosujemy do kodowania e obciążony 8 bitowy kod NKB.
2.12. Kody BCD
Ogólnie rzecz biorąc, kod BCD (od ang. Binary Coded Decimal czyli cyfra dziesiętna zakodowana binarnie) to każde odwzorowanie k: {0,1,...9}→{01}n. Zbiór obiektów kodowanych jest więc zbiorem V1 = {0,1,...9} cyfr dziesiętnych. Kod BCD można traktować jako kod alfanumeryczny lub kod liczbowy. Kody BCD nazywa się również w literaturze polskiej kodami dwójkowo-dziesiętnymi.
Ponieważ zależy nam na tym, żeby kod był możliwie krótki, kody BCD są z reguły funkcjami k: {0,1,...9}→{01}n.. Oczywiście nie możemy przyjąć tu krótszych słów kodowych niż 4 bitowe (bo funkcja k nie będzie różnowartościowa) tak więc n ≥ 4.
Wszystkich możliwych kodów 4 bitowych BCD jest bardzo dużo bo tyle ile 10 elementowych wariacji bez powtórzeń ze zbioru 16 elementowego czyli
a różnych kodów BCD n bitowych dla n ≥ 4 mamy tyle ile 10 elementowych wariacji bez powtórzeń ze zbioru 2n elementowego czyli
Kodów BCD używanych w praktyce jest jednak zaledwie kilka
- kod BCD 8421 – kod naturalny, najbardziej popularny kod BCD.
- kod BCD 2421 tzw. kod Aikena (kod 4 bitowy)
- kod z nadmiarem 3 tzw. kod excess 3 (kod 4 bitowy)
- kod 2 z 5 (kod 5 bitowy)
- kod 1 z 10 (kod 10 bitowy)
- kod Graya z nadmiarem 3 (kod 4 bitowy)
- kod wskaźników cyfrowych 7 –mio segmentowych (kod 7 bitowy)
Kod BCD 8421 jest następujący
0 → 0000
1 → 0001
2 → 0010
3 → 0011
4 → 0100
5 → 0101
6 → 0110
7 → 0111
8 → 1000
9 → 1001
Czterobitowy kod BCD w naturalny sposób umożliwia zapisanie 2 słów kodowych BCD na jednym bajcie. Taką sytuację nazywamy spakowanym kodem BCD. Czasem jednak wygodnie jest w jednym bajcie mieć tylko jedno słowo kodowe BCD na mniej znaczących bitach. Taką sytuację nazywamy nie spakowanym kodem BCD. Kod BCD 8421 w postaci nie spakowanej ma tę zaletę, że słowa kodowe dają się łatwo i w naturalny sposób zastąpić odpowiednimi słowami kodowymi kodu alfanumerycznego ASCII.
Korekcja wyniku dodawania 2 cyfr zapisanych w kodzie BCD 8421. Sumator mikroprocesora z reguły nie ma specjalnego sumatora sumującego w kodzie BCD i używa do sumowania zwykłego sumatora sumującego w kodzie NKB. Dlatego też wynik dodawania 2 cyfr (zwykłego dodawania 4 bitowych słów binarnych) nie musi być wynikiem prawidłowym. Jeśli uzyskany wynik jest mniejszy równy 9 (w kodzie NKB) wynik jest prawidłowy. Jeśli jest większy od 9 lub pojawiło się przeniesienie na pozycję 4-tą (przy numeracji bitów od 0). wynik należy skorygować. Korekcja polega na dodaniu do 4 bitowego wyniku liczby 6.
Kody BCD używane są z reguły połączeniu z innymi kodami numerycznymi np. BCD i naturalny kod dziesiętny. Algorytm dodawania tak zapisanych liczb dziesiętnych kodowanych kodem BCD 8421 polega na kolejnym dodawaniu tetrad (tetrada to słowo 4 bitowe) z ewentualną korekcją wyniku.
W systemach mikroprocesorowych korekcję wyniku przy operacjach w kodzie BCD 8421 realizuje specjalny rozkaz zwykle o mnemonice (czyli symbolicznym oznaczeniu rozkazu) DAA (Decimal Addition Adjust).
Kod BCD excess 3. Kod excess 3 uzyskujemy dodając arytmetycznie 3 (czyli 0011) do słów kodu BCD 8421. Kod BCD excess 3 jest następujący
0 → 0011
1 → 0100
2 → 0101
3 → 0110
4 → 0111
5 → 1000
6 → 1001
7 → 1010
8 → 1011
9 → 1100
Algorytm korekcji wyniku dodawania dla kodu excess 3 jest następujący
- Jeśli nie pojawiło się przeniesienie z danej pozycji, to odejmij 3 od wyniku.
- Jeśli pojawiło się przeniesienie z danej pozycji, to dodaj 3 do wyniku.
Algorytm korekcji jest więc sterowany jest przeniesieniem, jest to proste i wygodne, (tzn. potrzeba korekcji sygnalizowana jest przez przeniesienie). Korekcja przeprowadzana jest „jakby na boku”. Maksymalne opóźnienie tego algorytmu może być znacznie mniejsze niż w przypadku algorytmu dla kodu BCD 8421. Uzasadnienie opisanego algorytmu korekcji pozostawiamy Czytelnikowi.
Kod 2 z 5 . Kod 2 z 5 (należy do grupy kodów BCD i kodów o stałej liczbie jedynek w słowie kodowym tzw. kodów m z n). Słowo kodowe dla kodu 2 z 5 ma długość 5. Zbiór obiektów kodowanych V1 = {0,1,...9} czyli jest taki jak w każdym kodzie BCD. Alfabet kodu V2 = {0,1} :
3 słowa z 1 na pozycji 4 i 0 na pozycji 5
2 słowa z 1 na pozycji 3 i 0 na pozycji 5 i 4
1 słowo z 1 na pozycji 2 i 0 na pozycji 5,4 i 3
Cechy charakterystyczne kodu 2 z 5:
- długość podstawowego słowa kodowego jest zawsze równa 5
- mamy w słowie kodowym stałą równą 2, liczbie jedynek,
- kod 2 z 5 może być traktowany jako alfanumeryczny, jeśli 0,1,...,9 uważamy za symbole lub jako liczbowy, jeśli 0,1,...,9 uważamy za liczby;
- jako kod numeryczny, kod 2 z 5 jest kodem niewagowym;
- 16
- kod 2 z 5 jest kodem redundancyjnym.
Kod 1 z 10. Dla takiego kodu mamy jak dla każdego kodu BCD V1 = {0,1,2,3,...,8,9}
0→1000000000
1→0100000000
2→0010000000
3→0001000000
4→0000100000
5→0000010000
6→0000001000
7→0000000100
8→0000000010
9→0000000001
Zasada tworzenia kolejnych słów kodowych jest przesuwająca się jedynka od lewej strony do prawej. Kod 1 z 10 jest oczywiście przykładem redundancyjnego kodu BCD.
2.13. Kody Graya
Kody Graya (w najprostszym ujęciu) to kody numeryczne, które liczbom całkowitym ze zbioru <0,2n−1> (gdzie n ≥ 1) przyporządkowują słowa kodowe n bitowe w taki sposób, że sąsiednie liczby (tzn. różniące się o 1 lub 2n−1) mają słowa kodowe różniące się tylko na jednej pozycji. Oznacza to, że odległość Hamminga słów kodowych sąsiednich liczb jest równa 1. Można pokazać, że kod Graya jest kodem niewagowym.
0 → 0000
1 → 0001
2 → 0011
3 → 0010
4 → 0110
5 → 0111
6 → 0101
7 → 0100
8 → 1100
9 → 1101
10 → 1111
11 → 1110
12 → 1010
13 → 1011
14 → 1001
15 → 1000
Sposób tworzenia tego kodu dla zbioru <0,2n+1−1> jeśli już mamy kod Graya dla zbioru< wyjaśnia Rys. 6. Opisany na Rys. 6. mechanizm umożliwia zdefiniowanie kodu Graya dla dowolnego zbioru liczb postaci <0,2n+1−1>. Definicja ma charakter indukcyjny. Punktem wyjścia jest oczywiście stwierdzenie, że kod dla zbioru liczb <0,1> = {0,1} zdefiniowany jako funkcja realizująca przyporządkowanie 0 → 0, 1 → 1 jest kodem Graya.
Rys. 6. Sposób tworzenia kodu Graya dla zbioru liczb <0,2n+1−1> jeśli mamy kod Graya dla zbioru liczb .<0,2n−1>.
Kody Graya stosowane są w urządzeniach do cyfrowego pomiaru położenia w urządzeniach mechanicznych np. w tarczach kodowych do pomiaru kąta określającego położenie wału obrotowego.
Uogólnieniem koncepcji kodów Graya są kody Graya na grafach. Chodzi w nich o takie przyporządkowanie słów kodowych wierzchołkom grafu by połączone gałęzią wierzchołki miały słowa kodowe różniące się tylko na jednej pozycji.
2.14. Kody m z n
Kody m z n są kodami numerycznymi binarnymi o stałej długości n charakteryzujące się stałą liczbą m jedynek na n pozycjach w słowie kodowym. Przy ustalonym m i n liczba takich słów kodowych jest równa . Zaletą takich kodów jest stosunkowo łatwa kontrola poprawności słowa kodowego.
Do najprostszych kodów m z n należy kod 1 z n. W rozdziale poświęconym kodom BCD poznaliśmy również kod 2 z 5 oraz kod 1 z 10.
Kod 1 z n. Zbiór obiektów kodowanych to V1 = {0,1,...,<em>n</em>−1}. Alfabet kodu V2 = {0,1}. Kod 1 z n to odwzorowanie różnowartościowe V1 → Vn2 zadane wzorem:
Czyli m kodujemy za pomocą pojedynczej jedynki umieszczonej na pozycji m + 1 licząc od lewej strony.
0 → 100000000
1 → 01000000
2 → 00100000
3 → 00010000
4 → 00001000
5 → 00000100
6 → 00000010
7 → 00000001
Zasada tworzenia kolejnych słów kodowych to przesuwająca się jedynka od lewej strony do prawej.
2.15. Zapisy resztowe
Zapis resztowy to inaczej zapis rezidualny lub zapis RNS (od ang. Residue Number System). Koncepcja zapisu rezidualnego (lub nieco ogólniej arytmetyki ) pochodzi od A.Svobody i M.Valacha i została podana w pracy zamieszczonej w czeskim czasopiśmie ”Stroje na Zapracovani Informaci” w roku 1955. Niezależnie od A.Svobody i M.Valacha pomysł zapisu rezidualnego podał w 1959 r. H.L.Garner w pracy opublikowanej w IRE Transactions EC-8.
Zapis resztowy umożliwia realizację szybkiej arytmetyki (dokładniej: dodawania, odejmowania i mnożenia) bez propagacji przeniesień. Mnożenia które są dosyć złożonymi działaniami (a np. w cyfrowym przetwarzaniu sygnałów mnożeń wykonujemy bardzo dużo) można jak zobaczymy wykonywać bardzo szybko i prosto. Skomplikowane stają się natomiast w zapisie RNS algorytmy dzielenia i porównania liczb.
Symbolem lub x (mod m) będziemy w dalszym ciągu oznaczać resztę z dzielenia liczby x ∈ Z przez liczbę m ∈ N, m ≥ N, m ≥ 2. Potrzebne nam będzie ponadto pojęcie kongruencji. Niech m ∈ N, m ≥ 2, mówimy, że dwie liczby a, b ∈ Z przystają modulo m jeśli dają tę samą resztę z dzielenia przez m. Zapisujemy to pisząc a ≡ b (mod m). Zapis taki nazywamy kongruencją modulo m.
Podstawą zapisu resztowego jest następujące chińskie twierdzenie o resztach.
Niech liczby naturalne m1,m2,...mn większe od jedności będą parami względnie pierwsze (tzn. dla każdego i ≠ j mamy NWD(mi,mj) = 1 wówczas dla dowolnych n liczb całkowitych x1,x2,...xn ∈ Z układ kongruencji liniowych
x ≡ x1 (mod mi)
...
x ≡ xi (mod mn)
ma dokładnie jedno rozwiązanie x w zbiorze <a,a + M − 1>, gdzie a ∈ Z, M = m1⋅m1⋅...mn. Rozwiązanie to jest dane wzorem
gdzie ki dla i = 1,2,3,...n są liczbami całkowitymi spełniającymi warunek
ki ≡ i (mod mi) dla i = 1,2,3,...,n
(3)
ki ≡ 0 (mod mj) dla i ≠ j
Dowód: Możemy znaleźć n liczb c1c2,...cn takich, że ci ∈ oraz ci
= 1 (lub równoważnie ci jest elementem odwrotnym do elementu
w pierścieniu
czyli ci =
). Łatwo sprawdzić, że ki = ci
i = 1,2,3,...,n spełniają warunek (**). Wynika to z tego, że NWD(
) = 1.
Niech będzie dana liczba x ∈ Z i niech liczby naturalne m1,m2,...mn większe od jedności będą parami względnie pierwsze. Zapisem resztowym liczby x nazywamy ciąg skończony
W ten sposób definiujemy kod numeryczny →
. Zakres tak zdefiniowanego zapisu RNS jest oczywiście równy
.
Dodawanie, odejmowanie i mnożenie liczb w zapisie resztowym jest wykonywane po współrzędnych. Dokładniej, jeśli mamy 2 liczby zapisane w zapisie resztowym =
=
i
=
to wówczas (jeśli wynik działania należy do zakresu zapisu) algorytmy dodawania, odejmowania i mnożenia dane są wzorami
gdzie ,
,
są odpowiednio dodawaniem, odejmowaniem i mnożeniem modulo
.
Przyjmijmy m1 = 3 m2 = 5 m3 = 7 i niech x = 3, y = 6. Stosując zapis resztowy mamy x = (0,3,3,0), y = (0,1,6).
Dodając liczby x = (0,3,3,0), y = (0,1,6) w zapisie resztowym według zaproponowanego wyżej algorytmu mamy
x + y = (0,3,3,0) + (0,1,6) = ( )= (0,4,2)
Zauważmy, że x + y = 9 i zapisując 9 w zapisie resztowym mamy (0,4,2) a więc z zaproponowanego algorytmu otrzymaliśmy wynik prawidłowy.
Odejmując liczby y = (0,1,6) i x = (0,3,3), w zapisie resztowym według zaproponowanego wyżej algorytmu mamy
y − x = (0,1,6) − (0,3,3,0) = () = (0,3,3)
Zauważmy, że y − x = 3 i zapisując 3 w zapisie resztowym mamy (0,3,3) a więc z zaproponowanego algorytmu otrzymaliśmy wynik prawidłowy.
Mnożąc liczby x = (0,3,3,0), y = (0,1,6) w zapisie resztowym według zaproponowanego wyżej algorytmu mamy
y ⋅ x = (0,1,6) ⋅ (0,3,3,0) = () = (0,3,4)
Zauważmy, że y ⋅ x = 18 i zapisując 18 w zapisie resztowym mamy (0,3,4) a więc z zaproponowanego algorytmu otrzymaliśmy wynik prawidłowy.
Konwersja zapis NKB liczby x → na zapis RNS liczby x sprowadza się do obliczenia n reszt z dzielenia liczby x przez moduły m1, m2, m3, ..., mn.
Konwersja zapis RNS → zapis NKB jest nieco bardziej złożona. Podamy dwa algorytmy konwersji.
Podstawowy algorytm konwersji jest na ogół formułowany w samym chińskim twierdzeniu o resztach. Przypomnijmy algorytm:
Niech a1, a2, ..., an będzie zapisem RNS liczby x oraz M = m1, m2, ..., mn.
I. Znajdujemy liczby bj ∈ Z takie, że:
bj ⋅ = 1 (mod
) dla j = 1,2,...n).
(8)
II. Jeśli x ∈ [0, M], to
x = [a1 ⋅b1 ⋅ + a2 ⋅b2 ⋅
+ ... + an ⋅bn
]M,
(9)
a ogólnie, jeśli x ∈ [a, a + m), to
x = a + (a1 ⋅b1 ⋅ + a2 ⋅b2 ⋅
+ ... + an ⋅bn − a)(mod M).
(10)
Konwersja wykorzystująca „mixed radix representation” (algorytm Garnera).
I. Niech (a1, a2, a3) będzie zapisem RNS liczby x. Definiujemy stałych cij (liczymy je tylko jeden raz – wstępnie).
cij ⋅ mi ≡ 1 (mod mj) dla 1 ≤ i < j ≤ n,(11)
lub co na jedno wychodzi, poszukujemy odwrotności elementu w pierścieniu
, czyli takiego cij, że cij
mj = 1 w pierścieniu
.
II. Obliczamy ciąg v1, v2,...vn gdzie v1 ∈ [0, mi, −1) będący zapisem mixed radix szukanej liczby x,
v1 := a1 (mod m1)
v2 := (a1 − v1)c12(mod m2)
v3 := ((a1 − v1)c13−v1)c23(mod m3)(12)
.
.
.
vr := ((un − v1)c1r − v2)c2n − ...− vr−1)cr−1,n (mod mn)
III. Obliczamy
x = vn ⋅mn−1 ⋅ ...⋅ m1 + vn−1 ⋅mn−2⋅ ...⋅m1 +...+ v3 ⋅m2 ⋅m1 + v2 ⋅m1 ⋅v1 (13)
Łatwo zauważyć, że w zapisie RNS złożoność konwersji z RNS → zapis binarny; zapis binarny RNS w pewnym stopniu zależy od wyboru modułów m1,m2,m3,...mn.
Dla modułów postaci 2e − 1 konwersja liczby x na jej zapis RNS jest bardzo prosta.
Ogólnie rzecz biorąc, jeśli mamy liczbę x ∈ Z otrzymujemy jej reprezentację resztową przez dzielenie x przez kolejne moduły mi, co daje ciąg (a1,a2,...ar). Jednak dla modułów postaci 2ei − 1, można zrobić tak:
x = atAt + at−1At−1 +...+ a1A + a0(14)
gdzie A = 2ei i 0 ≤ ak < 2ej i dla każdego k = 0,1,...t.
Zatem:
u = a1 + at −1 + ... + a1 + a0 (mod 2ej − 1)(15)
ponieważ A ≡ (mod 2ej − 1) i ostatecznie:
ui = at + at −1 + ... + a0 (mod (2ej − 1))(17)
Zajmijmy się modułami postaci mj = 2ej − 1, gdzie ej ∈ N, ej ≥ 2, dokładniej. Pokażemy jakie korzyści wynikają z wyboru modułów m1,m2,...mr tak, by było mj = 2ej − 1, ej ∈ N, ej ≥ 2. Zależy nam na tym, żeby były to moduły parami względnie pierwsze. Następujące twierdzenie stanowi kryterium na to, by dwie liczby postaci 2− 1 i 2e − 1 były względnie pierwsze.
Dowód: 1. Zauważmy, że zachodzi równość:
((2e − 1)(mod 2− 1)) = 2e, (mod
) − 1
(18)
Jeśli e ≤ , to jest to oczywiste. Niech e>
. Równość (18) jest równoważna do kongruencji
(2e − 1) ≡ 2e (mod ) − 1 (mod (2
− 1))
(19)
i dalej
2e(mod ) 2
...q
≡ 2e mod
(mod (2
− 1))
(21)
ale: 2 ≡ 1 (mod (2
− 1)) zatem prawdziwa jest równość (18).
2. Wracamy do dowodzonej równości. Niech e> (dla e=
równość jest oczywista) wówczas na mocy (18) i algorytmu Euklidesa NWD(2e − 1,2
− 1) = NWD(2
− 1,2e mod
− 1).
Kolejno stosujemy wzór (18), czyli obliczamy resztę z dzielenia przez liczbę mniejszą, zauważmy przy tym, że NWD(e, ) = NWD(
,e (mod
)).
Postępując zgodnie z algorytmem Euklidesa otrzymamy wreszcie, w kolejnym kroku, iloraz reszt =0.
Będziemy więc mieli: NWD(2e − 1,2− 1) = NWD(2a− 1,20 − 1) = 2a − 1
Wniosek: Liczby 2e − 1 i 2 − 1 są względnie pierwsze wtedy i tylko wtedy, gdy e i
są względnie pierwsze.
Słowo maszynowe k-bitowe, ogólnie rzecz biorąc może być jednak takie, że 2k > mi lub 2k < mi . Jeśli moduły dają się zapisać w kodzie NKB na słowie maszynowym to jest to oczywiście wygodne, ale nie jest to niezbędne.
Zasadnicze znaczenie dla szybkości wykonywania działań arytmetycznych w zapisie resztowym mają sposoby wykonywania działań modulo m. Działania modulo m powinny być realizowane za pomocą odpowiednio zaprojektowanych układów.
Uogólnieniem zapisu RNS na liczby zespolone jest zapis resztowy QRNS (Quadratic Residue Numer System)
(Z / m1m2...mnZ) ≅ (Z / m1Z) ⊕ (Z / m2Z) ⊕ ...⊕ (Z / mnZ)
i izomorfizm zadany jest wzorem γ(x) = ([x],[x]
,...,[x]
). Jest to nieco inne sformułowanie chińskiego twierdzenia o resztach.
2.16. Kody wagowe ze zmienną wagą
Kody wagowe ze zmienną wagą lub zapisy ze zmienną podstawą (ang Mixed Radix Notation lub Mixed Radix Representation) to kody liczbowe, zasadniczo służące do reprezentowania (czyli zapisu) liczb ze zbioru N ∪ {0}.
Przypomnijmy podstawowe twierdzenie umożliwiające konstruowanie kodów wagowych. Jest to jednocześnie zasadnicze twierdzenie, na którym opiera się definicja kodów wagowych ze zmienną wagą.
1) dla każdego i = 0,1,...k, ai ∈ {0,1...<em>m<sub>i </sub></em>−1} , ak ≠ 0
2) m = akmk−1mk−2 ⋅...⋅ m0 + ak−1mk−2mk−3⋅...⋅ m0 + a2m1m0 + a1m0 + a0
Oznaczmy Ai = {0,1...<em>m<sub>i </sub></em>−1} dla i = 0,1,2 i niech A = Ai oraz
B = A0 ∪ A0 × A1 ∪ A0 × A1 × A2 ∪... ∪ A0 × A1 × A2 × ... × Ak ∪ ...
Odwzorowanie K: N ∪ {0} ∋ m → K(m) = akak−1...a1a0 ∈ B (gdzie a ∈ Ai) określone na zbiorze N ∪ {0} i zdefiniowane w powyższym twierdzeniu nazywamy kodem wagowym ze zmienną wagą lub zapisem wagowym ze zmienną wagą (ang. mixed radix notation) przy czym przyjmujemy K(0) = 0. Ta przejrzysta i prosta definicja wymaga jednak kilku uwag.
Elementy zbioru A = Ai nazywamy cyframi i często utożsamiamy je, czy wiążemy z pewnymi symbolami np. pisanymi na papierze. Raz więc możemy na element ze zbioru A =
Ai patrzeć jak na liczbę raz jak na symbol. Jest to w praktyce dosyć wygodne. Oczywiście byłoby lepiej gdyby A =
Ai było zbiorem skończonym. Posługiwanie się nieskończonym zbiorem symboli jest bowiem wysoce niepraktyczne.
Zauważmy, że jeśli ciąg (mi)∞i=0 jest ograniczony to A = Ai jest zbiorem skończonym a więc alfabetem. Ponadto istnieje takie k, że mi ≤ mk dla każdego i = 0,1,2... oraz Ai ⊂ Ak = A dla każdego i = 0,1,2.... Istnieje więc taki alfabet A, że odwzorowanie K przyjmuje wartości z A* a więc kod wagowy ze zmienną wagą jest kodem w sensie przyjętej definicji kodu.
Jeśli ciąg (mi)∞i=0 nie jest ograniczony to A = Ai = N ∪ {0} a więc A nie jest zbiorem skończonym i formalnie rzecz biorąc zapis wagowy K ze zmienną wagą nie jest kodem w sensie przyjętej definicji. Jeśli jednak użyć jakiegokolwiek pomocniczego kodu numerycznego K': N ∪ {0} → V* do zapisywania liczb z ze zbioru A =
Ai = N ∪ {0}, gdzie V jest jakimkolwiek alfabetem (np. K': N ∪ {0} → V* może być zwykłym zapisem dziesiętnym znanym ze szkoły) to łatwo odwzorowanie K': N ∪ {0} ∋ m → K(m) = akak−1...a1a0 ∈ B tak zmodyfikować by uzyskać odwzorowanie będące kodem. Wystarczy przyjąć:
: N ∪ {0} ∋ m →
(m) = K'(ak), K'(ak−1), ...K'(a1), K'(a0) ∈ (V ∪ {,})*
Podobnie jak ma to miejsce w zapisie wagowym z ustaloną wagą W, możemy w zapisie ze zmienną wagą reprezentować również liczby ułamkowe „wprowadzając kropkę”.
Zapis ze zmienną wagą jest wykorzystywany w jednym z algorytmów konwersji zapisu RNS na zapis wagowy.
3. Kody alfanumeryczne
Kody alfanumeryczne
3.1. Uwagi wstępne
Jeśli zbiorem obiektów kodowanych V1 jest zbiór znaków to kod : V1 → V*2 (dla ustalonego alfabetu V2 na ogół binarnego) nazywamy kodem alfanumerycznym (ang. alphanumeric code).
W tym podrozdziale poznamy szereg binarnych kodów alfanumerycznych. Najpopularniejsze binarne kody alfanumeryczne to ośmiobitowy kod ASCII (American Standard Code for Information Interchange) i szesnastobitowy kod Unicode. Są to oczywiście kody o stałej długości słowa kodowego.
Znany każdemu telegrafiście i harcerzowi kod Morse'a jest również kodem alfanumerycznym binarnym ale kodem zmiennej długości. Kody alfanumeryczne ASCII zmiennej długości mają specyficzne zalety opisane bliżej w podrozdziale o kompresji danych.
Kod ASCII został wprowadzony w USA w 1963 roku jako kod 7 bitowy. Kod ASCII w swej podstawowej wersji jest więc w zasadzie 7-mio bitowym kodem ale uzupełnionym z reguły bitem parzystości. Jako kod 8 bitowy ma jednak szereg odmian tzw. wersji narodowych.
Kod ASCII jest równoważny z kodem ISO-7 (ISO to skrót od International Organization for Standardization) . Kod ISO-7 został opisany w normie ISO 646 wydanej w roku 1973. Norma ISO 646 przewiduje wprowadzenie znaków narodowych co wiąże się z rozszerzeniem kodu ASCII do kodu 8 bitowego
ISO-8859-2 to ogólnie już przyjęty standard kodowania polskich liter wg polskich norm PN stanowiący rozszerzenie ASCII.
Kod alfanumeryczny UNICODE jest standardem ISO/IEC-10646. Jest to kod 16 bitowy (pierwsza wersja) lub 31 bitowy (druga wersja). UNICODE umożliwia zapisanie wszystkich znaków z alfabetów narodowych (również cyrylicy, alfabetu chińskiego i alfabetu japońskiego)
Kod ASCII w wersji podstawowej koduje 128 znaków. Pierwsze 33 znaki (uporządkowane wg wartości w kodzie NKB słowa kodowego to tzw. znaki sterujące takie jak CR (Carriage Return czyli „powrót karetki”) lub LF (Line Feed czyli „od nowego wiersza”). Służą one do sterowania systemem drukowania lub wyświetlania znaków. Pozostałe znaki to m.in. małe i duże litery alfabetu angielskiego, cyfry, znaki przestankowe oraz kilka symboli matematycznych takich jak nawiasy i znak równości.
3.2. Kod ASCII
W technice cyfrowej i wywodzącej się z niej inżynierii komputerowej dominują obecnie kody alfanumeryczne oparte na standardzie ASCII (American Standard Code for Information Interchange), znanym także jako ANSI X3.4 oraz ISO-646-US i US-ASCII. W standardzie tym 128 podstawowym znakom, takim jak litery alfabetu łacińskiego, cyfry, znaki przestankowe oraz kody sterujące, przyporządkowano liczby z zakresu od 0 do 127. Z uwagi na oczywiste problemy z sortowaniem tak różnorodnych znaków tabele kodowe zestawia się według wartości kodów (dziesiętnych i szesnastkowych) - jak w poniższej tabeli.
|
|
|
|
Tab. 1. US-ASCII
ASCII jest kodem 7-bitowym, przy czym oryginalny standard nie definiuje roli ósmego bitu, który w czysto sprzętowych realizacjach cyfrowych układów sterowania ciągle bywa wykorzystywany do kontroli parzystości lub do przechowywania dodatkowego atrybutu (np. podświetlenia). Najczęściej jednak ósmy bit służy rozszerzeniu podstawowego kodu ASCII o niezbędne znaki alfabetów narodowych, symbole matematyczne, znaki semigraficzne itp.. Niegdyś próbowano dopasować kod ASCII do warunków lokalnych poprzez odmienne wykorzystanie niektórych wartości kodów; np. w ISO-646-DE pod wartościami z zakresu 123-126 (7B-7E) kryją się odpowiednio znaki ä, ö, ü i ß. W miarę, jak rosły potrzeby, pojawiały się kolejne kody o rozmaitych sposobach i zakresach implementacji dodatkowych znaków, a wielu producentów sprzętu i oprogramowania wprowadzało własne "standardy". Z tych najbardziej znane jest rozszerzenie kodu ASCII wprowadzone przez firmę IBM, czyli CP437 (IBM PC Extended ASCII czyli DosLatinUS), które zainicjowało całą serię tzw. stron kodowych, począwszy od CP850 (DosLatin1) i CP852 (DosLatin2), obecnych w kartach graficznych, drukarkach i w MS-DOS.
Tab. 2. CP437
Tab. 3. CP850
Tab. 4. CP852
ISO 8859 to zestaw kilkunastu tabel wykorzystujących wszystkie 256 wartości 8-bitowego słowa kodowego i rozszerzających właściwą tabelę ASCII (kody od 0 do 127 pozostają bez zmian) o znaki pochodzące z poszczególnych regionów Europy, alfabet grecki, cyrylicę 3 itp. Podobnie jak w podstawowym kodzie ASCII, pierwsze 32 pozycje każdej rozszerzonej tabeli ISO 8859-n to niedrukowalne znaki sterujące. Odpowiednikiem CP850 jest ISO 8859-1, czyli Latin1, dedykowany Europie Zachodniej. Odpowiednikiem CP852 jest ISO 8859-2 (Latin2) zawierający komplet polskich znaków, a np. greckie litery można znaleźć w ISO 8859-7 (Greek)
|
|
|
|
Tab. 5. ISO 8859-2 (Latin2)
Wprowadzenie standardu ISO 8859 miało w założeniu uporządkować zasady kodowania rozszerzeń ASCII. Niestety, nie tylko nie zapobiegło definitywnie dalszemu mnożeniu się stosowanych kodów alfanumerycznych, lecz wręcz zapoczątkowało kolejną ich falę, tak w ramach samego standardu (wystarczy prześledzić ewolucję obsługi języków krajów nadbałtyckich od ISO 8859-4 (Latin4) poprzez ISO 8859-10 (Latin6) do ISO 8859-13 (Latin7 Baltic Rim)), jak i incjatyw zewnętrznych (na przykład Microsoft zmodyfikował tabelę ISO 8859-2 i wprowadził ją do Windows jako stronę kodową CP1250 (WinLatin2)). Główną tego przyczyną są obiektywne ograniczenia wynikające ze zbyt małej ilości słów kodu 8-bitowego.
Tab. 6. CP1250 (WinLatin2)
3.3. Unicode
W zapisie UTF-8 każdy znak UCS/Unicode jest przedstawiany w postaci sekwencji od jednego do sześciu 8-bitowych bajtów, zależnie od wartości samego znaku. Poniższa tabela obrazuje zasadę, na jakiej to się odbywa:
|
Tab. 7. UTF-8
0xxxxxxx |
: pierwszy i jedyny bajt sekwencji : drugi i dalsze bajty sekwencji : pierwszy bajt sekwencji 2-bajtowej : pierwszy bajt sekwencji 3-bajtowej : pierwszy bajt sekwencji 4-bajtowej : pierwszy bajt sekwencji 5-bajtowej : pierwszy bajt sekwencji 6-bajtowej |
Tab. 8. Znaczenie bajtu w zapisie UTF-8
W wielobajtowej sekwencji bity oznaczone 'x'-ami czytane od 1-szego, najstarszego bajtu tworzą właściwą wartość znaku UCS/Unicode. Z kolei wartość bieżącego bajtu wskazuje na jego miejsce w sekwencji UTF-8. W ten sposób zapis UTF-8 jest kompatybilny z 7-bitowym US-ASCII i zachowuje względną zwartość tekstów o niewielu znakach rozszerzonych, pozwalając jednocześnie na zapis nawet 31-bitowych wartości i na łatwą do realizacji synchronizację i interpretację przetwarzanych sekwencji.
Unicode | UTF-8 |
Hex | Hex |
0001 | 01 |
007F | 7F |
0080 | C2 80 |
07FF | DF BF |
0800 | E0 A0 80 |
0FFF | E0 BF BF |
1000 | E1 80 80 |
FFFF | EF BF BF |
Zapis UTF-8 jest nadmiarowy, bowiem np. wartościom 16-bitowym przyporządkowuje wartości 24-bitowe (3-bajtowe). W efekcie istnieje wiele ciągów bajtów, które nie są legalnymi sekwencjami UTF-8, nawet jeżeli wykonanie przekształcenia odwrotnego jest technicznie możliwe.
sekwencja | C0 80 | 11000000 10000000 |
maska | 110xxxxx 10xxxxxx | |
wynik | 00 | 00000 000000 |
Oprócz zapisu UTF-8 spotyka się czasem UTF-7 wykorzystujący tylko 7 bitów używanego słowa oraz UTF-16 będący po prostu zapisem kolejnych kodów.
A oto reprezentacja polskich „ogonków” w Unicode:
ą | 0105 | Ą | 0104 |
ć | 0107 | Ć | 0106 |
ę | 0119 | Ę | 0118 |
ł | 0142 | Ł | 0141 |
ń | 0144 | Ń | 0143 |
ó | 00F3 | Ó | 00D3 |
ś | 015B | Ś | 015A |
ź | 017A | Ź | 0179 |
ż | 017C | Ż | 017B |
Tab. 9. Polskie znaki w Unicode
4. Kody wykrywające i korygujące błędy
Kody wykrywające i korygujące błędy
4.1. Dlaczego stosujemy kody wykrywające i korygujące błędy
Całkowicie pewne, bezbłędne przesyłanie słów nad ustalonym alfabetem (z reguły słów binarnych) przez tzw. kanał komunikacyjny nie jest możliwe z uwagi na obecność w środowisku fizycznym szumów i różnego typu zakłóceń. Podobnie przechowywanie, magazynowanie słów w pamięci nie jest pozbawione błędów. Wie o tym dobrze każdy doświadczony (przez los) użytkownik pamięci (własnej). Kody wykrywające i korygujące błędy stosowane są po to by zminimalizować błędy w interpretacji słowa kodowego przenoszącego informację pomimo ewentualnych błędów jakie mogą się pojawić na niektórych pozycjach wewnątrz odebranego słowa kodowego. Kody potrafiące wykrywać tylko błędy nazywamy kodami wykrywającymi błędy a kody potrafiące wykrywać i korygować błędy nazywamy kodami korekcyjnymi ale często dla uproszczenia obie grupy kodów nazywamy kodami korekcyjnymi.
Uwaga: Kody korekcyjne stosowane są głównie w:
- telekomunikacji i sieciach komputerowych
- pamięciach półprzewodnikowych RAM
- systemach zapisu danych na płytach CD
Rys. 1. Przesyłanie słów przez kanał komunikacyjny z zakłóceniami. Ze względu na szumy i zakłócenia musimy liczyć się z wystąpieniem błędów (przekłamań) w odebranym w odbiorniku słowie.
4.2. Wykrywanie błędów metodą kontroli parzystości
Wykrywanie błędów metodą kontroli parzystości jest najprostszą często stosowaną metodą wykrywania błędów.
Załóżmy, że w systemie cyfrowym chcemy przesyłać dane porcjami za pomocą słów n bitowych. Niech x1x2...xn ∈ {0,1}n będzie takim n bitowym słowem. Przed wysłaniem uzupełniamy to słowo jeszcze jednym bitem xn+1 tzw. bitem parzystości przy czym
xn+1 = 1 jeśli liczba jedynek w słowie x1x2...xn ∈ {0,1}n jest nieparzysta
xn+1 = 0 jeśli liczba jedynek w słowie x1x2...xn ∈ {0,1}n jest parzysta
Zatem słowo przesyłane x1x2...xnxn+1 zawiera zawsze parzystą liczb jedynek. Łatwo sprawdzić, że układ generujący bit parzystości jest n wejściową sumą modulo 2.
xn+1 = x1 ⊕ x2 ⊕ ... ⊕ xn
Nadajnik wysyła słowo n+1 bitowe x1x2...xnxn+1 a w odbiorniku dostajemy słowo być może z błędami (przekłamaniami) y1y2...ynyn+1 ∈ {0,1}n+1. Sprawdzamy parzystość liczby jedynek w słowie binarnym odebranym y1y2...ynyn+1. Układ sprawdzający czy liczba jedynek jest parzysta jest n+1 wejściową sumą modulo 2 (por. Rys.2). Jeśli liczba jedynek w słowie y1y2...ynyn+1 jest parzysta to na wyjściu układu mamy 0 jeśli nieparzysta to 1.
Jeśli wysłaliśmy parzystą liczbę jedynek a otrzymaliśmy w odbiorniku nieparzystą tzn., że podczas przesyłania wystąpił błąd. Sprawdzając parzystość wykryjemy każdą nieparzystą ilość błędów.
Zamiast funkcji xn+1 = x1 ⊕ x2 ⊕ ... ⊕ xn można wykorzystać funkcję xn+1 = , wtedy przesyłane słowo będzie zawierało nieparzystą liczbę jedynek.
Pewną modyfikacją opisanej wyżej koncepcji są kody ze stałą liczbą jedynek m w słowie kodowym o stałej długości n tzw. „kody m z n” (por. podrozdział „kody numeryczne”). Pojedyncza zmiana 0 na 1 lub odwrotnie zmienia w takich kodach „bilans” jedynek i zer” i możemy wykryć błąd.
Rys. 2. Układy sprawdzające parzystość słowa binarnego x1x2x3x4x5x6x7x8
4.3. Podstawowe twierdzenia o kodach wykrywających i korygujących błędy
Definicja (m,n) kodu i kodu liniowego. Zakładamy, że chcemy przesyłać słowa nad alfabetem GF(pk) (alfabetem jest ciało skończone mające pk elementów, gdzie p jest liczbą pierwszą w szczególności może to być ciało GF(2) = Z2 = {0,1}. Słowa przesyłane mają długość m, chcemy więc przesyłać słowa z (GF(pk))m. Koncepcja kodu wykrywającego lub korygującego błędy polega na wykorzystaniu kodu redundancyjnego GF(pk))m → (GF(pk))n, gdzie n > m. Taki kod nazywamy (m,n) kodem. Obiektami kodowanymi są tu słowa o długości m nad alfabetem GF(pk) a słowami kodowymi słowa z (GF(pk))m. Zbiory (GF(pk))m i (GF(pk))n są przestrzeniami liniowymi nad ciałem GF(pk). Jeśli odwzorowanie
GF(pk))m → (GF(pk))n jest liniowe to kod nazywamy kodem liniowym.
Liniowość odwzorowania oznacza, że istnieje taka macierz A o współczynnikach w ciele GF(pk) mająca n wierszy i m kolumn, że dla wektora a ∈ (GF(pk))m mamy
gdzie wektor a jest traktowany jako macierz kolumnowa.
Kod wykrywający błędy powinien zachowywać się tak, że gdy odbieramy zamiast słowa kodowego (a) słowo z przekłamaniami na pewnej niewielkiej liczbie co najwyżej r pozycji (oznaczmy to słowo z przekłamaniami przez br(
(a))) to oglądając br(
(a)) będziemy mogli stwierdzić czy nastąpiły przekłamania.
Powiemy, że kod GF(pk))m → (GF(pk))n wykrywa fakt popełnienia co najwyżej r błędów jeśli dla każdego a ∈ (GF(pk))m zmiana cyfr na co najwyżej r pozycjach w słowie kodowym
(a) nie powoduje przejścia słowa kodowego w słowo
(b) dla pewnego b ∈ (GF(pk))m, a ≠ b.
Praktycznie więc wykrywanie błędu może polegać na porównaniu br((a)) z wszystkimi możliwymi słowami kodującymi
(c) dla c ∈ (GF(pk))m. Jeśli nie stwierdzamy zgodności, to stwierdzamy, że słowo br(
(a)) zawiera przekłamanie.
Kod korygujący błędy powinien zachowywać się tak, że gdy odbieramy zamiast słowa kodowego (a) słowo z przekłamaniami na pewnej niewielkiej liczbie co najwyżej r pozycji (tzn. słowo br(
(a))) to oglądając br(
(a)) będziemy mogli skorygować błędy (stosując pewien algorytm) uzyskując słowo
(a) a więc w konsekwencji stwierdzamy, że zostało nadane słowo a.
Zasadnicza koncepcja na jakiej opierają się kody korekcyjne jest taka: Niech GF(pk))m → (GF(pk))n będzie (m,n) kodem kodującym słowa o długości m za pomocą słów o długości n, gdzie n > m. Zakładamy, że podczas transmisji słowa
(a) o długości m może powstać co najwyżej r błędów. Dla każdego a ∈ (GF(pk))m kodujemy przy tym słowo a takim ciągiem a ∈ (GF(pk))n , że
gdzie K(x,r) oznacza kulę o promieniu r w przestrzeni metrycznej GF(pk))n z metryką Hamminga dH. Warunek (1) oznacza, że każde dwie kule rodziny kul K((x),r); a ∈ GF(pk))m są rozłączne. Jeśli tak jest to aby stwierdzić jaka informacja została nadana wystarczy zastosować do słowa odebranego bk(
(a)); a ∈ (GF(pk))m regułę decyzyjną d: GF(pk))n→GF(pk))n zdefiniowaną wzorem
d(br(f(a))) = a
wtedy i tylko wtedy, gdy
dH((a),br,
(a)) =
{<d<sub><em>d<sub>H</sub></em>(<span class="equation">
</span>(<em>x</em>),<em>b<sub>r</sub></em>(<span class="equation">
</span>(<em>a</em>)) }
(2)
Innymi słowy jako informację nadaną przyjmujemy takie a (GF ( pk ))m , że odebrane słowo br (f(a)) (GF( pk ))n jest najbliższe f(a) tzn.
dH (f(a), b) dH ( f(x), b)
(3)
Jest oczywiste, że powyższa reguła decyzyjna przy warunku (1) jest poprawna w tym sensie, że pozwala na odtworzenie wiadomości nadanej przy ograniczonej do r ilości błędów transmisji.
Warto zauważyć, że istotą powyższego pomysłu na kod korekcyjny jest to, że wokół każdego słowa kodującego f(a) (gdzie a (GF ( pk ))m ) tworzymy otoczenie (w metryce Hamminga), którego elementy (oprócz f(a)) nie są wykorzystywane do kodowania słów z a (GF ( pk ))m . Możemy jednak odebrać (w wyniku wprowadzenia błędów podczas przesyłania słowa f(a)) dowolny element z tego otoczenia a mimo to nie tracimy orientacji w sytuacji, potrafimy wykryć i skorygować przekłamania.


Kod blokowy. Niech V będzie ustalonym alfabetem. Kodem blokowym nazywamy kod f : Vm Vn , w którym słowom o długości m (obiektom kodowanym) przyporządkowujemy słowa o długości n, gdzie n m .
Kod blokowy nazywamy kodem grupowym, jeśli słowa kodowe kodu tworzą grupę addytywną.
Różnych typów kodów korekcyjnych (ang. ECC od Error Correcting Codes) jest dosyć dużo. Z bardziej znanych warto wymienić:
- kody cykliczne (kody CRC - ang. Cyclic Redundancy Check)
- kody Hamminga
- kody Reeda-Solomona
- kody Bose-Chaudhuri-Hocquenghema (kody BCH)
5. Szyfry i kryptografia
Szyfry i kryptografia
5.1. Główne cele kryptografii
Ponieważ szyfr to w pewnym uproszczeniu rodzina kodów indeksowana kluczem, więc w naturalny sposób szyfry wpisują się w zagadnienia związane z kodami i kodowaniem.
Kryptologia bazuje na czterech działach matematyki: teorii liczb, algebrze, teorii algorytmów komputerowych i teorii prawdopodobieństwa. Kryptologia dzieli się na kryptografię (sztukę szyfrowania i tworzenia wyrafinowanych zabezpieczeń systemów komputerowych) oraz kryptoanalizę (sztukę łamania szyfrów i zabezpieczeń). Obecnie często nazywa się kryptologię niezbyt precyzyjnie kryptografią i takie uproszczoną nieco terminologię będziemy stosować w dalszym ciągu rozdziału.
Kryptografia to zespół metod matematycznych i technik tkwiący częściowo w szeroko pojętej elektronice a częściowo w informatyce teoretycznej.
Kryptografia oraz nieco obszerniejsza dziedzina jaką jest ochrona informacji w systemach i sieciach komputerowych odgrywają bardzo ważną rolę nie tylko we współczesnej informatyce, ale również w organizacji życia gospodarczego i administracji państwowej. Handel elektroniczny, bankowość elektroniczna, elektroniczna wymiana dokumentów (słynne EDI – Electronic Documents Interchange) czy ogólnie rzecz biorąc bezpieczne magazynowanie i przesyłanie informacji nie byłyby możliwe bez współczesnych metod kryptograficznych. Takie skróty oznaczające bezpieczne protokoły przesyłania danych jak SSL (Secure Socket Layer), SSH (Secure Shell) czy HTTPS (Secure HTTP czyli Secure HyperText Protocol) znane są każdemu użytkownikowi przeglądarki internetowej.
Główne cele kryptografii to
- zapewnienie poufności wiadomości nazywane też utajnianiem wiadomości (ang. privacy lub confidentiality)
- uwierzytelnianie strony czyli identyfikacja strony pragnącej uzyskać dostęp do zasobów systemu komputerowego lub sieci komputerowej (ang. entity authentication lub identification)
- uwierzytelnianie wiadomości lub uwierzytelnianie dokumentu (ang. data origin authentication)
- zapewnienie integralności danych (ang. data integrity)
- niezaprzeczalność (ang. non repudiation)
5.2. System kryptograficzny
Teraz powiemy co to jest wiadomość jawna, kryptogram (albo szyfrogram) oraz szyfr.
System kryptograficzny lub szyfr to piątka uporządkowana (V1,V2 , K , E, D) , gdzie:
- V1 jest alfabetem, w którym zapisujemy wiadomości jawne, V1
M, czyli zbiór wszystkich słów nad alfabetem V1 jest tzw. przestrzenią wiadomości jawnych (M od ang. message) a każdy element M nazywamy wiadomością jawną lub tekstem jawnym.
- V2 jest alfabetem, w którym zapisujemy kryptogramy (szyfrogramy) V2
C , czyli zbiór wszystkich słów nad alfabetem V2 jest tzw. przestrzenią wiadomości zaszyfrowanych, czyli, jak mówimy, szyfrogramów (szyfrogram nazywamy też kryptogramem, wiadomością zaszyfrowaną, lub tekstem zaszyfrowanym). Często alfabety V1 i V2 są tym samym afabetem.
- K jest dowolnym zbiorem jest to tzw. przestrzeń kluczy (ang. key space), każdy element k K nazywamy kluczem (lub kluczem szyfrującym). Na ogół K jest zbiorem skończonym ale w definicji ogólnej systemu kryptograficznego nie robimy tego założenia.
- E jest funkcją E : M K C , obliczanie wartości funkcji E nazywamy szyfrowaniem, a samą funkcję przekształceniem szyfrującym lub co jest nieco mylące również szyfrem. Wartość E(k, m) nazywamy wiadomością zaszyfrowaną, szyfrogramem, kryptogramem lub tekstem zaszyfrowanym. Sposób obliczania E(k,m) dla danych k,m nazywamy algorytmem szyfrowania.
- D jest funkcją D : C K M obliczanie wartości funkcji D nazywamy deszyfrowaniem a samą funkcję D przekształceniem deszyfrującym. Sposób obliczania D(k,m) dla danych k, m nazywamy algorytmem deszyfrowania.
Od systemu kryptograficznego wymagamy by dla każdego klucza szyfrującego k1 K , istniał klucz deszyfrujący k2 K taki, że dla dowolnej wiadomości jawnej m zachodzi
D(k2 , E(k1, m)) m .
Istota rzeczy: Każdą wiadomość zaszyfrowaną musimy umieć rozszyfrować, ale być może służy do tego już inny klucz.
Z powyższej definicji wynika, że dla każdego k1 K , odwzorowanie E( , k1 ) : M C jest funkcją różnowartościową. Oczywiście podobnie odwzorowanie D( , k2 ) : E( M , k1 ) C , (gdzie k2 K jest kluczem deszyfrującym odpowiadającym kluczowi k1 K ) jest funkcją różnowarościową.
Z powyższej definicji wynika również, że mówiąc niezbyt precyzyjnie, szyfr to rodzina kodów sparametryzowana kluczem k K .
Istota rzeczy: Szyfr służy do zastąpienia wiadomości jawnej m kryptogramem c. Celem jest takie przekształcenie postaci wiadomości jawnej, by uzyskać postać nieczytelną dla osób nie dysponujących kluczem deszyfrującym. Realizujemy w ten sposób jeden z zasadniczych celów kryptografii tzw. poufność (ang. confidentiality)
Na ogół im więcej elementów ma zbiór K tym szyfr jest bezpieczniejszy trudniej bowiem dopasować wówczas klucz k do przechwyconego kryptogramu c tak by było metodą przeglądania wszystkich kluczy k czyli za pomocą tzw. ataku brutalnego. D(c, k ) m
Klucz w praktyce to słowo nad jakimś ustalonym alfabetem, na ogół alfabetem, w którym zapisujemy wiadomość jawną, w szczególności może to być alfabet binarny {0,1} i wówczas z reguły K {0,1}n dla pewnego n N lub K {0,1}* . Jeśli klucz jest słowem nad ustalonym alfabetem to możemy mówić o długości klucza.
Na ogół im dłuższy jest klucz tym bezpieczniejszy jest system kryptograficzny. Oczywiście technicznie rzecz biorąc klucz może mieć dowolną długość. Istnieją jednak w wielu krajach (np. w USA, Francji czy Iraku) pewne ograniczenia o charakterze prawnym np. 128 bitowe klucze to standard w USA i w Polsce do zakupów sieciowych z kartą kredytową i w transakcjach bankowych.
W powszechnym mniemaniu klucze 128 bitowe uważa się za bezpieczne, ale rzecz jasna taka zasada jest niebezpieczną półprawdą, bowiem klucza nie można rozpatrywać w oderwaniu od systemu kryptograficznego np. 128 bitowy czy nawet 512 bitowy klucz w przypadku szyfru RSA nie jest kluczem bezpiecznym. Przyjmuje się, że dla RSA bezpieczna długość klucza to 1024 bity lub więcej.
Ważna uwaga praktyczna o tajności metod i algorytmów kryptograficznych:
Nigdy nie budujemy bezpieczeństwa systemu na ukrywaniu metody czy algorytmu kryptograficznego. Wprost przeciwnie, algorytm publicznie znany, o którym wiadomo, że był atakowany bez powodzenia, można uznać za pewny i bezpieczny.
5.3. Podział szyfrów
Systemy kryptograficzne, szyfry, algorytmy kryptograficzne dzielimy na
- symetryczne (inaczej z kluczem symetrycznym )
- asymetryczne (inaczej z kluczem asymetrycznym).
Jeśli do szyfrowania i rozszyfrowania używamy tego samego klucza (lub nieco ogólniej pary kluczy k1 , k2 K , takich że z klucza szyfrującego k1 K daje się łatwo obliczyć klucz deszyfrujący k2 K i odwrotnie z klucza deszyfrującego k2 K daje się łatwo obliczyć klucz szyfrujący k1 K ) to system kryptograficzny, szyfr czy algorytm kryptograficzny nazywamy systemem kryptograficznym, szyfrem czy algorytmem kryptograficznym z kluczem symetrycznym.
Jeśli szyfr jest taki, że do szyfrowania i rozszyfrowania używamy różnych kluczy ( kluczy k1 K takich że z k1 K nie daje się łatwo obliczyć klucz deszyfrujący k2 K i odwrotnie z klucza deszyfrującego k2 K nie daje się łatwo obliczyć klucz szyfrujący k1 K ) to system kryptograficzny, szyfr czy algorytm kryptograficzny nazywamy systemem, szyfrem czy algorytmem kryptograficznym z kluczem asymetrycznym.
Systemy kryptograficzne z kluczem symetrycznym nazywamy też systemami z kluczem prywatnym. Przykładami takich systemów są DES (Digital Encryption Standard), 3DES (tzw. potrójny DES), DESX (DES z wybielaniem), IDEA, LOKI, Twofish, Blowfish, RC5, AES (Advanced Encryption Standard) czyli Rijndael .
Systemy kryptograficzne z kluczem asymetrycznym nazywamy też systemami z kluczem publicznym. Przykładami takich systemów są szyfr RSA, szyfr Rabina, szyfr ElGamala, szyfr plecakowy Merklego-Hellmanna, szyfr plecakowy Chora-Rivesta, szyfr McEliece’a i szyfr probabilistyczny Bluma-Goldwasser’a.
Określenie: "silna kryptografia" oznacza szyfry trudne do złamania, np. stosuje się w tym celu długie klucze. Pod pojęciem silnej kryptografii rozumie się na ogół algorytm kryptograficzny z kluczem dłuższym od 128 bitów (w przypadku .algorytmów z kluczem prywatnym) i z kluczem dłuższym od 2048 bitów (w przypadku algorytmów z kluczem publicznym).
5.4. Bloki, blokowa funkcja szyfrująca i szyfry blokowe
Funkcję szyfrującą E : M K C wygodnie jest zdefiniować za pomocą różnowartościowej tzw. blokowej funkcji szyfrującej f : V1 n K V2 m . Blokowa funkcja szyfrująca f : V1 n K V2 m musi spełniać warunek: dla każdego k K funkcja f ( , k ) : V1 n V2 m jest różnowartościowa.
Z kolei funkcję deszyfrującą blokowej funkcji deszyfrującej D : C K M wygodnie jest zdefiniować za pomocą blokowej funkcji szyfrującej g : V2 m K V1 n .
Jeśli V1 V2 , to musimy mieć oczywiście m n by zachować różnowartościowość funkcji f ( k) : V n V m . Najczęściej jednak w praktyce V1 V2 i m n .
Blokową funkcję szyfrującą f : V1 n K V1 m nazywamy też przekształceniem szyfrującym dla tekstów jawnych o stałej długości.
Standardowym postępowaniem przy szyfrowaniu długich tekstów jawnych jest podział takiego tekstu na tzw. jednostki tekstu lub bloki czyli słowa o stałej długości n, które skonkatenowane dają wiadomość jawną m. Każdą jednostkę tekstu możemy szyfrować wówczas niezależnie za pomocą blokowej funkcji szyfrującej f : V1 n K V2 m . Jednostkom tekstu (lub jak mówimy czasem blokom) o długości n blokowa funkcja szyfrująca przyporządkowuje fragment szyfrogramu o długości m.
Dzielenie długiego tekstu wiadomości jawnej na krótsze jednostki tekstu jest bardzo wygodne, ale wymaga niekiedy przedłużenia wiadomości jawnej m tak by ta długość była równa r n dla pewnego r N .
Istota rzeczy: Szyfrujemy długie teksty jawne "po kawałku". Ten "kawałek" nazywamy blokiem lub jednostką tekstu. Powstaje rzecz jasna od razu problem a jeśli to się nie da podzielić na jednakowe „kawałki”. Rozwiązanie jest oczywiste. Dopełniamy tekst szyfrowany do wielokrotności długości bloku. Najczęściej jest to zgodne z pewnymi standardami.
5.5. Funkcja jednokierunkowa
Funkcja f : X Y , gdzie X, Y są dowolnymi niepustymi zbiorami, jest nazywana funkcją jednokierunkową (ang. one way function) jeśli:
- Dla każdego x X łatwo potrafimy obliczyć wartość f(x).
- Dla każdego y f ( X ) , znalezienie takiego x X , że y = f(x) jest praktycznie nierealizowalne (z uwagi na złożoność obliczeniową problemu).
Nie znamy żadnego dowodu na to, że jakaś funkcja jednokierunkowa w ogóle istnieje. Jesteśmy jednak przekonani, że funkcje jednokierunkowe w przyrodzie istnieją.
A oto trzy kandydatki na funkcje jednokierunkowe. Są to permutacje podzbioru liczb całkowitych Zp . Ogólnie rzecz biorąc jednak funkcja jednokierunkowa nie musi być różnowartościowa.
- Funkcja wykładnicza modulo p (ang. exponentation modulo p), gdzie p jest liczbą pierwszą. Niech p będzie liczbą pierwszą i niech będzie generatorem grupy multiplikatywnej Zp * . Funkcja wykładnicza modulo p f : Zp * Zp * jest zdefiniowana dla każdego x Zp * wzorem
f (x) x (mod p) ,
- lub wzorem f (x) x , jeśli mnożenie traktujemy jako mnożenie w Z p . Odwrócenie p Z funkcji f jest dokładnie problemem znalezienia wartości logarytmu dyskretnego w grupie multiplikatywnej Zp * (jeśli y f (x) , czyli y x , to x log y ). Nie są znane efektywne obliczeniowo algorytmy obliczania wartości logarytmu dyskretnego w grupie Zp * .
- Funkcja RSA (ang. RSA function). Niech p, q będą różnymi nieparzystymi liczbami pierwszymi i niech n p q . Niech ponadto NWD(e,( p 1)(q 1)) 1. Funkcja RSA zdefiniowana jest tak f : Zn Zn , f (x) xe (mod n) dla każdego x Zn .
- Funkcja Rabina. Niech n p q gdzie p i q są różnymi liczbami pierwszymi takimi, że p 3(mod 4) i q 3(mod 4) (n jest tzw. liczbą Bluma). Funkcja Rabina f zdefiniowana jest następująco
f : Qn x n f (x) x 2 (mod n) Q
gdzie Qn Zn jest tzw. zbiorem reszt kwadratowych modulo n. Można pokazać, że f jest permutacją zbioru Qn . Odwracanie funkcji f, czyli obliczanie pierwiastka kwadratowego w pierścieniu Zn jest dla dużych n praktycznie nierealizowalne, tzn. nie są znane żadne efektywne obliczeniowo algorytmy rozwiązujące ten problem, chyba, ze potrafimy rozłożyć liczbę n na czynniki pierwsze.
5.6. Szyfry przestawieniowe czyli szyfry permutacyjne
Szyfry permutacyjne (ang. transposition ciphers) należą do klasy szyfrów symetrycznych. Załóżmy, że alfabety V1 i V2 są takie, że V1 V2 V , a zbiór kluczy
K = {zbiór wszystkich permutacji zbioru <1,<i>r</i>>}
gdzie r jest ustaloną liczbą naturalną.
Niech m m1m2 ...mr M będzie dowolną wiadomością jawną. Dla i 1,2,..., r, mi V . Oznaczmy przez f blokową funkcję szyfrującą bloki (czyli jednostki tekstu) o długości r
f : V r K V r
a dokładniej
f : V r K (m, ) f (m, ) m (1) m (2) ...m (r ) c V r
Zatem wiadomości jawnej m o długości r przyporządkowujemy kryptogram o długości r ustawiając w pewnej kolejności (wyznaczonej przez permutację ) wyrazy ciągu stanowiącego wiadomość jawną.
Do zapamiętania: Kluczem jest permutacja zbioru <1,r>, gdzie r jest długością jednostki tekstu (bloku).
5.7. Szyfry podstawieniowe
Prosty szyfr podstawieniowy (ang. simple substitution cipher) to szyfr blokowy o długości bloku 1. Niech V1 będzie ustalonym alfabetem a f : V1 V2 funkcją różnowartościową (z reguły V1 V2 ). Jeśli wiadomość jawna m m1m2 ...mr , to szyfrogram c f (m1) f (m2 )... f (mr) . Funkcja f jest tu kluczem szyfrującym a f 1 kluczem deszyfrującym.
Klasycznym przykładem takiego szyfru jest szyfr Cezara,. który będzie omówiony dokładniej w dalszym ciągu podrozdziału.
Polialfabetyczny szyfr podstawieniowy (ang. polyalphabetic substitution cipher) to szyfr blokowy o długości bloku t. Niech V1 będzie ustalonym alfabetem a fi : V1 V2 , funkcją różnowartościową dla i 1,2,..., t (z reguły V1 V2 ). Jeśli wiadomość jawna m m1m2 ...mt to szyfrogram c f1 (m1 ) f2 (m2 )... ft (mt ) . Funkcja f1 f 2 ... ft jest tu kluczem szyfrującym a f1 1 f2 1 ... ft 1 kluczem deszyfrującym.
Przykład: Rozważmy dla ustalenia uwagi 26 literowy alfabet języka angielskiego V {<i>a</i>, <i>b</i>, <i>c</i>,..., <i>z</i>} . Klasycznym szyfrem Cezara nazywamy szyfr podstawieniowy, w którym V1 V2 V i funkcją definiującą podstawienia jest permutacja : V V , gdzie
Jeśli utożsamimy litery z liczbami z pierścienia Z26 na zasadzie a ~ 0, b~1, c~2, d~3, ..., y~24, z~25 , to widać, że:
(x) x 3(mod 26)
co odpowiada braniu jako wartości permutacji liczby – litery znajdującej się o 3 pozycje w prawo (modulo 26) w stosunku do liczby – litery argumentu.
Ogólnie rzecz biorąc, jeśli alfabet V jest n literowy, to możemy V utożsamić z Zn i zdefiniować permutację : V V wzorem:
(x) (x r)(mod n)
Kryptogram wiadomości jawnej m m1m2 m3 ...ms otrzymujemy więc jako
(m1 ) (m2 ) (m3 )... (ms )
Tak zdefiniowany szyfr, nazywamy szyfrem Cezara z przesunięciem r. Oczywiście r jest kluczem szyfrującym i deszyfrującym jednocześnie. Kluczem w szyfrze Cezara jest więc liczba naturalna ze zbioru Zn
Przykład: Załóżmy, że m jest licznością alfabetu jawnego V tzn. m cardV , np. m = 26 w przypadku języka angielskiego.
Identyfikujemy:
a 1 czyli identyfikujemy kolejne litery z kolejnymi liczbami
b 2 naturalnymi ze zbioru {1,2,...,26}
...
z 26
W przypadku alfabetu polskiego liter jest trochę więcej, bo mamy ą, ć, ę, ł, ń, ś , ó, ź. Klucz k k1 k2 ...kt V t , t N powtarzamy, jeśli trzeba, okresowo.
Szyfr Vigenere`a zadajemy tak: definiujemy odwzorowane fi : V V wzorem:
dla tekstu jawnego m m1m2 ...mk
Przykład: Okres klucza 5, klucz k = OKRES
m = ENCYKLOPEDIA TECHNIKI
k = OKRESOKRESOKRESOKRESO
c = TYUCC ZGJWXLRYXRSENCX
Oczywiście szyfr Cezara jest szczególnym przypadkiem szyfru Vigenere`a.
5.8. Szyfr produktowy lub złożeniowy
5.8 Szyfr produktowy lub złożeniowy
Produktem lub złożeniem 2 systemów kryptograficznych o blokowych przekształceniach szyfrujących f1 : V1 n K1 V2 m i f2 : V2 m K2 V3 r nazywamy system kryptograficzny zdefiniowany przez blokowe przekształcenie szyfrujące g : V1 n (K1 K2 ) V3 r zadane wzorem g(m,(k1 , k2 )) f 2 ( f1 (m, k1 ), k2 ) , gdzie m V1 n oraz k1 K1 , k2 K 2 . Szyfr zdefiniowany przez blokowe przekształcenie szyfrujące g : V n (K1 K2 ) V3 r nazywa się szyfrem produktowym (ang. product cipher) lub szyfrem złożeniowym.
Typowy szyfr złożeniowy tworzymy składając szyfr permutacyjny z szyfrem podstawieniowym, co tworzy tzw. rundę. Składanie rund z kolei jest ogólnym często stosowanym pomysłem na tworzenie szyfrów blokowych z kluczem symetrycznym. Składając rundy tworzymy nowy szyfr zwany siecią permutacyjno - podstawieniową (ang. substitution-permutation network).
Składając odpowiednio dobrane rundy tworzymy bardzo mocne szyfry. Tak skonstruowane są m.in. szyfry DES, IDEA i AES (Rijndael).
Uwaga: Nie zawsze (jakby się wydawało na pierwszy rzut oka) złożenie dwu systemów kryptograficznych prowadzi do istotnie nowego czy lepszego systemu kryptograficznego
Istota rzeczy: Szyfr produktowy tworzymy "stosując jeden po drugim" dwa lub większą ilość szyfrów. Na ogół prowadzi to do znacznie mocniejszych szyfrów ale nie jest to regułą.
5.9. Szyfry strumieniowe
Szyfry strumieniowe (ang. stream ciphers) są ważną klasą szyfrów z kluczem symetrycznym. Należą do klasy szyfrów blokowych, (z długością bloku =1, a więc oddzielnie szyfrujemy każdą literę tekstu jawnego). Istotą szyfru strumieniowego jest to, że przekształcenie szyfrujące może zmieniać się dla każdego szyfrowanego symbolu.
Szyfry strumieniowe są użyteczne w sytuacji, gdy
- wysokie jest prawdopodobieństwo błędów transmisji, ponieważ w szyfrach strumieniowych nie ma propagacji błędu;
- dane muszą być przesyłane symbol po symbolu (np. szyfrator lub deszyfrator nie ma pamięci).
Zdefiniujemy teraz szyfr strumieniowy formalnie. Niech V1 i V2 będą alfabetami. Punktem wyjścia jest blokowa funkcja szyfrująca (szyfrująca bloki o długości 1) E : K V1 V2 i blokowa funkcja deszyfrująca (deszyfrująca bloki o długości 1) spełniające warunek:
Tworzymy ciąg (k1) , gdzie ki K dla każdego i N . Nazywamy ten ciąg strumieniem kluczy. Definiujemy nowy system kryptograficzny (V1 ,V2 ,
) następująco. V1 i V2 są wprowadzonymi już alfabetami,
{(<i>k</i><sub><i>i</i> </sub>)<sup><span class="equation" style="width:100%;;;;">



((ki )
, * m1m2 ...mr ) E(k1, m1)E(k2 , m2 )...E(kr , mr ) c1c2 ...cr
:
V2* V1* funkcją deszyfrującą, dokładniej
zadane jest wzorem:
((ki )
c1c2 ...cr ) D(k1, c1)D(k2 , c2 )...D(kr , cr ) m1m2 ...mr
Taki system kryptograficzny nazywamy szyfrem strumieniowym.
5.10. Szyfry idealne
Szyfr Vernama to inaczej szyfr idealny lub szyfr z kluczem jednokrotnym (ang. one pad cipher).
Idealnym z kryptograficznego punktu widzenia jest szyfr z jednokrotnym kluczem losowym generowanym w ciągu prób Bernoulliego. Szyfr z kluczem jednokrotnym charakteryzuje się bezpieczeństwem idealnym, ponieważ przy przechwyceniu szyfrogramu c o określonej długości n prawdopodobieństwo warunkowe wystąpienia danego tekstu jawnego m (długości n) pod warunkiem odebrania c nie zależy od m.
Szczególnym przypadkiem szyfru jednokrotnego jest szyfr Vernama, przekształcający ciągi zerojedynkowe na ciągi zerojedynkowe. Szyfr ten został wynaleziony w 1917 roku przez dwóch Amerykanów: Gilberta S. Vernama (pracującego dla American Telephone and Telegraph Company, w skrócie AT&T) i Josepha O. Mauborgne ( z US Army Signal Corps.)
Rys. 1. Szyfr Vernama. Szyfrowanie: ci mi ki . Deszyfrowanie: mi ci ki
Algorytm deszyfrowania jest następujący: mi ci ki . Jego poprawność wynika z łączności sumy modulo 2 i faktu, że ki ki 0.
Wadą szyfru Vernama jest to, że klucz jest tak długi, jak wiadomość jawna. Jednak w pewnych zastosowaniach użycie klucza jednokrotnego jest uzasadnione (dyplomacja, wojsko).
W praktyce często zastępuje się ciąg losowy k k1 k2 ...kt {0,1}* ciągiem pseudolosowym. Np. mamy do zaszyfrowania tekst jawny (mi) o długości N0 , generujemy pseudolosowy ciąg bitów (ki )
i szyfrujemy tekst jawny jako (ci)
, gdzie ci ki ai dla każdego i i1 i=1,2,..., N0 .
Szyfr Vernama jest szyfrem strumieniowym. Można go uogólnić z przypadku binarnego na dowolny alfabet skończony zastępując sumę modulo 2 sumą modulo n.
5.11. Algorytm szyfrowania szyfrem RSA
Szyfr RSA jest typowym, najczęściej stosowanym szyfrem z kluczem publicznym. Nazwa RSA pochodzi od nazwisk 3 profesorów z MIT (Massachussets Institute of Technology) R.L.Rivesta, A.Shamira i L.M.Adlemana, którzy w roku 1978 opracowali koncepcję tego szyfru. Szyfr RSA uważany jest za jeden z najbezpieczniejszych, najpewniejszych szyfrów. Używany jest do szyfrowania wiadomości, do tworzenia podpisów cyfrowych oraz dystrybucji kluczy kryptograficznych.
Opiszemy teraz, jak tworzymy system kryptograficzny RSA. Potrzebne nam będzie przy omawianiu szyfru RSA pojęcie funkcji Eulera. Dla danej liczby naturalnej n symbolem (n) oznaczamy liczbę liczb naturalnych względnie pierwszych z n i mniejszych od n, tzn. (n) card{<i>m</i> <font face="Symbol, serif"></font> <i>N</i>; <i>m</i> <font face="Symbol, serif"></font> <i>n</i>, <i>NWD</i>(<i>m</i>, <i>n</i>) <font face="Symbol, serif"></font> 1}. W ten sposób definiujemy funkcję : N N , która nazywa się funkcją Eulera. Łatwo można wykazać, że jeśli p jest liczbą pierwszą to ( p) p 1 i dla każdego k N , mamy
Jeśli m i n są względnie pierwszymi liczbami naturalnymi to mamy
Z (1) i (2) dostajemy, że jeśli n p1k1 p2k2 ... prkr , gdzie p1, p2 ,..., pr są parami różnymi liczbami pierwszymi to
(n) p1k1 1 ( p1 1) p2k2 1 ( p2 1) ... prkr 1 ( pr 1) (3)
Wybieramy teraz dwie duże liczby pierwsze p i q obliczamy iloczyn n=pq oraz wartość funkcji Eulera (n) ( p 1)(q 1) . Wybieramy następnie losowo dowolną taką liczbę (n) d Z(n) , dla której istnieje element odwrotny e d 1 w pierścieniu Z (n) czyli takie e Z (n) by d (n) e 1 w pierścieniu Z (n) (oczywiście jest to równoważne temu by znaleźć takie e Z (n) , żeby d e 1 (mod (n)) ). Warunkiem koniecznym i dostatecznym na to by istniał taki element odwrotny jest by NWD( (n), d ) 1 . Obliczamy e d 1 i ujawniamy parę uporządkowaną (e,n) jako klucz publiczny. Kluczem prywatnym będzie para uporządkowana (d,n).
Tekst szyfrowany, czyli tekst jawny m V1 np. "ala ma kota" zamieniamy na liczbę całkowitą x np. 10982398.
Podnosimy tę liczbę do ustalonej potęgi e w pierścieniu Zn ((e,n) jest znane i stanowi klucz publiczny) lub co na jedno wychodzi podnosimy tę liczbę do potęgi e w pierścieniu Z i bierzemy resztę z dzielenia tej liczby przez liczbę n. Tę resztę wysyłamy do adresata.
c xe
Adresat podnosi (w pierścieniu Zn ) uzyskaną liczbę do pewnej potęgi d będącej kluczem tajnym czyli kluczem prywatnym uzyskując wiadomość jawną x.
x cd
Wykażemy teraz poprawność RSA. Skorzystamy z następującego lematu będącego konsekwencją chińskiego twierdzenia o resztach.
Lemat: Niech m1 , m2 ,..., mn N będą parami względnie pierwsze (tzn. NWD(mi , m j ) 1 dla każdego i, j 1, n ,i j ) i mi 2 dla każdego i=1,2,...,n oraz niech a, b będą dowolnymi liczbami całkowitymi czyli a, b Z .
a b(mod mi ) dla każdego i=1,2,...,n wtedy i tylko wtedy gdy a b(mod m1 m2 ... mn )
Dowód poprawności algorytmu RSA: Z założenia ed 1 (mod (n)), zatem istnieje takie k Z , że
Załóżmy teraz, że wiadomość jawna m Zn jest względnie pierwsza z p, czyli NWD(m, p) 1 . Z małego twierdzenia Fermata mamy wówczas:
Podnosząc obie strony kongruencji (**) do potęgi k (q 1) i mnożąc przez m dostajemy:
m1k ( p1)(q1) m(mod p)
a więc korzystając z (*) mamy:
med m(mod p) .
Jeżeli NWD(m, p) 1 , to NWD(m, p) p . Zatem również mamy wówczas:
Istotnie, ponieważ m jest podzielna przez p, więc med 0(mod p) i m 0(mod p), i kongruencja (***) zachodzi.
Zatem niezależnie od tego czy NWD(m, p) 1 , czy NWD(m, p) 1 , zawsze mamy
med m(mod p) .
Podobnie możemy rozumować dla liczby pierwszej q otrzymując
med m(mod q)
Z lematu dostajemy ostatecznie, że med m(mod pq) .
Probabilistyczne uzasadnienie algorytmu RSA: Warto zwrócić uwagę na to, że nie wymagamy w założeniach powyższego twierdzenia by tak jak w założeniach twierdzenia Eulera. Próba uzasadniania kongruencji NWD(a, n) 1, med m(mod pq) twierdzeniem Eulera byłaby więc zręcznym korzystaniem z nieuwagi Czytelnika.
Jednak przy n p1 p2 (gdzie czynniki są liczbami pierwszymi) i założeniu równomiernego rozkładu prawdopodobieństwa na zbiorze Zn prawdopodobieństwo tego, że NWD(a, n) 1 maleje do 0 wraz ze wzrostem p1 i p2 . Istotnie z definicji funkcji Eulera mamy, że:
P({<i>a </i><font face="Symbol, serif"></font> <i>Z</i> ; <i>NWD</i>(<i>a</i>, <i>n</i>) <font face="Symbol, serif"></font> 1})
a więc P({<i>a</i> <font face="Symbol, serif"></font> <i>Z</i><sub><i>n</i> </sub>; <i>NWD</i>(<i>a</i>, <i>n</i>) <font face="Symbol, serif"></font> 1}) 0 jeśli p1 i p2
zatem sytuacja, że NWD(a, n) 1 nie zachodzi staje się bardzo mało prawdopodobna.
Uwagi końcowe o algorytmie RSA: Szyfr RSA jako algorytm z kluczem publicznym może być wykorzystywany zarówno do zwykłego szyfrowania jak również do dystrybucji tzw. kluczy sesyjnych oraz realizacji algorytmów podpisów cyfrowych.
Bezpieczeństwo szyfru RSA jest zależne od tego czy istnieje efektywny algorytm rozkładu dużej liczby n na czynniki pierwsze (dotychczas nie jest znany taki efektywny algorytm). Dokładniej, gdyby taki algorytm istniał to moglibyśmy łatwo obliczyć wartość funkcji Eulera (n) , n jest ogólnie znanym parametrem sytemu kryptograficznego. Odtworzenie klucza prywatnego z klucza publicznego sprowadzałoby się w tej sytuacji do znalezienia elementu odwrotnego do klucza publicznego w pierścieniu Z (n) co sprowadza się z kolei do realizacji rozszerzonego algorytmu Euklidesa (istnieją szybkie efektywne wersje tego algorytmu np. algorytm Stein'a).
Główną wadą algorytmu RSA jest jego znacznie większa złożoność obliczeniowa w porównaniu z innymi algorytmami szyfrowania jak np. algorytmem DES. Okazuje się to specjalnie kłopotliwe, gdy zależy nam na szyfrowaniu w czasie rzeczywistym, np. gdy szyfrujemy próbki sygnału mowy w systemie telekomunikacyjnym.
5.12. Funkcje skrótu
Funkcje skrótu (ang. hash function) nazywa się również jednokierunkowymi funkcjami skrótu, funkcja skrótu f : M {0,1}n (gdzie M jest zbiorem wiadomości a n jest ustalone) powinna być bowiem jednokierunkowa tzn. jeśli powinno być niemożliwe obliczenie x. f (x) y i znamy y, to praktycznie
Plik, tekst, wiadomość m o dowolnej długości „obłożona” funkcja skrótu daje ciąg bitów o ustalonej długości n (np.: 20 bajtów, czyli 160 bitów).
Jeśli znamy wartość funkcji skrótu f (m) i samą funkcję skrótu i dysponujemy parą (m', f (m)) , to możemy sprawdzić integralność danych, czyli sprawdzić czy m m' obliczając dla m' wartość f (m') . Jeśli f (m) f (m') , to przyjmujemy, że m m' (choć tak być nie musi), czyli stwierdzamy, że „integralność danych jest zachowana”.
5.13. Uwagi końcowe
Realizacja systemów kryptograficznych: Systemy kryptograficzne można realizować programowo, sprzętowo lub w sposób mieszany czyli częściowo sprzętowo częściowo programowo. Na ogół uważa się, że im więcej specjalizowanego sprzętu , tym system jest bezpieczniejszy. Współczesne profesjonalne systemy kryptograficzne są z reguły realizowane sprzętowo, wewnątrz specjalizowanych układów szyfrujących. Są to na ogół układy scalone typu ASIC (Application Specific Integrated Circuit) lub układy typu PLD (Programmable Logic Design) . Zaszycie algorytmu kryptograficznego w układzie scalonym typu ASIC lub PLD nie stanowi współcześnie większego problemu.
Realizując systemy kryptograficzne trzeba zwracać uwagę na zastrzeżenia eksportowe i patenty np. szyfr z kluczem publicznym RSA jest opatentowany w obu wersjach podstawowej i uogólnionej.
Probabilistyczne systemy kryptograficzne: Jak uwzględnić losowość w szyfrowaniu. W pewnych algorytmach kryptograficznych takich jak np. algorytm z kluczem publicznym ElGamela (z uwagi na losowość „wmontowaną” w algorytm), ustalonej jednostce tekstu jawnego mogą odpowiadać różne szyfrogramy. Tak więc jeśli G jest zbiorem skończonym lub przeliczalnym a (G,2G , P) przestrzenią probabilistyczną, to blokowe przekształcenie szyfrujące definiujemy jako f :V1l1 G V2 l2 dodając warunek, że
f (, g) :V1l1 V2 l2 jest funkcją różnowartościową
Informacja o wybranym w losowy sposób g G albo nie jest istotna przy deszyfrowaniu (alternatywnie np. możemy ustalonej jednostce tekstu przyporządkowywać różne szyfrogramy) albo jest zawarta w samym szyfrogramie, wbudowana w szyfrogram (tak jest np. w systemie kryptograficznym ElGamala).
Ogólnie rzecz biorąc funkcja E z definicji systemu kryptograficznego może być funkcją E : M K H C , gdzie H jest dowolnym skończonym zbiorem a (H ,2H , P) jest pewną przestrzenią probabilistyczną.
Istota rzeczy: Fakt losowej zmienności kryptogramu dla tej samej wiadomości jawnej bez wątpienia utrudnia kryptoanalizę.
6. Kompresja informacji
Kompresja informacji
6.1. Wstęp
Kompresja informacji to inaczej kompresja danych. Dokładniej, konkretna metoda kompresji danych to zawsze 2 algorytmy:
- algorytm kompresji danych
- algorytm rekonstrukcji danych
Kompresję dzielimy na 2 kategorie: kompresję stratną i kompresję bezstratną. Jeśli dane poddawane kompresji potrafimy odtworzyć, zrekonstruować w dokładnie takiej samej postaci jak dane poddawane kompresji to kompresję nazywamy bezstratną. W przeciwnym razie kompresja jest kompresją stratną.
Rodzaj kompresji zależy również od danych poddawanych kompresji. Tak więc mamy:
- kompresję plików tekstowych
- kompresję plików dźwiękowych (kompresja audio)
- kompresję obrazów statycznych
- kompresję sekwencji video czyli obrazów dynamicznych (obrazów ruchomych)
Model probabilistyczny generacji danych przez źródło danych: W zagadnieniach związanych z kompresją danych bardzo ważny jest model matematyczny generacji danych. Załóżmy, że obiektami kodowanymi są litery (symbole) z pewnego alfabetu V1 {<i>a</i><sub>1 </sub>, <i>a</i><sub>2 </sub>,..., <i>a</i><sub><i>n </i></sub>} i znamy prawdopodobieństwo pojawienia się każdej litery tzn. podane są prawdopodobieństwa P(ai ) pi dla i 1, 2,..., n . Jeśli przyjmiemy, że słowo nad alfabetem V1 {<i>a</i><sub>1 </sub>, <i>a</i><sub>2 </sub>,..., <i>a</i><sub><i>n </i></sub>} generowane jest przez źródło danych jako ciąg niezależnych k losowań liter (podobnie jak w ciągu k doświadczeń Bernoulliego) to otrzymujemy w naturalny rozkład prawdopodobieństwa na zbiorze V1 k wszystkich słów o długości k .
Ważnym parametrem charakteryzującym źródło danych jest entropia źródła. Jest to liczba definiowana wzorem:
Shannon udowodnił, że najlepszym rezultatem (przy kodowaniu binarnym) jaki można uzyskać za pomocą algorytmu kompresji bezstratnej jest taki algorytm kodowania (taki kod binarny k : V1 {0,1} ) by średnia bitów wykorzystanych do kodowania liter z alfabetu była równa entropii źródła.
Podstawowy pomysł na kompresję jest następujący. Obiekty kodowane występujące częściej (czyli o większym prawdopodobieństwie wystąpienia) powinny być kodowane krótszym słowem a obiekty występujące rzadziej (o mniejszym prawdopodobieństwie) dłuższym słowem. Takie rozwiązanie wykorzystywane jest w znanym każdemu harcerzowi kodzie zwanym „alfabetem Morse’a”. Na przykład litera „e” często pojawiająca się w tekstach angielskich jest kodowana za pomocą jednej kropki (słowo o długości 1 nad alfabetem V2 {<font face="Symbol, serif">□</font>, <font face="Symbol, serif"></font>} ).
Jednoznaczna dekodowalność: Dosyć ważną użyteczną własnością kodu jest jego jednoznaczna dekodowalność. Oto definicja tej własności. Niech V1 {<i>a</i><sub>1 </sub>, <i>a</i><sub>2 </sub>,..., <i>a</i><sub><i>n</i> </sub>} . Kod k : V1 V nazywamy jednoznacznie dekodowalnym jeżeli każdy ciąg słów kodowych daje 2 się odkodować w dokładnie jeden sposób tzn. nie ma dwóch różnych słów 12 ...m , 1 2 ...m nad alfabetem V1 dających (po zakodowaniu każdej litery z V1 i konkatenacji uzyskanych słów kodowych) to samo słowo tzn. nie może być
k(1 )k(2 )...k (m ) k(1 )k(2 )...k(m )
Prefiks i sufiks. Niech V będzie dowolnym ustalonym alfabetem oraz a1a2 ...an słowem nad alfabetem V, wówczas każde słowo a1a2 ...ak , gdzie 1 k n nazywamy prefiksem słowa a1a2 ...an a każde słowo ak a2 ...an , gdzie 1 k n nazywamy sufiksem słowa a1a2 ...an . Jednocześnie słowo puste jest sufiksem i prefiksem każdego słowa.
Kod prefiksowy. Kod prefiksowy to kod w którym żadne słowo kodowe nie jest prefiksem innego słowa kodowego.
Istota rzeczy jest tu taka. Załóżmy, ze odbieramy słowo kodowe litera po literze. W kodzie prefiksowym patrząc na słowo kodowe nie mamy wątpliwości czy to już koniec słowa kodowego czy być może tylko początkowy fragment innego słowa kodowego.
Kody prefiksowe są w oczywisty sposób jednoznacznie dekodowalne.
Jeśli stosujemy kody o zmiennej długości słowa kodowego to ważnym pojęciem jest średnia długość słowa kodowego. W przypadku słów binarnych średnią długość słowa kodowego nazywamy średnią bitową kodu. Dokładniej, niech źródło danych będzie takie, 2 że V1 {<i>a</i><sub>1 </sub>, <i>a</i><sub>2 </sub>,..., <i>a</i><sub><i>n</i> </sub>} oraz prawdopodobieństwa P(ai ) pi dla i 1, 2,..., n . Średnią długością kodu k : V1 V2 nazywamy liczbę
gdzie l( ) oznacza długość słowa
Twierdzenie (warunek konieczny dekodowalności kodu, nierówność Krafta-McMillana):
Jeśli K jest zbiorem słów kodowych (binarnych) o długościach l1 , l2 ,..., ln N i kod k : V1 K jest jednoznacznie dekodowany to spełniony jest następujący warunek n tzw. nierówność Krafta-McMillana
Twierdzenie (warunek wystarczający istnienia kodu prefiksowego):
Jeśli l1 , l2 ,..., ln N spełniają nierówność
to można znaleźć dla n elementowego alfabetu V1 {<i>a</i><sub>1 </sub>, <i>a</i><sub>2 </sub>,..., <i>a</i><sub><i>n </i></sub>} kod prefiksowy k : V1 {0,1} o długościach słów kodowych wynoszących l , l ,..., ln .
6.2. Kod Huffmana
Kod Huffmana to kod ale jednocześnie najbardziej znana, powszechnie stosowana, efektywna metoda kompresji danych. W zależności od typu kompresowanego pliku, osiąga się oszczędności w objętości danych od 20% do nawet 90%. Kod Huffmana to kod prefiksowy spełniający następujące warunki:
- obiektom kodowanym występującym częściej (mającym większe prawdopodobieństwo wystąpienia) odpowiadają krótsze słowa niż obiektom występującym rzadziej (mającym mniejsze prawdopodobieństwo wystąpienia);
- dwa najrzadziej występujące (najmniej prawdopodobne) mają słowa tej samej długości.
Będziemy dalej zakładać, że obiektami kodowanymi są litery (symbole) z pewnego alfabetu V1 {a1 , a2 ,..., an } i znamy prawdopodobieństwo pojawienia się każdej litery tzn. podane są prawdopodobieństwa binarnego {0,1}. P(ai ) pi dla i 1, 2,..., n . Ponadto używamy jako alfabetu V2 zbioru
Algorytm kodowania Huffmana czyli algorytm Huffmana jest następujący:
- dla każdej litery tworzymy drzewo złożone tylko z korzenia i ustawiamy te drzewa w malejącym porządku prawdopodobieństwa użycia danej litery
- while (istnieją przynajmniej 2 drzewa)
Z drzew t1 i t2 o najmniejszych prawdopodobieństwach p1 i p2 tworzymy nowe drzewo zawierające w korzeniu prawdopodobieństwo p1 p2 i mające t1 i t2 jako lewe i prawe poddrzewo. Przypisujemy 0 każdej lewej krawędzi i 1 każdej prawej krawędzi; - Tworzymy słowo kodowe dla każdej litery przechodząc drzewo od korzenia do liścia zawierającego prawdopodobieństwo stowarzyszone z tą literą i łącząc napotkane 0 i 1;
Przykład: Załóżmy, że chcemy zbudować kod Huffmana dla 4 literowego alfabetu V1 {a1 , a2 , a3 , a4 } przy czym P(a1 ) 0,1 , P(a2 ) 0, 2 , P(a3 ) 0, 3 , P(a4 ) 0, 4 .
Postępując zgodnie z podanym algorytmem Huffmana tworzymy drzewo pokazane na Rys. 1. Odczytany z tego drzewa kod Huffmana jest następujący:
a1 000 , a2 001
a3 01, a4 1.
Rys. 1. Tworzenie kodu Huffmana.
Metoda kompresji plików tekstowych oparta na wykorzystaniu kodu Huffmana jest bardzo prosta. Kodujemy kolejne litery wchodzące w skład tekstu za pomocą kodu Huffmana i tak uzyskane ciągi konkatenujemy. Warto przy tym przypomnieć, że kod Huffmana jest kodem prefiksowym, a więc jednoznacznie dekodowalnym.
6.3. Algorytmy słownikowe
Jak już wspomnieliśmy w zagadnieniach związanych z kompresją danych bardzo ważny jest model matematyczny generacji danych. Algorytmy słownikowe kompresji należą do metod kompresji wykorzystujących cechy strukturalne występujące w danych. Algorytmy słownikowe dzielimy na dwie grupy:
- algorytmy ze statycznym słownikiem
- algorytmy z dynamicznym słownikiem
Istotę algorytmów słownikowych wyjaśnia następujący przykład. Załóżmy, ze dany jest tekst złożony ze słów 4 literowych i każde słowo składa się z czterech znaków. Alfabet, w którym zapisujemy tekst, składa się z 26 małych liter i znaków przestankowych, co daje w sumie 32 znaki (5 bitów na znak). Jeśli wybierzemy ze słów 4 znakowych najczęściej spotykane słowa (np. 256 słów) i umieścimy je w słowniku, to możemy postępować tak:
Analizujemy słowa 4 znakowe (dzielimy cały tekst kompresowany na 4 znakowe bloki) jeśli dane słowo znajduje się w słowniku to przesyłamy tylko jeden bit stwierdzający fakt znalezienia słowa w słowniku i pozycję słowa w słowniku (8 bitów). W sumie więc 9 bitów. Jeśli nie znaleźliśmy słowa w słowniku to 4 znakowe słowo kodujemy tak: jeden bit stwierdzający że słowo jest spoza słownika oraz 4 słowa 5 bitowe kodujące znaki z naszego alfabetu (w sumie 21 bitów).
Efektywność takiego kodowania będzie zależeć od tego, jaki odsetek słów analizowanych znajduje się słowniku.
Jeśli prawdopodobieństwo wystąpienia wzorca ze słownika jest równe p, to średnia liczba bitów poświęcanych na zapis 4 znakowego fragmentu tekstu wyraża się następująco:
L 9 p 21(1 p) 21 12 p
Gdybyśmy nie stosowali powyższego algorytmu to każdy 4 znakowy fragment tekstu zabierałby 20 bitów.
Oczywiście w powyższym schemacie kodowania efekt będzie tym lepszy, im większe jest prawdopodobieństwo p znalezienia wzorca w słowniku.
Popularne pakiety kompresji danych takie jak PKZip, Zip, gzip i ARJ używają algorytmów słownikowych opartych na LZ77. Inne znane słownikowe algorytmy kompresji to m.in. algorytmy LZ 77, LZ 78 (litery LZ są tu skrótem od nazwisk Lempel, Ziv), EZW itd. Powszechnie stosowanym algorytmem kompresji jest również algorytm LZW (Lempel-Ziv- Welch) należący do klasy algorytmów typu LZ 78.
Znane z systemu operacyjnego Unix polecenie compress, specyfikacja V.42.bis wykorzystywana w modemach oraz standard GIF (Graphics Interchange Format) są kolejnymi przykładami zastosowania metod słownikowych kompresji.