3DES - algorytm szyfrowania danych

Tu możesz pisać o swoich problemach z pisaniem programów w języku C dla AVR.
Awatar użytkownika
gaweł
Expert
Expert
Posty: 911
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

3DES - algorytm szyfrowania danych

Postautor: gaweł » sobota 06 maja 2017, 04:58

Szyfrowanie danych „schodzi” pod strzechy. To co do niedawna było domeną wielkich komputerów, staje się dostępne w środowiskach mikrokontrolerów, nawet tych o niekoniecznie dużych mocach obliczeniowych. Skomplikowane rozwiązania, często wymagają zaawansowanych narzędzi (jak przykładowo filozofia komponentów w języku C++). Nie zawsze jednak jest to konieczne. Przykładem jest zaprezentowana poniżej implementacja szyfrowania danych z zastosowaniem algorytmu 3DES (algorytm 3DES jest rozszerzeniem algorytmu DES).
Generalnie w szyfrowaniu i deszyfrowaniu występuje pojęcie klucza. W przypadku szyfrowania jest to dodatkowa informacja, która jest użyta do zaszyfrowania danych, czyli zastąpienia danych, które podlegają ochronie innymi danymi sprawiającymi wrażenie danych przypadkowych. W deszyfrowaniu potrzebny jest właściwy klucz, bez znajomości którego odtworzenie oryginalnych danych jest niemożliwe. Klucz do zaszyfrowania i odszyfrowania może być identyczny, wtedy mamy przypadek klucza symetrycznego.
DES jest szyfrem blokowym z blokami o długości 64 bitów (czyli 8 bajtów). Do szyfrowania i deszyfrowania danych wykorzystywanych jest 56 bitów klucza (klucz jest symetryczny), który zapisany jest w postaci 64 bitowego ciągu, w którym co 8 bit jest bitem kontrolnym i może służyć do kontroli parzystości. W szyfrowaniu tym algorytmem dane jawne są dzielone na bloki 64-bitowe (po 8 bajtów), które są później przetwarzane. Algorytm 3DES jest modyfikacją algorytmu podstawowego DES. Wykorzystuje ona do szyfrowania i deszyfrowania trzy klucze. Najpierw wiadomość jest szyfrowana pierwszym kluczem, następnie deszyfrowana drugim i ponownie szyfrowana trzecim kluczem. Koncepcyjnie można uznać, że algorytm 3DES jest algorytmem DES z kluczem trzy razy większym, czyli ma 192-bitowy klucz (jako 24 bajty).
W przypadku mikrokontrolerów AVR algorytmu 3DES można użyć przykładowo do ochrony zawartości pamięci EEPROM lub do szyfrowania transmisji poprzez UART. Poniższy program prezentuje „obróbkę” bloku danych polegającą na:
  • wyświetleniu zawartości bloku danych przed zaszyfrowaniem,
  • wyświetlenie zawartości bloku danych po zaszyfrowaniu,
  • wyświetlenie zawartości bloku danych po odszyfrowaniu.
Każde wyświetlenie jest realizowane jako wysłanie przez UART tekstu do wyświetlenia na terminalu (emulatorze terminala). Tekst jest wyświetlany w formie znaków (dla znaków kontrolnych wyświetlany jest znak kropki) oraz ten sam tekst w formie liczb 8-bitowych.

Środowisko sprzętowe eksperymentu jest następujące:
3des_1.png
3des_2.png
3des_3.png
3des_5.jpg


Tekst programu prezentacyjnego:

Kod: Zaznacz cały

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>

#include "descrypto.h"

#define LEDPort                         PORTB
#define LEDDirPort                      DDRB

#define Cr                              0x0D
#define Lf                              0x0A


#define USART_Speed                     47   // USART BAUD Rate Generator fosc=7.3728 MHz , 9600 bps

#define StandardTimerDelay              24

#define nop() __asm__ __volatile__ ("nop")


#define IdleState                       0
#define BusyState                       1

#define TXDBuffSize                     16
#define RXDBuffSize                     16

typedef struct { uint8_t CycFr ;
                 uint8_t CycTo ;
                 uint8_t State ;
                 uint8_t Buffer [ TXDBuffSize ] ;
               } TXDRecT ;
