From 8538e89f9232ecb90387a5a78af2a6d421a15753 Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Tue, 17 Mar 2026 16:19:51 +0800 Subject: [PATCH] add chacha20 --- Tun/PacketTunnelProvider.swift | 52 +-------------- Tun/Punchnet/AESCipher.swift | 13 ---- Tun/Punchnet/Actors/SDLContextActor.swift | 37 +++++++---- Tun/Punchnet/Cipher/CCAESChiper.swift | 27 ++++++++ Tun/Punchnet/Cipher/CCChaCha20Cipher.swift | 77 ++++++++++++++++++++++ Tun/Punchnet/Cipher/CCDataCipher.swift | 13 ++++ Tun/Punchnet/Cipher/CCRSACipher.swift | 41 ++++++++++++ Tun/Punchnet/SDLError.swift | 3 + Tun/Punchnet/SDLMessage.pb.swift | 38 ++++++++--- 9 files changed, 216 insertions(+), 85 deletions(-) delete mode 100644 Tun/Punchnet/AESCipher.swift create mode 100644 Tun/Punchnet/Cipher/CCAESChiper.swift create mode 100644 Tun/Punchnet/Cipher/CCChaCha20Cipher.swift create mode 100644 Tun/Punchnet/Cipher/CCDataCipher.swift create mode 100644 Tun/Punchnet/Cipher/CCRSACipher.swift diff --git a/Tun/PacketTunnelProvider.swift b/Tun/PacketTunnelProvider.swift index a832959..eff3c7b 100644 --- a/Tun/PacketTunnelProvider.swift +++ b/Tun/PacketTunnelProvider.swift @@ -31,10 +31,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider { // 加密算法 let rsaCipher = try! CCRSACipher(keySize: 1024) - let aesChiper = CCAESChiper() - self.rootTask = Task { - self.contextActor = SDLContextActor(provider: self, config: config, rsaCipher: rsaCipher, aesCipher: aesChiper) + self.contextActor = SDLContextActor(provider: self, config: config, rsaCipher: rsaCipher) await self.contextActor?.start() completionHandler(nil) } @@ -79,52 +77,4 @@ extension PacketTunnelProvider { return interfaces.first {$0.name == "en0"} }() - - struct CCRSACipher: RSACipher { - var pubKey: String - let privateKeyDER: Data - - init(keySize: Int) throws { - let (privateKey, publicKey) = try Self.loadKeys(keySize: keySize) - let privKeyStr = SwKeyConvert.PrivateKey.derToPKCS1PEM(privateKey) - - self.pubKey = SwKeyConvert.PublicKey.derToPKCS8PEM(publicKey) - self.privateKeyDER = try SwKeyConvert.PrivateKey.pemToPKCS1DER(privKeyStr) - } - - public func decode(data: Data) throws -> Data { - let tag = Data() - let (decryptedData, _) = try CC.RSA.decrypt(data, derKey: self.privateKeyDER, tag: tag, padding: .pkcs1, digest: .none) - - return decryptedData - } - - private static func loadKeys(keySize: Int) throws -> (Data, Data) { - if let privateKey = UserDefaults.standard.data(forKey: "privateKey"), - let publicKey = UserDefaults.standard.data(forKey: "publicKey") { - - return (privateKey, publicKey) - } else { - let (privateKey, publicKey) = try CC.RSA.generateKeyPair(keySize) - UserDefaults.standard.setValue(privateKey, forKey: "privateKey") - UserDefaults.standard.setValue(publicKey, forKey: "publicKey") - - return (privateKey, publicKey) - } - } - } - - struct CCAESChiper: AESCipher { - func decypt(aesKey: Data, data: Data) throws -> Data { - let ivData = Data(aesKey.prefix(16)) - return try CC.crypt(.decrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: data, key: aesKey, iv: ivData) - } - - func encrypt(aesKey: Data, data: Data) throws -> Data { - let ivData = Data(aesKey.prefix(16)) - - return try CC.crypt(.encrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: data, key: aesKey, iv: ivData) - } - } - } diff --git a/Tun/Punchnet/AESCipher.swift b/Tun/Punchnet/AESCipher.swift deleted file mode 100644 index ae92578..0000000 --- a/Tun/Punchnet/AESCipher.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// AESCipher.swift -// sdlan -// -// Created by 安礼成 on 2025/7/14. -// -import Foundation - -public protocol AESCipher { - func decypt(aesKey: Data, data: Data) throws -> Data - - func encrypt(aesKey: Data, data: Data) throws -> Data -} diff --git a/Tun/Punchnet/Actors/SDLContextActor.swift b/Tun/Punchnet/Actors/SDLContextActor.swift index 4b53add..f4a914b 100644 --- a/Tun/Punchnet/Actors/SDLContextActor.swift +++ b/Tun/Punchnet/Actors/SDLContextActor.swift @@ -26,15 +26,13 @@ actor SDLContextActor { var natType: SDLNATProberActor.NatType = .blocked // AES加密,授权通过后,对象才会被创建 - nonisolated let aesCipher: AESCipher - - // aes - private var aesKey: Data? + private var dataCipher: CCDataCipher? // session token private var sessionToken: Data? // rsa的相关配置, public_key是本地生成的 + // 加密算法相关 nonisolated let rsaCipher: RSACipher // 依赖的变量 @@ -84,11 +82,10 @@ actor SDLContextActor { // 注册任务 private var registerTask: Task? - public init(provider: NEPacketTunnelProvider, config: SDLConfiguration, rsaCipher: RSACipher, aesCipher: AESCipher) { + public init(provider: NEPacketTunnelProvider, config: SDLConfiguration, rsaCipher: RSACipher) { self.provider = provider self.config = config self.rsaCipher = rsaCipher - self.aesCipher = aesCipher self.puncherActor = SDLPuncherActor() self.proberActor = SDLNATProberActor(addressArray: config.stunProbeSocketAddressArray) @@ -392,10 +389,28 @@ actor SDLContextActor { private func handleRegisterSuperAck(registerSuperAck: SDLRegisterSuperAck) async { // 需要对数据通过rsa的私钥解码 - self.aesKey = try! self.rsaCipher.decode(data: Data(registerSuperAck.aesKey)) + guard let key = try? self.rsaCipher.decode(data: Data(registerSuperAck.key)) else { + SDLLogger.shared.log("[SDLContext] registerSuperAck invalid key", level: .error) + self.provider.cancelTunnelWithError(SDLError.invalidKey) + return + } + + let algorithm = registerSuperAck.algorithm.lowercased() + let regionId = registerSuperAck.regionID self.sessionToken = registerSuperAck.sessionToken - SDLLogger.shared.log("[SDLContext] get registerSuperAck, aes_key len: \(self.aesKey!.count)", level: .info) + switch algorithm { + case "aes": + self.dataCipher = CCAESChiper(key: key) + case "chacha20": + self.dataCipher = CCChaCha20Cipher(regionId: regionId, keyData: key) + default: + SDLLogger.shared.log("[SDLContext] registerSuperAck invalid algorithm \(algorithm)", level: .error) + self.provider.cancelTunnelWithError(SDLError.unsupportedAlgorithm(algorithm: algorithm)) + return + } + + SDLLogger.shared.log("[SDLContext] get registerSuperAck, aes_key len: \(key.count)", level: .info) // 服务器分配的tun网卡信息 do { try await self.setNetworkSettings(networkAddress: self.config.networkAddress, dnsServer: SDLDNSClient.Helper.dnsServer) @@ -511,7 +526,7 @@ actor SDLContextActor { } private func handleHoleData(data: SDLData) async throws { - guard let aesKey = self.aesKey else { + guard let dataCipher = self.dataCipher else { return } @@ -521,7 +536,7 @@ actor SDLContextActor { return } - guard let decyptedData = try? self.aesCipher.decypt(aesKey: aesKey, data: Data(data.data)) else { + guard let decyptedData = try? dataCipher.decrypt(cipherText: Data(data.data)) else { return } @@ -683,7 +698,7 @@ actor SDLContextActor { let networkAddr = self.config.networkAddress // 将数据封装层2层的数据包 let layerPacket = LayerPacket(dstMac: dstMac, srcMac: networkAddr.mac, type: type, data: data) - guard let udpHole = self.udpHole, let aesKey = self.aesKey, let encodedPacket = try? self.aesCipher.encrypt(aesKey: aesKey, data: layerPacket.marshal()) else { + guard let udpHole = self.udpHole, let dataCipher = self.dataCipher, let encodedPacket = try? dataCipher.encrypt(plainText: layerPacket.marshal()) else { return } diff --git a/Tun/Punchnet/Cipher/CCAESChiper.swift b/Tun/Punchnet/Cipher/CCAESChiper.swift new file mode 100644 index 0000000..f75ca06 --- /dev/null +++ b/Tun/Punchnet/Cipher/CCAESChiper.swift @@ -0,0 +1,27 @@ +// +// CCAESChiper.swift +// punchnet +// +// Created by 安礼成 on 2026/3/17. +// +import Foundation + +struct CCAESChiper: CCDataCipher { + private let aesKey: Data + + init(key: Data) { + self.aesKey = key + } + + func decrypt(cipherText: Data) throws -> Data { + let ivData = Data(aesKey.prefix(16)) + return try CC.crypt(.decrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: cipherText, key: aesKey, iv: ivData) + } + + func encrypt(plainText: Data) throws -> Data { + let ivData = Data(aesKey.prefix(16)) + + return try CC.crypt(.encrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: plainText, key: aesKey, iv: ivData) + } + +} diff --git a/Tun/Punchnet/Cipher/CCChaCha20Cipher.swift b/Tun/Punchnet/Cipher/CCChaCha20Cipher.swift new file mode 100644 index 0000000..3aaae26 --- /dev/null +++ b/Tun/Punchnet/Cipher/CCChaCha20Cipher.swift @@ -0,0 +1,77 @@ +// +// NonceGenerator.swift +// punchnet +// +// Created by 安礼成 on 2026/3/17. +// +import Foundation +import CryptoKit + +/// ChaCha20-Poly1305 加解密示例 +struct CCChaCha20Cipher: CCDataCipher { + private let key: SymmetricKey + private let nonceGenerator: NonceGenerator + + init(regionId: UInt32, keyData: Data) { + self.key = SymmetricKey(data: keyData) + self.nonceGenerator = NonceGenerator(regionId: regionId) + } + + /// 加密 + func encrypt(plainText: Data) throws -> Data { + let nonce = nonceGenerator.nextNonceData() + let sealedBox = try ChaChaPoly.seal(plainText, using: key, nonce: .init(data: nonce)) + + return sealedBox.combined + } + + /// 解密 + func decrypt(cipherText: Data) throws -> Data { + let sealedBox = try ChaChaPoly.SealedBox(combined: cipherText) + return try ChaChaPoly.open(sealedBox, using: key) + } + +} + +extension CCChaCha20Cipher { + + /// Nonce生成器(基于ServerRange + 毫秒时间低位 + 本地自增counter) + final class NonceGenerator { + private let locker = NSLock() + + private let regionId: UInt32 // 32-bit 全局前缀 + private var counter: UInt64 = 0 // 自增counter + + init(regionId: UInt32) { + self.regionId = regionId + } + + /// 生成64-bit Nonce + func nextNonceData() -> Data { + locker.lock() + defer { + locker.unlock() + } + + let nowMillis = UInt64(Date().timeIntervalSince1970 * 1000) + // 时间占用40个bit位, 自增id占用24位 + let timeMask: UInt64 = (1 << 40) - 1 + let timeLow = nowMillis & timeMask + + // 生成 Nonce + let counterMask: UInt64 = (1 << 24) - 1 + let nonce = (timeLow << 24) | (counter & counterMask) + // 自增counter + self.counter = (self.counter + 1) & counterMask // 超过最大值回到0 + + var data = Data() + // region: UInt32 -> 4字节大端 + data.append(contentsOf: withUnsafeBytes(of: regionId.bigEndian, Array.init)) + // nonce: UInt64 -> 8字节大端 + data.append(contentsOf: withUnsafeBytes(of: nonce.bigEndian, Array.init)) + + return data + } + } + +} diff --git a/Tun/Punchnet/Cipher/CCDataCipher.swift b/Tun/Punchnet/Cipher/CCDataCipher.swift new file mode 100644 index 0000000..c291469 --- /dev/null +++ b/Tun/Punchnet/Cipher/CCDataCipher.swift @@ -0,0 +1,13 @@ +// +// AESCipher.swift +// sdlan +// +// Created by 安礼成 on 2025/7/14. +// +import Foundation + +public protocol CCDataCipher { + func decrypt(cipherText: Data) throws -> Data + + func encrypt(plainText: Data) throws -> Data +} diff --git a/Tun/Punchnet/Cipher/CCRSACipher.swift b/Tun/Punchnet/Cipher/CCRSACipher.swift new file mode 100644 index 0000000..d26d259 --- /dev/null +++ b/Tun/Punchnet/Cipher/CCRSACipher.swift @@ -0,0 +1,41 @@ +// +// CCRSACipher.swift +// punchnet +// +// Created by 安礼成 on 2026/3/17. +// +import Foundation + +struct CCRSACipher: RSACipher { + var pubKey: String + let privateKeyDER: Data + + init(keySize: Int) throws { + let (privateKey, publicKey) = try Self.loadKeys(keySize: keySize) + let privKeyStr = SwKeyConvert.PrivateKey.derToPKCS1PEM(privateKey) + + self.pubKey = SwKeyConvert.PublicKey.derToPKCS8PEM(publicKey) + self.privateKeyDER = try SwKeyConvert.PrivateKey.pemToPKCS1DER(privKeyStr) + } + + public func decode(data: Data) throws -> Data { + let tag = Data() + let (decryptedData, _) = try CC.RSA.decrypt(data, derKey: self.privateKeyDER, tag: tag, padding: .pkcs1, digest: .none) + + return decryptedData + } + + private static func loadKeys(keySize: Int) throws -> (Data, Data) { + if let privateKey = UserDefaults.standard.data(forKey: "privateKey"), + let publicKey = UserDefaults.standard.data(forKey: "publicKey") { + + return (privateKey, publicKey) + } else { + let (privateKey, publicKey) = try CC.RSA.generateKeyPair(keySize) + UserDefaults.standard.setValue(privateKey, forKey: "privateKey") + UserDefaults.standard.setValue(publicKey, forKey: "publicKey") + + return (privateKey, publicKey) + } + } +} diff --git a/Tun/Punchnet/SDLError.swift b/Tun/Punchnet/SDLError.swift index 3c95aac..9fc2218 100644 --- a/Tun/Punchnet/SDLError.swift +++ b/Tun/Punchnet/SDLError.swift @@ -8,4 +8,7 @@ enum SDLError: Error { case socketClosed case socketError + + case invalidKey + case unsupportedAlgorithm(algorithm: String) } diff --git a/Tun/Punchnet/SDLMessage.pb.swift b/Tun/Punchnet/SDLMessage.pb.swift index 4eacda7..539f33b 100644 --- a/Tun/Punchnet/SDLMessage.pb.swift +++ b/Tun/Punchnet/SDLMessage.pb.swift @@ -127,7 +127,13 @@ struct SDLRegisterSuperAck: @unchecked Sendable { var pktID: UInt32 = 0 - var aesKey: Data = Data() + /// 目前支持aes, chacha20 + var algorithm: String = String() + + var key: Data = Data() + + /// 逻辑分段,chacha20加密算法需要使用该字段 + var regionID: UInt32 = 0 var sessionToken: Data = Data() @@ -268,7 +274,7 @@ struct SDLPolicyResponse: @unchecked Sendable { /// 版本号,客户端需要比较版本号确定是否覆盖; 请求端自己去管理版本号,服务端只是原样回写 var version: UInt32 = 0 - /// 4+1+2 的稀疏序列化规则 + /// 1 + 2稀疏序列化规则, 按照: <> 这个格式序列号所有的规则信息; 下发的数据默认都是allow,deny规则的服务器端已经屏蔽 var rules: Data = Data() var unknownFields = SwiftProtobuf.UnknownStorage() @@ -725,8 +731,10 @@ extension SDLRegisterSuperAck: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl static let protoMessageName: String = "SDLRegisterSuperAck" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "pkt_id"), - 2: .standard(proto: "aes_key"), - 3: .standard(proto: "session_token"), + 2: .same(proto: "algorithm"), + 3: .same(proto: "key"), + 4: .standard(proto: "region_id"), + 5: .standard(proto: "session_token"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -736,8 +744,10 @@ extension SDLRegisterSuperAck: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularUInt32Field(value: &self.pktID) }() - case 2: try { try decoder.decodeSingularBytesField(value: &self.aesKey) }() - case 3: try { try decoder.decodeSingularBytesField(value: &self.sessionToken) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.algorithm) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.key) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self.regionID) }() + case 5: try { try decoder.decodeSingularBytesField(value: &self.sessionToken) }() default: break } } @@ -747,18 +757,26 @@ extension SDLRegisterSuperAck: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl if self.pktID != 0 { try visitor.visitSingularUInt32Field(value: self.pktID, fieldNumber: 1) } - if !self.aesKey.isEmpty { - try visitor.visitSingularBytesField(value: self.aesKey, fieldNumber: 2) + if !self.algorithm.isEmpty { + try visitor.visitSingularStringField(value: self.algorithm, fieldNumber: 2) + } + if !self.key.isEmpty { + try visitor.visitSingularBytesField(value: self.key, fieldNumber: 3) + } + if self.regionID != 0 { + try visitor.visitSingularUInt32Field(value: self.regionID, fieldNumber: 4) } if !self.sessionToken.isEmpty { - try visitor.visitSingularBytesField(value: self.sessionToken, fieldNumber: 3) + try visitor.visitSingularBytesField(value: self.sessionToken, fieldNumber: 5) } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: SDLRegisterSuperAck, rhs: SDLRegisterSuperAck) -> Bool { if lhs.pktID != rhs.pktID {return false} - if lhs.aesKey != rhs.aesKey {return false} + if lhs.algorithm != rhs.algorithm {return false} + if lhs.key != rhs.key {return false} + if lhs.regionID != rhs.regionID {return false} if lhs.sessionToken != rhs.sessionToken {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true