import CryptoSwift import Curve25519Kit public enum AESGCM { public static let gcmTagSize: UInt = 16 public static let ivSize: UInt = 12 public struct EncryptionResult { public let ciphertext: Data, symmetricKey: Data, ephemeralPublicKey: Data } public enum Error : LocalizedError { case keyPairGenerationFailed case sharedSecretGenerationFailed public var errorDescription: String? { switch self { case .keyPairGenerationFailed: return "Couldn't generate a key pair." case .sharedSecretGenerationFailed: return "Couldn't generate a shared secret." } } } /// - Note: Sync. Don't call from the main thread. public static func generateSymmetricKey(x25519PublicKey: Data, x25519PrivateKey: Data) throws -> Data { if Thread.isMainThread { #if DEBUG preconditionFailure("It's illegal to call encrypt(_:forSnode:) from the main thread.") #endif } guard let sharedSecret = try? Curve25519.generateSharedSecret(fromPublicKey: x25519PublicKey, privateKey: x25519PrivateKey) else { throw Error.sharedSecretGenerationFailed } let salt = "LOKI" return try Data(HMAC(key: salt.bytes, variant: .sha256).authenticate(sharedSecret.bytes)) } /// - Note: Sync. Don't call from the main thread. public static func decrypt(_ ivAndCiphertext: Data, with symmetricKey: Data) throws -> Data { if Thread.isMainThread { #if DEBUG preconditionFailure("It's illegal to call decrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") #endif } let iv = ivAndCiphertext[0.. Data { if Thread.isMainThread { #if DEBUG preconditionFailure("It's illegal to call encrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") #endif } let iv = Data.getSecureRandomData(ofSize: ivSize)! let gcm = GCM(iv: iv.bytes, tagLength: Int(gcmTagSize), mode: .combined) let aes = try AES(key: symmetricKey.bytes, blockMode: gcm, padding: .noPadding) let ciphertext = try aes.encrypt(plaintext.bytes) return iv + Data(ciphertext) } /// - Note: Sync. Don't call from the main thread. public static func encrypt(_ plaintext: Data, for hexEncodedX25519PublicKey: String) throws -> EncryptionResult { if Thread.isMainThread { #if DEBUG preconditionFailure("It's illegal to call encrypt(_:forSnode:) from the main thread.") #endif } let x25519PublicKey = Data(hex: hexEncodedX25519PublicKey) let ephemeralKeyPair = Curve25519.generateKeyPair() let symmetricKey = try generateSymmetricKey(x25519PublicKey: x25519PublicKey, x25519PrivateKey: ephemeralKeyPair.privateKey) let ciphertext = try encrypt(plaintext, with: Data(symmetricKey)) return EncryptionResult(ciphertext: ciphertext, symmetricKey: Data(symmetricKey), ephemeralPublicKey: ephemeralKeyPair.publicKey) } }