typedef struct { uint8_t CycFr ;
                 uint8_t CycTo ;
                 uint8_t Buffer [ RXDBuffSize ] ;
               } RXDRecT ;


static volatile TXDRecT TXDRec ;
static volatile RXDRecT RXDRec ;
static uint8_t TimerCounter ;
static uint8_t LEDData ;
static DES3InstanceType DES3Instance ;
static uint8_t CryptKey [ 24 ] ;


static uint8_t HelloTxt [ ] PROGMEM                = "\r\nHello:\r\nPrezentacja szyfrowania algorytmem 3DES\r\n" ;



SIGNAL ( TIMER0_OVF_vect )
{
  /*-------------------------------------------------------------------------*/
  TimerCounter ++ ;
  if ( TimerCounter > StandardTimerDelay )
  {
    TimerCounter = 0 ;
    LEDData ++ ;
    LEDPort = LEDData ;
  } /* if */ ;
} /* TIMER0_OVF_vect */


SIGNAL ( SIG_UART_RECV )
{
  uint8_t RecVCh ;
  /*-------------------------------------------------------------------------*/
  RecVCh = UDR ;
  RXDRec . Buffer [ RXDRec . CycTo ] = RecVCh ;
  RXDRec . CycTo ++ ;
  if ( RXDRec . CycTo >= RXDBuffSize )
    RXDRec . CycTo = 0 ;
} /* SIG_USART_RECV */


SIGNAL ( SIG_UART_TRANS )
{
  /*-------------------------------------------------------------------------*/
  if ( TXDRec . CycFr == TXDRec . CycTo )
  {
    TXDRec . State = IdleState ;
  } /* if ... */
  else
  {
    UDR = TXDRec . Buffer [ TXDRec . CycFr ] ;
    TXDRec . CycFr ++ ;
    if ( TXDRec . CycFr >= TXDBuffSize )
      TXDRec . CycFr = 0 ;
  } /* if ... else */ ;
} /* SIG_USART_TRANS */


static void USARTSoftwareInit ( void )
{
  /*-------------------------------------------------------------------------*/
  RXDRec . CycFr = 0 ;
  RXDRec . CycTo = 0 ;
  TXDRec . CycFr = 0 ;
  TXDRec . CycTo = 0 ;
  TXDRec . State = IdleState ;
} /* USARTSoftwareInit */


static void SendSerial ( uint8_t SerialCh )
{
  uint8_t NextInx ;
  /*-------------------------------------------------------------------------*/
  cli ( ) ;
  if ( TXDRec . State == IdleState )
  {
    TXDRec . State = BusyState ;
    UDR = SerialCh ;
    sei ( ) ;
  } /* if ... */
  else
  {
    for ( ; ; )
    {
      NextInx = TXDRec . CycTo + 1 ;
      if ( NextInx >= TXDBuffSize )
        NextInx = 0 ;
      if ( NextInx != TXDRec . CycFr )
        break ;
      sei ( ) ;
      nop ( ) ;
      nop ( ) ;
      cli ( ) ;
    } /* for */ ;
    TXDRec . Buffer [ TXDRec . CycTo ] = SerialCh ;
    TXDRec . CycTo ++ ;
    if ( TXDRec . CycTo >= TXDBuffSize )
      TXDRec . CycTo = 0 ;
    sei ( ) ;
  } /* if ... else */ ;
} /* SendSerial */


static void SendSerialFlashTxt ( uint16_t String )
{
  uint8_t Data ;
  /*-------------------------------------------------------------------------*/
  for ( ; ; )
  {
    Data = pgm_read_byte_near ( String ) ;
    if ( Data )
      SendSerial ( Data ) ;
    else
      break ;
    String ++ ;
  } /* for */ ;
} /* SendSerialFlashTxt */


