Browse Source

Added end-to-end AES encryption

main 1.2.0
Unbewohnte 3 years ago
parent
commit
f4c8f2d728
  1. 7
      README.md
  2. 30
      encryption/decrypt.go
  3. 24
      encryption/encrypt.go
  4. 27
      encryption/key.go
  5. 15
      main.go
  6. 6
      protocol/headers.go
  7. 33
      protocol/packet.go
  8. 38
      receiver/receiver.go
  9. 78
      sender/sender.go

7
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;

30
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
}

24
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
}

27
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
}

15
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

6
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.

33
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
}

38
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)

78
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")

Loading…
Cancel
Save