package decoder import ( "bytes" "crypto/aes" "crypto/cipher" "encoding/binary" "fmt" ) // XOR encrypts or decrypts text with the specified key. It requires the packetID and sending node ID for the AES IV func XOR(text []byte, key []byte, packetID, fromNode uint32) ([]byte, error) { if len(key) != 16 && len(key) != 24 && len(key) != 32 { return nil, fmt.Errorf("key length must be 16, 24, or 32 bytes") } block, err := aes.NewCipher(key) if err != nil { return nil, err } // The IV needs to be unique, but not secure. It's common to include it at // the beginning of the text. In CTR mode, the IV size is equal to the block size. //if len(text) < aes.BlockSize { // return nil, fmt.Errorf("text too short") //} iv, err := CreateNonce(packetID, fromNode) if err != nil { return nil, err } //text = text[aes.BlockSize:] // CTR mode is the same for both encryption and decryption, so we use // the NewCTR function rather than NewCBCDecrypter. stream := cipher.NewCTR(block, iv) // XORKeyStream can work in-place if the two arguments are the same. plaintext := make([]byte, len(text)) stream.XORKeyStream(plaintext, text) return plaintext, nil } // CreateNonce creates a 128-bit nonce. // It takes a uint32 packetId, converts it to a uint64, and a uint32 fromNode. // The nonce is concatenated as [64-bit packetId][32-bit fromNode][32-bit block counter]. func CreateNonce(packetId uint32, fromNode uint32) ([]byte, error) { // Expand packetId to 64 bits packetId64 := uint64(packetId) // Initialize block counter (32-bit, starts at zero) blockCounter := uint32(0) // Create a buffer for the nonce buf := new(bytes.Buffer) // Write packetId, fromNode, and block counter to the buffer err := binary.Write(buf, binary.LittleEndian, packetId64) if err != nil { return nil, err } err = binary.Write(buf, binary.LittleEndian, fromNode) if err != nil { return nil, err } err = binary.Write(buf, binary.LittleEndian, blockCounter) if err != nil { return nil, err } return buf.Bytes(), nil }