static void HardwareInit ( void )
{
  /*-------------------------------------------------------------------------*/
  TIMSK = ( 1 << TOIE0 ) ;              // Timer/counter Interrupt Mask
                                        //      TOIE0: Timer/Counter0 Overflow Interrupt Enable
  TCCR0 = ( 1 << CS02 ) ;               // Timer/Counter Control Register
                                        //      CS00: Clock Select
                                        //      CS01: Clock Select
                                        //      CS02: Clock Select
  UCSRA = 0 ;
  UCSRB = ( 1 << RXCIE ) | ( 1 << TXCIE ) | ( 1 << RXEN ) | ( 1 << TXEN ) ;
                                        // USART Control and Status Register B
                                        //      TXEN : Transmitter Enable
                                        //      RXEN : Receiver Enable
                                        //      TXCIE : TX Complete Interrupt Enable
                                        //      RXCIE : RX Complete Interrupt Enable
  UCSRC = ( 1 << URSEL ) | ( 1 << UCSZ1 ) | ( 1 << UCSZ0 ) ;
                                        // UART Control and Status Register C
                                        //      UCSZ0 : USART Character Size
                                        //      UCSZ1 : USART Character Size
                                        //      URSEL : USART Register Select
  UBRRH = ( uint8_t ) ( ( USART_Speed >> 8 ) & 0xFF ) ;
  UBRRL = ( uint8_t ) ( USART_Speed & 0xFF ) ;
} /* HardwareInit */


static void EnvironmentInit ( void )
{
  /*-------------------------------------------------------------------------*/
  LEDDirPort = 0xFF ;
  LEDPort = 0 ;
} /* EnvironmentInit */


static void SoftwareInit ( void )
{
  /*-------------------------------------------------------------------------*/
  USARTSoftwareInit ( ) ;
  TimerCounter = 0 ;
  LEDData = 0 ;
  CryptKey [  0 ] = 'K' ;
  CryptKey [  1 ] = 'l' ;
  CryptKey [  2 ] = 'u' ;
  CryptKey [  3 ] = 'c' ;
  CryptKey [  4 ] = 'z' ;
  CryptKey [  5 ] = ' ' ;
  CryptKey [  6 ] = 'd' ;
  CryptKey [  7 ] = 'o' ;
  CryptKey [  8 ] = ' ' ;
  CryptKey [  9 ] = 's' ;
  CryptKey [ 10 ] = 'y' ;
  CryptKey [ 11 ] = 'f' ;
  CryptKey [ 12 ] = 'r' ;
  CryptKey [ 13 ] = 'o' ;
  CryptKey [ 14 ] = 'w' ;
  CryptKey [ 15 ] = 'a' ;
  CryptKey [ 16 ] = 'n' ;
  CryptKey [ 17 ] = 'i' ;
  CryptKey [ 18 ] = 'a' ;
  CryptKey [ 19 ] = '.' ;
  CryptKey [ 20 ] = 0 ;
  CryptKey [ 21 ] = 0 ;
  CryptKey [ 22 ] = 0 ;
  CryptKey [ 23 ] = 0 ;
} /* SoftwareInit */


void WordToStr ( uint8_t * Cnv ,
                 uint8_t   ConvWidth ,
                 uint8_t   EmptyCh ,
                 uint16_t  Value )
{
  uint8_t Loop ;
  /*-------------------------------------------------------------------------*/
  for ( Loop = 0 ; Loop < ConvWidth ; Loop ++ )
    * ( Cnv + Loop ) = EmptyCh ;
  * ( Cnv + ConvWidth ) = ( uint8_t ) 0 ;
  Loop = ConvWidth - 1 ;
  for ( ; ; )
  {
    * ( Cnv + Loop ) = ( uint8_t ) ( ( Value % 10 ) + '0' ) ;
    Value /= 10 ;
    if ( ( ! Loop ) || ( ! Value ) )
      break ;
    Loop -- ;
  } /* for */ ;
} /* WordToStr */


void LoadCryptKey ( void )
{
  uint8_t * DES3_keys0 ;
  uint8_t * DES3_keys1 ;
  uint8_t * DES3_keys2 ;
  /*-------------------------------------------------------------------------*/
  DES3_keys0 = & CryptKey [ 0 ] ;
  DES3_keys1 = & CryptKey [ 8 ] ;
  DES3_keys2 = & CryptKey [ 16 ] ;
  des3_set_3keys ( & DES3Instance , DES3_keys0 , DES3_keys1 , DES3_keys2 ) ;
} /* LoadCryptKey */


