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: 634
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
Geek
Geek
Posty: 1931
Rejestracja: piątek 04 wrz 2015, 09:03
Lokalizacja: JO90JK

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?
73 Wojtek

Awatar użytkownika
gaweł
Expert
Expert
Posty: 634
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: 1096
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: 634
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ść