From f4c8f2d7289cf64d4ef486bab6d71d2cc1bd3b41 Mon Sep 17 00:00:00 2001 From: Unbewohnte Date: Tue, 15 Jun 2021 13:55:06 +0300 Subject: [PATCH] Added end-to-end AES encryption --- README.md | 7 ++-- encryption/decrypt.go | 30 +++++++++++++++++ encryption/encrypt.go | 24 +++++++++++++ encryption/key.go | 27 +++++++++++++++ main.go | 15 +++------ protocol/headers.go | 6 ++++ protocol/packet.go | 33 ++++++++++++++---- receiver/receiver.go | 38 ++++++++++++++++----- sender/sender.go | 78 +++++++++++++++++++++++++++++++++---------- 9 files changed, 212 insertions(+), 46 deletions(-) create mode 100644 encryption/decrypt.go create mode 100644 encryption/encrypt.go create mode 100644 encryption/key.go diff --git a/README.md b/README.md index f76bb10..b14737e 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Thus, with a connection and a way of communication, the sender will send some pa - Lack of proper error-handling; somewhat FIXED - [x] - Lack of information about the process of transferring (ETA, lost packets, etc.); FIXED - [ ] - No way to verify if the transferred file is not corrupted; FIXED via checksum- [x] -- No encryption; FIXED - [ ] +- No encryption; FIXED via AES encryption of packets` body - [x] - Messy and hard to follow code && file structure; FIXED? - [x] - No way to stop the download/upload and resume it later or even during the next connection; FIXED - [ ] - No tests; FIXED - [ ] @@ -66,9 +66,10 @@ Thus, with a connection and a way of communication, the sender will send some pa `./FTU [FLAGS_HERE]` or `FTU [FLAGS_HERE]` ### Flags - +`./FTU --help` - to get all flags` description + - `-port` (int) - specifies a working port (if sending - listens on this port, else - tries to connect to this port); -- `addr` (string) - specifies an address to connect to; +- `-addr` (string) - specifies an address to connect to; - `-sharefile` (string) - specifies path to a file you want to share, if given a valid path - sender will offer to download this file to receiver; - `-downloadto` (string) - specifies path to a folder where the receiver wants to store downloaded file; diff --git a/encryption/decrypt.go b/encryption/decrypt.go new file mode 100644 index 0000000..5a8b648 --- /dev/null +++ b/encryption/decrypt.go @@ -0,0 +1,30 @@ +package encryption + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +// Decrypts encrypted aes data with given key. +// https://www.melvinvivas.com/how-to-encrypt-and-decrypt-data-using-aes/ - very grateful to the author, THANK YOU. +func Decrypt(key, dataToDecrypt []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, fmt.Errorf("could not create new AES cipher: %s", err) + } + + aesGCM, err := cipher.NewGCM(block) + if err != nil { + return nil, fmt.Errorf("could not create new GCM: %s", err) + } + + nonce, encryptedBytes := dataToDecrypt[:aesGCM.NonceSize()], dataToDecrypt[aesGCM.NonceSize():] + + decryptedData, err := aesGCM.Open(nil, nonce, encryptedBytes, nil) + if err != nil { + return nil, fmt.Errorf("could not decrypt given data: %s", err) + } + + return decryptedData, nil +} diff --git a/encryption/encrypt.go b/encryption/encrypt.go new file mode 100644 index 0000000..339f379 --- /dev/null +++ b/encryption/encrypt.go @@ -0,0 +1,24 @@ +package encryption + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" +) + +// Encrypts given data using aes encryption. +// https://www.melvinvivas.com/how-to-encrypt-and-decrypt-data-using-aes/ - very grateful to the author, THANK YOU. +func Encrypt(key, dataToEncrypt []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, fmt.Errorf("could not create new AES cipher: %s", err) + } + aesGCM, err := cipher.NewGCM(block) + if err != nil { + return nil, fmt.Errorf("could not create new GCM: %s", err) + } + nonce := make([]byte, aesGCM.NonceSize()) + encryptedData := aesGCM.Seal(nonce, nonce, dataToEncrypt, nil) + + return encryptedData, nil +} diff --git a/encryption/key.go b/encryption/key.go new file mode 100644 index 0000000..b10b417 --- /dev/null +++ b/encryption/key.go @@ -0,0 +1,27 @@ +package encryption + +import ( + "math/rand" + "time" +) + +// using aes256, so 32 bytes-long key +const KEYLEN uint = 32 +const CHARS string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +// Generates 32 pseudo-random bytes to use as a key +func Generate32AESkey() []byte { + var generatedKey []byte + + rand.Seed(time.Now().UTC().UnixNano()) + // choosing "random" 32 bytes from CHARS + for { + if len(generatedKey) == int(KEYLEN) { + break + } + randomIndex := rand.Intn(len(CHARS)) + generatedKey = append(generatedKey, CHARS[randomIndex]) + } + + return generatedKey +} diff --git a/main.go b/main.go index b42aa61..938a285 100644 --- a/main.go +++ b/main.go @@ -12,23 +12,16 @@ import ( // flags var PORT *int = flag.Int("port", 8080, "Specifies a port for a sender|port to connect to") -var SENDERADDR *string = flag.String("addr", "", "Specifies an IP for connection") +var SENDERADDR *string = flag.String("addr", "", "Specifies an address to connect to") var DOWNLOADSFOLDER *string = flag.String("downloadto", "", "Specifies where the receiver will store downloaded file") var SHAREDFILE *string = flag.String("sharefile", "", "Specifies what file sender will send") var SENDING bool -// helpMessage -var HELPMSG string = ` -"-port", default: 8080, Specifies a port for a sender|port to connect to -"-addr", default: "", Specifies an IP for connection -"-downloadto", default: "", Specifies where the receiver will store downloaded file -"-sharefile", default: "", Specifies what file sender will send` - // Input-validation func processFlags() { if *PORT < 0 { - fmt.Println("Invalid port !\n", HELPMSG) + fmt.Println("Invalid port !") os.Exit(-1) } @@ -39,7 +32,7 @@ func processFlags() { // specifying address to connect to -> receiving if strings.TrimSpace(*SENDERADDR) != "" { if SENDING { - fmt.Println("Cannot specify an address when sharing !\n", HELPMSG) + fmt.Println("Cannot specify an address when sharing !") os.Exit(-1) } SENDING = false @@ -47,7 +40,7 @@ func processFlags() { // specifying path to download to -> receiving if strings.TrimSpace(*DOWNLOADSFOLDER) != "" { if SENDING { - fmt.Println("Cannot specify a downloads directory when sharing !\n", HELPMSG) + fmt.Println("Cannot specify a downloads directory when sharing !") os.Exit(-1) } SENDING = false diff --git a/protocol/headers.go b/protocol/headers.go index 97244d9..35954c1 100644 --- a/protocol/headers.go +++ b/protocol/headers.go @@ -7,6 +7,12 @@ type Header string //// In the following below examples "|" is PACKETSIZEDELIMETER and "~" is HEADERDELIMETER +// ENCRKEY. +// The FIRST header to be sent. Sent immediately after the connection has been established +// by sender. Body contains randomly generated by sender aes encryption key. +// ie: |40|ENCRKEY~SUPER_SECURE_ENCRYPTION_KEY_YESS +const HeaderEncryptionKey Header = "ENCRKEY" + // FILENAME. // This header is sent only by sender. The packet with this header // must contain a name of the transported file in BODY. diff --git a/protocol/packet.go b/protocol/packet.go index d6a6b8d..2144a15 100644 --- a/protocol/packet.go +++ b/protocol/packet.go @@ -12,6 +12,8 @@ import ( "fmt" "net" "strconv" + + "github.com/Unbewohnte/FTU/encryption" ) // Internal representation of packet before|after the transportation @@ -56,7 +58,7 @@ func SendPacket(connection net.Conn, packetToSend Packet) error { packetSize := MeasurePacketSize(packetToSend) if packetSize > uint64(MAXPACKETSIZE) { - return fmt.Errorf("invalid packet: HEADER: %s BODY: %s: EXCEEDED MAX PACKETSIZE", packetToSend.Header, packetToSend.Body) + return fmt.Errorf("invalid packet!: EXCEEDED MAX PACKETSIZE") } // packetsize between delimeters (ie: |17|) @@ -83,9 +85,28 @@ func SendPacket(connection net.Conn, packetToSend Packet) error { return nil } -// Reads a packet from given connection. +// Sends given packet to connection, as the normal `SendPacket` method, but +// encodes given packet`s BODY with AES encryption +func SendEncryptedPacket(connection net.Conn, packetToSend Packet, key []byte) error { + // encrypting packet`s body + encryptedBody, err := encryption.Encrypt(key, packetToSend.Body) + if err != nil { + return fmt.Errorf("could not encrypt packet`s body: %s", err) + } + packetToSend.Body = encryptedBody + + // sending the encrypted packet + err = SendPacket(connection, packetToSend) + if err != nil { + return fmt.Errorf("could not send packet: %s", err) + } + + return nil +} + +// Reads a packet from given connection, returns its bytes. // ASSUMING THAT THE PACKETS ARE SENT BY `SendPacket` function !!!! -func ReadFromConn(connection net.Conn) (Packet, error) { +func ReadFromConn(connection net.Conn) ([]byte, error) { var err error var delimeterCounter int = 0 var packetSizeStrBuffer string = "" @@ -114,7 +135,7 @@ func ReadFromConn(connection net.Conn) (Packet, error) { packetSize, err = strconv.Atoi(packetSizeStrBuffer) if err != nil { - return Packet{}, fmt.Errorf("could not convert packetsizeStr into int: %s", err) + return nil, fmt.Errorf("could not convert packetsizeStr into int: %s", err) } // have a packetsize, now reading the whole packet @@ -137,7 +158,5 @@ func ReadFromConn(connection net.Conn) (Packet, error) { packetBuffer.Write(buff[:read]) } - packet := BytesToPacket(packetBuffer.Bytes()) - - return packet, nil + return packetBuffer.Bytes(), nil } diff --git a/receiver/receiver.go b/receiver/receiver.go index 4b237f8..50c0af9 100644 --- a/receiver/receiver.go +++ b/receiver/receiver.go @@ -10,6 +10,7 @@ import ( "time" "github.com/Unbewohnte/FTU/checksum" + "github.com/Unbewohnte/FTU/encryption" "github.com/Unbewohnte/FTU/protocol" ) @@ -19,6 +20,7 @@ type Receiver struct { Connection net.Conn IncomingPackets chan protocol.Packet FileToDownload *File + EncryptionKey []byte ReadyToReceive bool Stopped bool FileBytesPacketCounter uint64 @@ -64,7 +66,7 @@ func (r *Receiver) Stop() { disconnectionPacket := protocol.Packet{ Header: protocol.HeaderDisconnecting, } - protocol.SendPacket(r.Connection, disconnectionPacket) + protocol.SendEncryptedPacket(r.Connection, disconnectionPacket, r.EncryptionKey) r.Stopped = true r.Disconnect() } @@ -110,7 +112,7 @@ func (r *Receiver) HandleFileOffer() error { rejectionPacket := protocol.Packet{ Header: protocol.HeaderReject, } - err := protocol.SendPacket(r.Connection, rejectionPacket) + err := protocol.SendEncryptedPacket(r.Connection, rejectionPacket, r.EncryptionKey) if err != nil { return fmt.Errorf("could not send a rejection packet: %s", err) } @@ -147,7 +149,7 @@ func (r *Receiver) HandleFileOffer() error { acceptancePacket := protocol.Packet{ Header: protocol.HeaderAccept, } - err = protocol.SendPacket(r.Connection, acceptancePacket) + err = protocol.SendEncryptedPacket(r.Connection, acceptancePacket, r.EncryptionKey) if err != nil { return fmt.Errorf("could not send an acceptance packet: %s", err) } @@ -174,16 +176,33 @@ func (r *Receiver) WritePieceOfFile(filePacket protocol.Packet) error { return nil } -// Listens in an endless loop; reads incoming packages and puts them into channel +// Listens in an endless loop; reads incoming packets, decrypts their BODY and puts into channel func (r *Receiver) ReceivePackets() { for { - incomingPacket, err := protocol.ReadFromConn(r.Connection) + incomingPacketBytes, err := protocol.ReadFromConn(r.Connection) if err != nil { - // in current implementation there is no way to receive a working file even if only one packet is missing fmt.Printf("Error reading a packet: %s\nExiting...", err) r.Stop() os.Exit(-1) } + + incomingPacket := protocol.BytesToPacket(incomingPacketBytes) + + // if this is the FIRST packet - it has HeaderEncryptionKey, so no need to decrypt + if incomingPacket.Header == protocol.HeaderEncryptionKey { + r.IncomingPackets <- incomingPacket + continue + } + + decryptedBody, err := encryption.Decrypt(r.EncryptionKey, incomingPacket.Body) + if err != nil { + fmt.Printf("Error decrypring incoming packet`s BODY: %s\nExiting...", err) + r.Stop() + os.Exit(-1) + } + + incomingPacket.Body = decryptedBody + r.IncomingPackets <- incomingPacket } } @@ -198,7 +217,6 @@ func (r *Receiver) MainLoop() { for { if r.Stopped { - // exit the mainloop break } @@ -206,7 +224,7 @@ func (r *Receiver) MainLoop() { readyPacket := protocol.Packet{ Header: protocol.HeaderReady, } - err := protocol.SendPacket(r.Connection, readyPacket) + err := protocol.SendEncryptedPacket(r.Connection, readyPacket, r.EncryptionKey) if err != nil { fmt.Printf("Could not send the packet: %s\nExiting...", err) r.Stop() @@ -225,6 +243,10 @@ func (r *Receiver) MainLoop() { // handling each packet header differently switch incomingPacket.Header { + case protocol.HeaderEncryptionKey: + r.EncryptionKey = incomingPacket.Body + fmt.Println("Got the encryption key: ", string(incomingPacket.Body)) + case protocol.HeaderFilename: r.FileToDownload.Filename = string(incomingPacket.Body) diff --git a/sender/sender.go b/sender/sender.go index bac4504..45afc12 100644 --- a/sender/sender.go +++ b/sender/sender.go @@ -7,6 +7,7 @@ import ( "strconv" "github.com/Unbewohnte/FTU/checksum" + "github.com/Unbewohnte/FTU/encryption" "github.com/Unbewohnte/FTU/protocol" ) @@ -17,13 +18,14 @@ type Sender struct { Listener net.Listener Connection net.Conn IncomingPackets chan protocol.Packet + EncryptionKey []byte SentFileBytesPackets uint64 TransferAllowed bool ReceiverIsReady bool Stopped bool } -// Creates a new sender with default fields +// Creates a new sender with default|necessary fields func NewSender(port int, filepath string) *Sender { fileToTransfer, err := getFile(filepath) if err != nil { @@ -45,6 +47,10 @@ func NewSender(port int, filepath string) *Sender { panic(err) } + // !!! + key := encryption.Generate32AESkey() + fmt.Printf("GENERATED ENCRYPTION KEY: %s\n", key) + var filepacketCounter uint64 fmt.Printf("Created a new sender at %s:%d (remote)\n%s:%d (local)\n", remoteIP, port, localIP, port) return &Sender{ @@ -54,6 +60,7 @@ func NewSender(port int, filepath string) *Sender { Connection: nil, IncomingPackets: incomingPacketsChan, SentFileBytesPackets: filepacketCounter, + EncryptionKey: key, TransferAllowed: false, ReceiverIsReady: false, Stopped: false, @@ -65,7 +72,7 @@ func (s *Sender) Stop() { disconnectionPacket := protocol.Packet{ Header: protocol.HeaderDisconnecting, } - err := protocol.SendPacket(s.Connection, disconnectionPacket) + err := protocol.SendEncryptedPacket(s.Connection, disconnectionPacket, s.EncryptionKey) if err != nil { panic(fmt.Sprintf("could not send a disconnection packet: %s", err)) } @@ -95,6 +102,21 @@ func (s *Sender) StopListening() { s.Listener.Close() } +// Sends generated earlier eas encryption key to receiver +func (s *Sender) SendEncryptionKey() error { + + keyPacket := protocol.Packet{ + Header: protocol.HeaderEncryptionKey, + Body: s.EncryptionKey, + } + err := protocol.SendPacket(s.Connection, keyPacket) + if err != nil { + return fmt.Errorf("could not send a packet: %s", err) + } + + return nil +} + // Sends multiple packets with all information about the file to receiver // (filename, filesize, checksum) func (s *Sender) SendOffer() error { @@ -103,7 +125,7 @@ func (s *Sender) SendOffer() error { Header: protocol.HeaderFilename, Body: []byte(s.FileToTransfer.Filename), } - err := protocol.SendPacket(s.Connection, filenamePacket) + err := protocol.SendEncryptedPacket(s.Connection, filenamePacket, s.EncryptionKey) if err != nil { return fmt.Errorf("could not send an information about the file: %s", err) } @@ -114,7 +136,7 @@ func (s *Sender) SendOffer() error { Body: []byte(strconv.Itoa(int(s.FileToTransfer.Filesize))), } - err = protocol.SendPacket(s.Connection, filesizePacket) + err = protocol.SendEncryptedPacket(s.Connection, filesizePacket, s.EncryptionKey) if err != nil { return fmt.Errorf("could not send an information about the file: %s", err) } @@ -124,7 +146,7 @@ func (s *Sender) SendOffer() error { Header: protocol.HeaderChecksum, Body: checksum.ChecksumToBytes(s.FileToTransfer.CheckSum), } - err = protocol.SendPacket(s.Connection, checksumPacket) + err = protocol.SendEncryptedPacket(s.Connection, checksumPacket, s.EncryptionKey) if err != nil { return fmt.Errorf("could not send an information about the file: %s", err) } @@ -133,7 +155,7 @@ func (s *Sender) SendOffer() error { donePacket := protocol.Packet{ Header: protocol.HeaderDone, } - err = protocol.SendPacket(s.Connection, donePacket) + err = protocol.SendEncryptedPacket(s.Connection, donePacket, s.EncryptionKey) if err != nil { return fmt.Errorf("could not send an information about the file: %s", err) } @@ -153,8 +175,8 @@ func (s *Sender) SendPiece() error { Header: protocol.HeaderFileBytes, } - // how many bytes we can send at maximum - maxFileBytes := protocol.MAXPACKETSIZE - uint(protocol.MeasurePacketSize(fileBytesPacket)) + // how many bytes we can send at maximum (including some little space for padding) + maxFileBytes := protocol.MAXPACKETSIZE - (uint(protocol.MeasurePacketSize(fileBytesPacket)) + 90) fileBytes := make([]byte, maxFileBytes) // if there is less data to send than the limit - create a buffer of needed size @@ -171,7 +193,7 @@ func (s *Sender) SendPiece() error { // filling BODY with bytes fileBytesPacket.Body = fileBytes - err = protocol.SendPacket(s.Connection, fileBytesPacket) + err = protocol.SendEncryptedPacket(s.Connection, fileBytesPacket, s.EncryptionKey) if err != nil { return fmt.Errorf("could not send a file packet : %s", err) } @@ -184,15 +206,27 @@ func (s *Sender) SendPiece() error { return nil } -// Listens in an endless loop; reads incoming packages and puts them into channel +// Listens in an endless loop; reads incoming packets, decrypts their BODY and puts into channel func (s *Sender) ReceivePackets() { for { - incomingPacket, err := protocol.ReadFromConn(s.Connection) + incomingPacketBytes, err := protocol.ReadFromConn(s.Connection) if err != nil { - // in current implementation there is no way to receive a working file even if only one packet is missing fmt.Printf("Error reading a packet: %s\nExiting...", err) + s.Stop() + os.Exit(-1) + } + + incomingPacket := protocol.BytesToPacket(incomingPacketBytes) + + decryptedBody, err := encryption.Decrypt(s.EncryptionKey, incomingPacket.Body) + if err != nil { + fmt.Printf("Error decrypting an incoming packet: %s\nExiting...", err) + s.Stop() os.Exit(-1) } + + incomingPacket.Body = decryptedBody + s.IncomingPackets <- incomingPacket } } @@ -204,12 +238,22 @@ func (s *Sender) MainLoop() { go s.ReceivePackets() + // instantly sending an encryption key, following the protocol`s rule + err := s.SendEncryptionKey() + if err != nil { + fmt.Printf("Could not send an encryption key: %s\nExiting...", err) + s.Stop() + } + // send an information about the shared file to the receiver - s.SendOffer() + err = s.SendOffer() + if err != nil { + fmt.Printf("Could not send an info about the file: %s\nExiting...", err) + s.Stop() + } for { if s.Stopped { - // exit the mainloop break } @@ -237,13 +281,13 @@ func (s *Sender) MainLoop() { fmt.Println("The transfer has been accepted !") s.TransferAllowed = true - case protocol.HeaderReady: - s.ReceiverIsReady = true - case protocol.HeaderReject: fmt.Println("The transfer has been rejected") s.Stop() + case protocol.HeaderReady: + s.ReceiverIsReady = true + case protocol.HeaderDisconnecting: // receiver is dropping the file transfer ? fmt.Println("Receiver has disconnected")