void TraceBuffer ( uint8_t * BuffToPrint ,
                   uint8_t   BuffSize )
{
  uint8_t Loop;
  uint8_t Data ;
  uint8_t Number [ 8 ] ;
  uint8_t * Buff ;
  /*-------------------------------------------------------------------------*/
  Buff = BuffToPrint ;
  for ( Loop = 0 ; Loop < BuffSize ; Loop ++ )
  {
    Data = * Buff ++ ;
    SendSerial ( ' ' ) ;
    SendSerial ( ' ' ) ;
    if ( Data < 32 )
      SendSerial ( '.' ) ;
    else
      SendSerial ( Data ) ;
    SendSerial ( ' ' ) ;
  } /* for */ ;
  SendSerial ( Cr ) ;
  SendSerial ( Lf ) ;
  Buff = BuffToPrint ;
  for ( Loop = 0 ; Loop < BuffSize ; Loop ++ )
  {
    Data = * Buff ++ ;
    WordToStr ( Number , 3 , ' ' , ( uint16_t ) Data ) ;
    SendSerial ( Number[0] ) ;
    SendSerial ( Number[1] ) ;
    SendSerial ( Number[2] ) ;
    SendSerial ( ' ' ) ;
  } /* for */ ;
  SendSerial ( Cr ) ;
  SendSerial ( Lf ) ;
} /* TraceBuffer */



static uint8_t Text1 [ ] PROGMEM      = "\r\nKlucz szyfrujacy: " ;
static uint8_t Text2 [ ] PROGMEM      = "\r\nDane przed szyfrowaniem:\r\n" ;
static uint8_t Text3 [ ] PROGMEM      = "\r\nDane po szyfrowaniu:\r\n" ;
static uint8_t Text4 [ ] PROGMEM      = "\r\nDane po odszyfrowaniu:\r\n" ;


void DES3_Test ( void )
{
  uint8_t InputBuff [ 20 ] ;
  uint8_t OutputBuff [ 20 ] ;
  uint8_t Loop ;
  int StrLgt ;
  /*-------------------------------------------------------------------------*/
  SendSerial ( Cr ) ;
  SendSerial ( Lf ) ;
  SendSerialFlashTxt ( ( uint16_t ) Text1 ) ;
  SendSerial ( '<' ) ;
  for ( Loop = 0 ; Loop < 24 ; Loop ++ )
  {
    if ( CryptKey [ Loop ] < 32 )
      SendSerial ( '.' ) ;
    else
      SendSerial ( CryptKey [ Loop ] ) ;
  } /* for */ ;
  SendSerial ( '>' ) ;
  SendSerial ( Cr ) ;
  SendSerial ( Lf ) ;
  InputBuff[0]='L';
  InputBuff[1]='i';
  InputBuff[2]='t';
  InputBuff[3]='w';
  InputBuff[4]='o';
  InputBuff[5]=' ';
  InputBuff[6]='o';
  InputBuff[7]='j';
  InputBuff[8]='c';
  InputBuff[9]='z';
  InputBuff[10]='y';
  InputBuff[11]='z';
  InputBuff[12]='n';
  InputBuff[13]='o';
  InputBuff[14]=' ';
  InputBuff[15]='m';
  InputBuff[16]='o';
  InputBuff[17]='j';
  InputBuff[18]='a';
  InputBuff[19]='.';
  StrLgt = 20 ;
  SendSerialFlashTxt ( ( uint16_t ) Text2 ) ;
  TraceBuffer ( InputBuff , StrLgt ) ;
  EncryptString ( & DES3Instance , InputBuff , OutputBuff , StrLgt ) ;
  SendSerialFlashTxt ( ( uint16_t ) Text3 ) ;
  TraceBuffer ( OutputBuff , StrLgt ) ;
  DecryptString ( & DES3Instance , OutputBuff , InputBuff , StrLgt ) ;
  SendSerialFlashTxt ( ( uint16_t ) Text4 ) ;
  TraceBuffer ( InputBuff , StrLgt ) ;
} /* DES3_Test */


