So far, the program works as follows.
We create a new VigenereCipher file in the Helpers folder:
import Foundation
public func convertToUnicodeScalars(
str: String,
minChar: UInt32,
maxChar: UInt32
) -> [UInt32] {
var scalars = [UInt32]()
for scalar in str.unicodeScalars {
let val = scalar.value
guard val >= minChar && val <= maxChar else {
continue
}
scalars.append(val)
}
return scalars
}
public struct Vigenere {
private let keyScalars: [UInt32]
private let smallestScalar: UInt32
private let largestScalar: UInt32
private let sizeAlphabet: UInt32
public init?(key: String, smallestCharacter: Character = "A", largestCharacter: Character = "Z") {
let smallScalars = smallestCharacter.unicodeScalars
let largeScalars = largestCharacter.unicodeScalars
guard smallScalars.count == 1, largeScalars.count == 1 else {
return nil
}
self.smallestScalar = smallScalars.first!.value
self.largestScalar = largeScalars.first!.value
self.sizeAlphabet = (largestScalar - smallestScalar) + 1
let scalars = convertToUnicodeScalars(str: key, minChar: smallestScalar, maxChar: largestScalar)
guard !scalars.isEmpty else {
return nil
}
self.keyScalars = scalars
}
public func decrypt(_ str: String) -> String? {
let txtBytes = convertToUnicodeScalars(str: str, minChar: smallestScalar, maxChar: largestScalar)
guard !txtBytes.isEmpty else {
return nil
}
var res = ""
for (i, c) in txtBytes.enumerated() where c >= smallestScalar && c <= largestScalar {
guard let char =
UnicodeScalar((c &+ sizeAlphabet &- keyScalars[i % keyScalars.count]) % sizeAlphabet &+ smallestScalar)
else {
return nil
}
res += String(char)
}
return res
}
public func encrypt(_ str: String) -> String? {
let txtBytes = convertToUnicodeScalars(str: str, minChar: smallestScalar, maxChar: largestScalar)
guard !txtBytes.isEmpty else {
return nil
}
var res = ""
for (i, c) in txtBytes.enumerated() where c >= smallestScalar && c <= largestScalar {
guard let char =
UnicodeScalar((c &+ keyScalars[i % keyScalars.count] &- 2 &* smallestScalar) % sizeAlphabet &+ smallestScalar)
else {
return nil
}
res += String(char)
}
return res
}
}
We create two new functions in the ChatViewModel file:
func encryptMessage(msg: String, key: String) ->String {
let cipher = Vigenere(key: key, smallestCharacter: " ", largestCharacter: "🛹")!
return cipher.encrypt(msg)!
}
func decryptMessage(msg: String, key: String) ->String {
let cipher = Vigenere(key: key, smallestCharacter: " ", largestCharacter: "🛹")!
return cipher.decrypt(msg)!
}
}
In the ChatViewModel file, a new key input parameter is added to the sendMessage function.
We perform the encryption process.
func sendMessage(msg: String, key: String) {
// Check that we have a selected chat
guard selectedChat != nil else {
return
}
// newly added code line -----
let emsg = encryptMessage(msg: msg, key: key)
// ------------------
databaseService.sendMessage(msg: emsg, chat: selectedChat!)
}
In the ConversationView File, we created a new chatKey variable to store the encryption key:
@State var chatKey = " "
We have created a new TextField. Here we can enter the key we want to encrypt:
// Key field
ZStack {
Rectangle()
.foregroundColor(Color("date-pill"))
.cornerRadius(50)
TextField("Type your key", text: $chatKey)
.foregroundColor(Color("text-input"))
.font(Font.bodyParagraph)
.disableAutocorrection(true)
.textInputAutocapitalization(.never)
.padding(10)
}
.frame(height: 44)
When we push send button clean up key text:
chatKey = chatKey.trimmingCharacters(in: .whitespacesAndNewlines)
We specify the new key parameter for encryption:
// Send message
chatViewModel.sendMessage(msg: chatMessage, key: chatKey)
— Decryption —
Decryption is performed in the ConversationView File:
// Text Message
let dmsg = chatViewModel.decryptMessage(msg: msg.msg, key: chatKey)
// Determine if it's a group chat and a msg from another user
if participants.count > 1 && !isFromUser {
let userOfMsg = participants.filter { p in
p.id == msg.senderid
}.first
// Show a text msg with name
ConversationTextMessage(msg: dmsg,
isFromUser: isFromUser,
name: "\(userOfMsg?.firstname ?? "") \(userOfMsg?.lastname ?? "")")
}
else {
// Text message with no name
ConversationTextMessage(msg: dmsg,
isFromUser: isFromUser)
}
This is what messaging looks like.
There is a problem that the chatKey must have at least one character or the application freezes. By default, the key is a space. When the message is sent, the space is deleted and the text we write becomes the key. If you want to delete the key you have to leave a character in the text field. The text field cannot be empty.
As a workaround I thought about this but it throws an error:
Type ‘()’ cannot conform to ‘View’
// Key field
ZStack {
if chatKey == "" {
chatKey = " "
}
Any idea what the problem might be?
I can share the code if necessary.