Implemented ECDH key exchange using ed25519 to share a symmetric AES key without transmitting it over the network.
This commit is contained in:
@@ -1,21 +1,29 @@
|
||||
import random
|
||||
import system
|
||||
import nimcrypto
|
||||
import nimcrypto/blake2
|
||||
from ed25519 import keyExchange, createKeyPair, seed
|
||||
# from monocypher import crypto_key_exchange_public_key, crypto_key_exchange, crypto_blake2b, crypto_wipe
|
||||
|
||||
import ./[utils, types]
|
||||
|
||||
proc generateSessionKey*(): Key =
|
||||
# Generate a random 256-bit (32-byte) session key for AES-256 encryption
|
||||
var key: array[32, byte]
|
||||
for i in 0 ..< 32:
|
||||
key[i] = byte(rand(255))
|
||||
return key
|
||||
#[
|
||||
Symmetric AES256 GCM encryption for secure C2 traffic
|
||||
Ensures both confidentiality and integrity of the packet
|
||||
]#
|
||||
proc generateKeyPair*(): KeyPair =
|
||||
let keyPair = createKeyPair(seed())
|
||||
|
||||
return KeyPair(
|
||||
privateKey: keyPair.privateKey,
|
||||
publicKey: keyPair.publicKey
|
||||
)
|
||||
|
||||
proc generateIV*(): Iv =
|
||||
# Generate a random 98-bit (12-byte) initialization vector for AES-256 GCM mode
|
||||
var iv: array[12, byte]
|
||||
for i in 0 ..< 12:
|
||||
iv[i] = byte(rand(255))
|
||||
return iv
|
||||
var iv: Iv
|
||||
if randomBytes(iv) != 12:
|
||||
raise newException(CatchableError, "Failed to generate IV.")
|
||||
return iv
|
||||
|
||||
proc encrypt*(key: Key, iv: Iv, data: seq[byte], sequenceNumber: uint64): (seq[byte], AuthenticationTag) =
|
||||
|
||||
@@ -47,3 +55,67 @@ proc decrypt*(key: Key, iv: Iv, encData: seq[byte], sequenceNumber: uint64): (se
|
||||
|
||||
return (data, tag)
|
||||
|
||||
#[
|
||||
ECDHE key exchange using ed25519
|
||||
]#
|
||||
proc loadKeys*(privateKeyFile, publicKeyFile: string): KeyPair =
|
||||
let filePrivate = open(privateKeyFile, fmRead)
|
||||
defer: filePrivate.close()
|
||||
|
||||
var privateKey: PrivateKey
|
||||
var bytesRead = filePrivate.readBytes(privateKey, 0, sizeof(PrivateKey))
|
||||
|
||||
if bytesRead != sizeof(PrivateKey):
|
||||
raise newException(ValueError, "Invalid private key length.")
|
||||
|
||||
let filePublic = open(publicKeyFile, fmRead)
|
||||
defer: filePublic.close()
|
||||
|
||||
var publicKey: PublicKey
|
||||
bytesRead = filePublic.readBytes(publicKey, 0, sizeof(PublicKey))
|
||||
|
||||
if bytesRead != sizeof(PublicKey):
|
||||
raise newException(ValueError, "Invalid public key length.")
|
||||
|
||||
return KeyPair(
|
||||
privateKey: privateKey,
|
||||
publicKey: publicKey
|
||||
)
|
||||
|
||||
proc writeKey*[T: PublicKey | PrivateKey](keyFile: string, key: T) =
|
||||
let file = open(keyFile, fmWrite)
|
||||
defer: file.close()
|
||||
|
||||
let bytesWritten = file.writeBytes(key, 0, sizeof(T))
|
||||
|
||||
if bytesWritten != sizeof(T):
|
||||
raise newException(ValueError, "Invalid key length.")
|
||||
|
||||
proc combineKeys(publicKey, otherPublicKey: Key): Key =
|
||||
# XOR is a commutative operation, that ensures that the order of the public keys does not matter
|
||||
for i in 0..<32:
|
||||
result[i] = publicKey[i] xor otherPublicKey[i]
|
||||
|
||||
proc deriveSessionKey*(keyPair: KeyPair, publicKey: Key): Key =
|
||||
var key: Key
|
||||
|
||||
# Calculate shared secret (https://monocypher.org/manual/x25519)
|
||||
let sharedSecret = keyExchange(publicKey, keyPair.privateKey)
|
||||
|
||||
# Add combined public keys to hash
|
||||
let combinedKeys: Key = combineKeys(keyPair.publicKey, publicKey)
|
||||
|
||||
# Calculate Blake2b hash to derive session key
|
||||
var ctx: blake2_512
|
||||
ctx.init()
|
||||
ctx.update(sharedSecret)
|
||||
ctx.update("CONQUEST".toBytes() & @combinedKeys)
|
||||
|
||||
let hash = ctx.finish
|
||||
let bytes = hash.data[0..<sizeof(Key)]
|
||||
copyMem(key[0].addr, bytes[0].addr, sizeof(Key))
|
||||
|
||||
# Cleanup
|
||||
zeroMem(sharedSecret[0].addr, sharedSecret.len)
|
||||
|
||||
return key
|
||||
@@ -52,6 +52,8 @@ type
|
||||
# Encryption
|
||||
type
|
||||
Key* = array[32, byte]
|
||||
PublicKey* = array[32, byte]
|
||||
PrivateKey* = array[64, byte]
|
||||
Iv* = array[12, byte]
|
||||
AuthenticationTag* = array[16, byte]
|
||||
|
||||
@@ -133,7 +135,7 @@ type
|
||||
|
||||
AgentRegistrationData* = object
|
||||
header*: Header
|
||||
sessionKey*: Key # [32 bytes ] AES 256 session key
|
||||
agentPublicKey*: Key # [32 bytes ] Public key of the connecting agent for key exchange
|
||||
metadata*: AgentMetadata
|
||||
|
||||
# Agent structure
|
||||
@@ -168,12 +170,17 @@ type
|
||||
|
||||
# Server structure
|
||||
type
|
||||
KeyPair* = object
|
||||
privateKey*: PrivateKey
|
||||
publicKey*: Key
|
||||
|
||||
Conquest* = ref object
|
||||
prompt*: Prompt
|
||||
dbPath*: string
|
||||
listeners*: Table[string, Listener]
|
||||
agents*: Table[string, Agent]
|
||||
interactAgent*: Agent
|
||||
keyPair*: KeyPair
|
||||
|
||||
# Agent Config
|
||||
type
|
||||
@@ -183,4 +190,5 @@ type
|
||||
ip*: string
|
||||
port*: int
|
||||
sleep*: int
|
||||
sessionKey*: Key
|
||||
sessionKey*: Key
|
||||
agentPublicKey*: PublicKey
|
||||
@@ -1,8 +1,14 @@
|
||||
import strutils, sequtils, random, strformat
|
||||
import strutils, sequtils, strformat
|
||||
import nimcrypto
|
||||
|
||||
import ./types
|
||||
|
||||
proc generateUUID*(): string =
|
||||
# Create a 4-byte HEX UUID string (8 characters)
|
||||
(0..<4).mapIt(rand(255)).mapIt(fmt"{it:02X}").join()
|
||||
var uuid: array[4, byte]
|
||||
if randomBytes(uuid) != 4:
|
||||
raise newException(CatchableError, "Failed to generate UUID.")
|
||||
return uuid.toHex().toUpperAscii()
|
||||
|
||||
proc uuidToUint32*(uuid: string): uint32 =
|
||||
return fromHex[uint32](uuid)
|
||||
@@ -62,4 +68,10 @@ proc toBytes*(value: uint64): seq[byte] =
|
||||
byte((value shr 40) and 0xFF),
|
||||
byte((value shr 48) and 0xFF),
|
||||
byte((value shr 56) and 0xFF)
|
||||
]
|
||||
]
|
||||
|
||||
proc toKey*(value: string): Key =
|
||||
if value.len != 32:
|
||||
raise newException(ValueError, "Invalid key length.")
|
||||
|
||||
copyMem(result[0].addr, value[0].unsafeAddr, 32)
|
||||
Reference in New Issue
Block a user