int main ( void )
{
  /*-------------------------------------------------------------------------*/
  HardwareInit ( ) ;
  EnvironmentInit ( ) ;
  SoftwareInit ( ) ;
  sei ( ) ;
  LoadCryptKey ( ) ;
  DES3_Test ( ) ;
  for ( ; ; )
  {
    nop ( ) ;
  } /* for */ ;
  return ( 0 ) ;
} /* main */




3des_6.png


Istotne elementy w programie:
static void SoftwareInit ( void )
{
...
CryptKey [ 0 ] = 'K' ;
CryptKey [ 1 ] = 'l' ;
CryptKey [ 2 ] = 'u' ;
CryptKey [ 3 ] = 'c' ;
CryptKey [ 4 ] = 'z' ;
CryptKey [ 5 ] = ' ' ;
CryptKey [ 6 ] = 'd' ;
CryptKey [ 7 ] = 'o' ;
CryptKey [ 8 ] = ' ' ;
CryptKey [ 9 ] = 's' ;
CryptKey [ 10 ] = 'y' ;
CryptKey [ 11 ] = 'f' ;
CryptKey [ 12 ] = 'r' ;
CryptKey [ 13 ] = 'o' ;
CryptKey [ 14 ] = 'w' ;
CryptKey [ 15 ] = 'a' ;
CryptKey [ 16 ] = 'n' ;
CryptKey [ 17 ] = 'i' ;
CryptKey [ 18 ] = 'a' ;
CryptKey [ 19 ] = '.' ;
CryptKey [ 20 ] = 0 ;
CryptKey [ 21 ] = 0 ;
CryptKey [ 22 ] = 0 ;
CryptKey [ 23 ] = 0 ;
} /* SoftwareInit */

Jako klucz należy wypełnić każdy bajt jego obszaru (w sumie 24 bajty - klucz 192-bitowy).

void DES3_Test ( void )
{
...
InputBuff[0]='L';
InputBuff[1]='i';
InputBuff[2]='t';
InputBuff[3]='w';
InputBuff[4]='o';
InputBuff[5]=' ';
InputBuff[6]='o';
InputBuff[7]='j';
InputBuff[8]='c';
InputBuff[9]='z';
InputBuff[10]='y';
InputBuff[11]='z';
InputBuff[12]='n';
InputBuff[13]='o';
InputBuff[14]=' ';
InputBuff[15]='m';
InputBuff[16]='o';
InputBuff[17]='j';
InputBuff[18]='a';
InputBuff[19]='.';
...
} /* DES3_Test */

Jako przykład jest podany tekst (jako łańcuch znaków). W ogólnym przypadku funkcje szyfrowania i deszyfrowania traktują dane wejściowe jako blok w pamięci RAM, dlatego w tych funkcjach podawana jest długość bloku wyrażona w bajtach (nie istnieje coś takiego jak znak końca danych występujący w przetwarzaniu stringów).

W dołączonym do programu prezentującego module zawierającym funkcje szyfrowania i deszyfrowania znajdują się:

Kod: Zaznacz cały

typedef struct {
                   uint32_t  esk [ 96 ] ;     /* Triple-DES encryption subkeys */
                   uint32_t  dsk [ 96 ] ;     /* Triple-DES decryption subkeys */
                 } DES3InstanceType ;

extern int des3_set_3keys ( DES3InstanceType * DES3Instance ,
                            uint8_t            key1 [ 8 ] ,
                            uint8_t            key2 [ 8 ] ,
                            uint8_t            key3 [ 8 ] ) ;

extern void EncryptString ( DES3InstanceType * DES3Instance ,
                            unsigned char    * Inp ,
                            unsigned char    * Out ,
                            int                StrLgt ) ;

extern void DecryptString ( DES3InstanceType * DES3Instance ,
                            unsigned char    * Inp ,
                            unsigned char    * Out ,
                            int                StrLgt ) ;


DES3InstanceType to struktura obszaru danych w pamięci RAM używanej w module szyfrująco/deszyfrującym. Każde użycie funkcji szyfrującej/deszyfrującej wymaga podania wskaźnika do takiego obszaru. Przed pierwszym użyciem należy ten obszar zainicjować poprzez wywołanie funkcji des3_set_3keys, która jest jednocześnie załadowaniem klucza (jako trzech kluczy 64-bitowych). Obszar każdego klucza musi zawierać 8 bajtów ustalonego klucza.

