Encode / Decode string to UTF-8 with Emojis - ios

I'm developping a messaging app between Android an iOS (Swift 2.2).
Users must be able to send and receive Emojis : Hello πŸ˜€
I need to encode emojis to UTF-8 instead of Unicode.
Here my code to encode / decode from Unicode :
static func decodeUnicode(input: String) -> String {
let ns = NSString(string: input)
let data:NSData = ns.dataUsingEncoding(NSUTF8StringEncoding)!
if let decoded = NSString(data:data,encoding:NSUTF8StringEncoding) as? String {
return decoded
}
return ""
}
static func encodeUnicode(input: String) -> String {
let data:NSData = input.dataUsingEncoding(NSNonLossyASCIIStringEncoding)!
if let decoded = NSString(data:data,encoding:NSUTF8StringEncoding) as? String {
return decoded
}
return ""
}
Encoding have to be compatible with Android decoding.

Related

Generating an AES key, encrypting and decrypting with CryptoSwift doesn't decrypt back to "Hello World"

I'm building a simple app, where users can add personal diary entries in the form of text only, and it gets saved to my cloud firestore database. Now, I want to build end-to-end encryption so the user's personal content is never readable by anyone but them.
To achieve this, I'm trying to use the CryptoSwift library to encrypt and decrypt content. For testing purposes, I'm starting with just a simple "Hello World" in my ViewModel. However, I'm unable to get it to work.
Couple of questions
I'm unable to get "Hello world" to show up from clear (hello world) > encrypted > cipher > decrypted (hello world). How can I get the same "hello world" result from my decrypt function?
In what format should I save my key to the keychain?
In what format should I save my encrypted text, and iv on firebase? Should I continue to use .toHexString() or should I be using .toBase64()?
I'm successfully able to generate a key, and save it as a "hexString". And I'm able to "run" through both the encrypt and the decrypt functions without errors, but they don't generate a "hello world" from cleartext(hello world) > encrypt > ciphertext > decrypt > helloworld.
class EncryptionService: EncryptionServicing {
private var keychainService = KeychainService()
func generateKey() throws -> String {
print("[EncryptionService] πŸ”‘πŸ”‘πŸ”‘ Generate key called")
do {
if let userId = UserService.shared.userInfo?.userId {
let userId: [UInt8] = Array(userId.utf8)
// Salt added for randomness
let salt: [UInt8] = (1...5).map( {_ in UInt8(Int.random(in: 1...10))} )
let key = try PKCS5.PBKDF2(
password: userId,
salt: salt,
iterations: 4096,
keyLength: 32, /* AES-256 */
variant: .sha2(.sha256)
).calculate()
print("πŸ”‘πŸ”‘πŸ”‘ UserId: \(userId)")
print("πŸ”‘πŸ”‘πŸ”‘ Salt: \(salt)")
print("πŸ”‘πŸ”‘πŸ”‘ KEY: \(key.toHexString())")
return key.toHexString()
}
}
catch {
print(error)
}
return ""
}
func encrypt(clearText: String, aesKey: String) throws -> (cipherText: String, iv: String) {
print("[EncryptionService] βœ… Encrypt called")
do {
let iv = AES.randomIV(AES.blockSize)
let keyArray = [UInt8](hex: aesKey)
let aes = try AES(key: keyArray, blockMode: CBC(iv: iv), padding: .pkcs7)
let cipherText = try aes.encrypt(Array(clearText.utf8))
return (cipherText.toHexString(), iv.toHexString())
}
catch {
print(error)
return ("", "")
}
}
func decrypt(cipherText: String, iv: String, aesKey: String) throws -> String {
print("[EncryptionService] βœ… Decryption called")
do {
print("Ciphertext: \(cipherText)")
print("Iv: \(iv)")
print("aesKey: \(aesKey)")
let keyArray = [UInt8](hex: aesKey)
let aes = try AES(key: keyArray, blockMode: CBC(iv: [UInt8](hex: iv)), padding: .pkcs7)
print("AES Key size: \(aes.keySize)")
let decryptedText = try aes.decrypt(cipherText.bytes)
return decryptedText.toHexString() // doesn't respond with "Hello world!"
}
}
}
This is what my ViewModel looks like.
#Published var dummyClearText: String = "Hello World!"
#Published var dummyCipherText: String = ""
#Published var dummyIv: String = ""
#Published var dummyDecryptedText: String = ""
// Have a "encrypt" button that calls this.
func generateCipherText() {
do {
let result = try EncryptionService().encrypt(clearText: self.dummyClearText, aesKey: self.aesKey)
self.dummyCipherText = result.cipherText
self.dummyIv = result.iv
}
catch {
print(error)
}
}
// Have a "decrypt" button that calls this, and takes the generated cipher text as input.
func generateClearText() {
do {
let result = try EncryptionService().decrypt(cipherText: self.dummyCipherText, iv: self.dummyIv, aesKey: self.aesKey)
self.dummyDecryptedText = result // doesn't respond with "Hello world!"
}
catch {
print(error)
}
}```
Thank you so much, still very much learning how to Swift.
Happy to provide more context.
After decryption, you don't return the decrypted text, you return its hex representation:
return decryptedText.toHexString()
You probably want something like this instead:
// Interpreting the data as UTF-8 can fail. You should handle this.
return String(data: decryptedText, encoding: .utf8) ?? ""

How to encode extended ASCII/macOS Roman (characters from 128 to 255) on 1 byte in Swift?

I'm trying to write a .txt file with extended ASCII code, but I need to do it on 8-bit characters.
I'd love to get extended ASCII from Codepage 437, but I can live with Mac OS Roman. But as it's operation on numbers, it shouldn't make any difference.
When using Character(UnicodeScalar(unicodePosition)), it works well for 0 to 127. Each character is 8-bit. From 128th scalar up, they are not ASCII/macOS Roman and they're encoded on 16 bits.
So I can create an array of UInt8 with specific characters that I want to save to file.
let firstCharacter: UInt8 = 240 // Apple Logo in macOS Roman or "≑" in codepage 437
let secondCharacter: UInt8 = 236 // Infinity symbol on codepage 437 or "I" with two dots in macOS Roman
let listOfCharacters: [UInt8] = [firstCharacter, secondCharacter]
But I have no idea on how to save such a list to a file, and then display it as extendedASCII or macOS Roman encoding.
I need to operate on this numbers because I'm trying to implement Vigenre Cipher for extended ASCII alphabet (or macOS Roman) and I need the 8-bit input to be 8-bit output so the content of the file have exactly the same file size. I have to do it on 256 characters, hence I need extended ascii/macOS Roman.
I'd also need to read this kind of file back, so method for reading a textile encoded with extended ASCII would also be appreciated. I guess that's why there's String.Encoding.nonLossyASCII and not only .ascii?
Codepage 437 is available as CFStringEncodings.dosLatinUS and can be converted to a String.Encoding as in How to use Big5 encoding in Swift on iOS:
let cfEnc = CFStringEncodings.dosLatinUS
let nsEnc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(cfEnc.rawValue))
let encoding = String.Encoding(rawValue: nsEnc) // String.Encoding
Now you can convert the bytes to a string and back:
let bytes = Data([240, 236])
// CP437 to string:
if let string = String(data: bytes, encoding: encoding) {
print(string) // β‰‘βˆž
// String to CP437:
if let bytes2 = string.data(using: encoding) {
print(Array(bytes2)) // [240, 236]
}
}
The simple approach is to start with a String instance, convert it to Data using a specified encoding and then convert it the [UInt8] array:
let text = "The quick brown fox ... éÒ..."
let data = text.data(using: .macOSRoman)
let characters [UInt8](data)
Be carefule with your encryption. Most characters in the range between 0 and 31 cannot be represented in text. They might not occur in the original text. But they will appear in the encrypted text. If you don't avoid it, the result will be binary data that can no longer be converted to readable text.
So my final solution looks like this:
class FileManager {
let encoding: String.Encoding
init() {
let cfEnc = CFStringEncodings.dosLatinUS
let nsEnc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(cfEnc.rawValue))
let encoding = String.Encoding(rawValue: nsEnc)
self.encoding = encoding
}
func loadFromFile(path: String) -> Data {
return NSData(contentsOfFile: path)! as Data
}
func saveToFile(data: Data, path: String) {
let string = dataToPage437String(data: data)
let fileURL = URL(fileURLWithPath: path)
try? string?.write(to: fileURL, atomically: false, encoding: encoding)
}
func page437StringToData(string: String) -> Data? {
return string.data(using: encoding)
}
private func dataToPage437String(data: Data) -> String? {
return String(data: data, encoding: encoding)
}
}
class EncryptionEngine {
func encrypt(originalData: Data, keyData: Data) -> Data {
var encryptedData = [UInt8]()
for (index, byte) in originalData.enumerated() {
let indexInCurrentBlock = index % keyData.count
let row = Int(byte)
let column = Int(keyData[indexInCurrentBlock])
//for pure Vigenère cipher modifier should be 0
let modifier = index + 1
let encryptedCharacter = UInt8((row + column + modifier) % 256)
encryptedData.append(encryptedCharacter)
}
return Data(encryptedData)
}
}
let fileManager = FileManager()
let encryptionEngine = EncryptionEngine()
let originalData = fileManager.loadFromFile(path: "/Path/test2.txt")
guard let keyData = fileManager.page437StringToData(string: "keyToEncryptTakenFromTextField") else { return }
let encryptedData = encryptionEngine.encrypt(originalData: originalData, keyData: keyData)
fileManager.saveToFile(data: encryptedData, path: "/Path/test_enc.txt")

AES 128 Encryption for a String and generating Binary return in Swift

I have a string of data that I want to encrypt in AES 128 and the encrypted data should be in binary. I have tried using CryptoSwith to do so. But the issue I am getting is that the encryption code that I found from an online help converts in into HexString. I tried looking for the conversion to binary but couldn't find a working solution. The code I am using is below :
func encrypt(data: String) -> String? {
if let aes = try? AES(key: "0101010101010101", iv: "0000000000000000"),
let encrypted = try? aes.encrypt(Array(data.utf8)) {
return encrypted.toHexString()
}
return
}
Would really appreciate the help.
Assuming you meant something like this?:
let binary = encrypted.map {
let binary = String($0, radix: 2)
let padding = String(repeating: "0", count: 8 - binary.count)
return padding + binary
}.joined()
Which prints the whole Byte array as a sequence of zeros (0) and ones (1):
100101010101011010101010101010100011010101010101
Your method would then look like:
func encrypt(data: String) -> String? {
if let aes = try? AES(key: "0101010101010101", iv: "0000000000000000"),
let encrypted = try? aes.encrypt(Array(data.utf8)) {
let binary = encrypted.map {
let binary = String($0, radix: 2)
let padding = String(repeating: "0", count: 8 - binary.count)
return padding + binary
}.joined()
return binary
}
return nil
}

UTF8 Encoding in iOS / swift for email containing +

How to encode URL parameters with UTF8 encoding in Query string format URL/param1/param2/param3
I have even tried URLHostAllowedCharacterSet, which encodes special characters but not '+' character.
How to encode email containing + character using almofire request?
Try to add an extension to handle the encoding and then just call the extension when you want to encode.
extension String {
func stringByAddingPercentEncodingForRFC3986() -> String? {
let unreserved = "-._~/?:"
let allowed = NSMutableCharacterSet.alphanumericCharacterSet()
allowed.addCharactersInString(unreserved)
return stringByAddingPercentEncodingWithAllowedCharacters(allowed)
}
}
And then to use it:
let query = "http://test.com/param1& param2+ param3"
let encoded = query.stringByAddingPercentEncodingForRFC3986()!
extension String {
func stringByAddingPercentEncodingForRFC3986() -> String? {
let unreserved = "-._~/?:"
let allowedCharacterSet = NSMutableCharacterSet.alphanumeric()
allowedCharacterSet.addCharacters(in: unreserved)
return self.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet as CharacterSet)
}
}
let encodedVal = usernameTextField.text.stringByAddingPercentEncodingForRFC3986()
--------- OR ----------
extension String {
var urlEncoded: String? {
let allowedCharacterSet = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-._~/?:"))
return self.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet)
}
}
let encodedVal = usernameTextField.text.urlEncoded

URL decode in iOS

I am using Swift 1.2 to develop my iPhone application and I am communicating with a http web service.
The response I am getting is in query string format (key-value pairs) and URL encoded in .Net.
I can get the response, but looking the proper way to decode using Swift.
Sample response is as follows
status=1&message=The+transaction+for+GBP+12.50+was+successful
Tried following way to decode and get the server response
// This provides encoded response String
var responseString = NSString(data: data, encoding: NSUTF8StringEncoding) as! String
var decodedResponse = responseString.stringByReplacingEscapesUsingEncoding(NSUTF8StringEncoding)!
How can I replace all URL escaped characters in the string?
To encode and decode urls create this extention somewhere in the project:
Swift 2.0
extension String
{
func encodeUrl() -> String
{
return self.stringByAddingPercentEncodingWithAllowedCharacters( NSCharacterSet.URLQueryAllowedCharacterSet())
}
func decodeUrl() -> String
{
return self.stringByRemovingPercentEncoding
}
}
Swift 3.0
extension String
{
func encodeUrl() -> String
{
return self.addingPercentEncoding( withAllowedCharacters: NSCharacterSet.urlQueryAllowed())
}
func decodeUrl() -> String
{
return self.removingPercentEncoding
}
}
Swift 4.1
extension String
{
func encodeUrl() -> String?
{
return self.addingPercentEncoding( withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
}
func decodeUrl() -> String?
{
return self.removingPercentEncoding
}
}
Swift 2 and later (Xcode 7)
var s = "aa bb -[:/?&=;+!##$()',*]";
let sEncode = s.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
let sDecode = sEncode?.stringByRemovingPercentEncoding
You only need:
print("Decode: ", yourUrlAsString.removingPercentEncoding)
The stringByReplacingEscapesUsingEncoding method is behaving correctly. The "+" character is not part of percent-encoding. This server is using it incorrectly; it should be using a percent-escaped space here (%20). If, for a particular response, you want spaces where you see "+" characters, you just have to work around the server behavior by performing the substitution yourself, as you are already doing.
It's better to use built-in URLComponents struct, since it follows proper guidelines.
extension URL
{
var parameters: [String: String?]?
{
if let components = URLComponents(url: self, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems
{
var parameters = [String: String?]()
for item in queryItems {
parameters[item.name] = item.value
}
return parameters
} else {
return nil
}
}
}
In my case, I NEED a plus ("+") signal in a phone number in parameters of a query string, like "+55 11 99999-5555". After I discovered that the swift3 (xcode 8.2) encoder don't encode "+" as plus signal, but space, I had to appeal to a workaround after the encode:
Swift 3.0
_strURL = _strURL.replacingOccurrences(of: "+", with: "%2B")
In Swift 3
extension URL {
var parseQueryString: [String: String] {
var results = [String: String]()
if let pairs = self.query?.components(separatedBy: "&"), pairs.count > 0 {
for pair: String in pairs {
if let keyValue = pair.components(separatedBy: "=") as [String]? {
results.updateValue(keyValue[1], forKey: keyValue[0])
}
}
}
return results
}
}
in your code to access below
let parse = url.parseQueryString
print("parse \(parse)" )

Resources