Funkcja EncryptString służy do zaszyfrowania obszaru Inp o długości StrLgt a wynik szyfrowania jest umieszczony w obszarze Out.

Funkcja DecryptString służy do odszyfrowania obszaru Inp o długości StrLgt a wynik deszyfrowania jest umieszczony w obszarze Out.

Załącznik (projekt w AVRSTUDIO):
crypt.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
wojtek
Uber Geek
Uber Geek
Posty: 2198
Rejestracja: piątek 04 wrz 2015, 09:03

Re: 3DES - algorytm szyfrowania danych

Postautor: wojtek » sobota 06 maja 2017, 05:51

No i kolejny kawał dobrej lektury, dzięki :)
Ładnie się zaimportowało do AS7 ale widzę, że musiałbym doinstalować WinAVR, którego juz dość dawno na rzecz Atmel Toolchain usunąłem z komputera - czy może się mylę i jest inne rozwiązanie?
Wojtek

Awatar użytkownika
gaweł
Expert
Expert
Posty: 911
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: 3DES - algorytm szyfrowania danych

Postautor: gaweł » sobota 06 maja 2017, 11:59

wojtek pisze:...ale widzę, że musiałbym doinstalować WinAVR...

Może nie koniecznie. W pliku projektu do AVRSTUDIO jest możliwość wskazania na narzędzia. Spakowane pliki projektu są "związane" z moim komputerem, z moim środowiskiem.
avr.PNG

Trzeba zaznaczyć opcję Use AVR Toolchain lub podać w avr-gcc i make właściwe wskazania. Ja zawsze w projektach mam jawne wskazanie na narzędzia, bo często różne systemy narzędziowe są "wzajemnie w konflikcie".

No i pozostaje najprostsze rozwiązanie: utworzyć w AS7 własny projekt, do którego dołączyć jedynie pliki źródłowe (całe dwa → całość składa się z pliku crypt.c oraz z dołączonego modułu descrypto.c+descrypto.h).
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
Ostatnio zmieniony sobota 06 maja 2017, 12:20 przez gaweł, łącznie zmieniany 1 raz.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
Antystatyczny
Geek
Geek
Posty: 1132
Rejestracja: czwartek 03 wrz 2015, 22:02

Re: 3DES - algorytm szyfrowania danych

Postautor: Antystatyczny » sobota 06 maja 2017, 12:17

wojtek pisze:No i kolejny kawał dobrej lektury, dzięki :)
Ładnie się zaimportowało do AS7 ale widzę, że musiałbym doinstalować WinAVR, którego juz dość dawno na rzecz Atmel Toolchain usunąłem z komputera - czy może się mylę i jest inne rozwiązanie?



Co prawda jeszcze nie zaglądałem do wnętrza zipa z projektem, ale nie widzę jakiegoś wielkiego problemu w ewentualnym dopasowaniu softu do nowego toolchaina. Prezentowany soft zapewne był pisany kilka lat temu, gdy jeszcze używało się SIGNAL itp. , a o atmel toolchain nawet nikt nie marzył :)
"The true sign of intelligence is not knowledge but imagination" Albert Einstein.

Awatar użytkownika
gaweł
Expert
Expert
Posty: 911
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: 3DES - algorytm szyfrowania danych

Postautor: gaweł » sobota 06 maja 2017, 12:26

Antystatyczny pisze:Co prawda jeszcze nie zaglądałem do wnętrza zipa z projektem, ale nie widzę jakiegoś wielkiego problemu w ewentualnym dopasowaniu softu do nowego toolchaina. Prezentowany soft zapewne był pisany kilka lat temu, gdy jeszcze używało się SIGNAL itp. , a o atmel toolchain nawet nikt nie marzył :)

Zgadza się, program dla AVR utworzyłem na przełomie 2005/2006, czyli jakiś czas temu :) . Teraz jedynie wyjąłem z tego elementy istotne z punktu widzenia prezentacji.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse


Wróć do „Programowanie AVR w C”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 1 gość