How to Decode Utf8-Literals Like "\Xc3\Xa6" in Swift 5

How do you decode utf8-literals like \xc3\xa6 in Swift 5?

You'll just have to parse the string, convert each hex string to a UInt8, and then decode that with String.init(byte:encoding:):

let s = "\\xc3\\xa6"
let bytes = s
.components(separatedBy: "\\x")
// components(separatedBy:) would produce an empty string as the first element
// because the string starts with "\x". We drop this
.dropFirst()
.compactMap { UInt8($0, radix: 16) }
if let decoded = String(bytes: bytes, encoding: .utf8) {
print(decoded)
} else {
print("The UTF8 sequence was invalid!")
}

Decoding strings including utf8-literals like '\xc3\xa6' in Swift?

To start with add the decoding code into a String extension as a computed property (or create a function)

extension String {
var decodeUTF8: String {
let bytes = self.components(separatedBy: "\\x")
.dropFirst()
.compactMap { UInt8($0, radix: 16) }
return String(bytes: bytes, encoding: .utf8) ?? self
}
}

Then use a regular expression and match using a while loop to replace all matching values

while let range = string.range(of: #"(\\x[a-f0-9]{2}){2}"#, options: [.regularExpression, .caseInsensitive]) {
string.replaceSubrange(range, with: String(string[range]).decodeUTF8)
}

AES encryption between iOS and Python

Edited to final answer with both ECB and CBC working between iOS and Python:

With credit to others who built the origional NSData_AESCrypt code at:

//  AES Encrypt/Decrypt
// Created by Jim Dovey and 'Jean'
// See http://iphonedevelopment.blogspot.com/2009/02/strong-encryption-for-cocoa-cocoa-touch.html
//
// BASE64 Encoding/Decoding
// Copyright (c) 2001 Kyle Hammond. All rights reserved.
// Original development by Dave Winer.
//
// Put together by Michael Sedlaczek, Gone Coding on 2011-02-22
//

On iOS, encryption logic is modified from the original NSData+AESCrypt is modified as:

@implementation NSData (AESCrypt)

- (NSData *)AES256EncryptWithKey:(NSString *)key
{
return [self AES256EncryptWithKey:key ECB:false];
}
- (NSData *)AES256EncryptWithKey:(NSString *)key ECB:(Boolean) ecb
{
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256 + 1]; // room for terminator (unused)
bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)

// fetch key data
[key getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];

// create results buffer with extra space for padding
NSUInteger dataLength = [self length];
size_t bufferSize = dataLength + kCCKeySizeAES256;
void *buffer = malloc( bufferSize );

size_t numBytesEncrypted = 0;
NSString *hardCodeIV = @"1111111111111111";
char iv[17];
bzero( iv, sizeof( iv ) );
[hardCodeIV getCString:iv maxLength:sizeof(iv) encoding:NSUTF8StringEncoding];
//CBC
CCCryptorStatus cryptStatus = kCCSuccess;
if (ecb == false)
{
cryptStatus = CCCrypt( kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
iv ,
[self bytes], dataLength,
buffer, bufferSize,
&numBytesEncrypted );
} else
{
// ECB
cryptStatus = CCCrypt( kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode,
keyPtr, kCCKeySizeAES256,
NULL,
[self bytes], dataLength,
buffer, bufferSize,
&numBytesEncrypted );
}
if( cryptStatus == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
}

free( buffer ); //free the buffer
return nil;
}
- (NSData *)AES256DecryptWithKey:(NSString *)key
{
return [self AES256DecryptWithKey:key ECB:false];
}
- (NSData *)AES256DecryptWithKey:(NSString *)key ECB:(Boolean) ecb
{
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)

// fetch key data
[key getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];

NSUInteger dataLength = [self length];
size_t bufferSize = dataLength + kCCKeySizeAES256;
void *buffer = malloc( bufferSize );

size_t numBytesDecrypted = 0;
NSString *hardCodeIV = @"1111111111111111";
char iv[17];
bzero( iv, sizeof( iv ) );
[hardCodeIV getCString:iv maxLength:sizeof(iv) encoding:NSUTF8StringEncoding];
CCCryptorStatus cryptStatus = kCCSuccess;
if (ecb == false)
{
cryptStatus = CCCrypt( kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
iv ,
[self bytes], dataLength,
buffer, bufferSize,
&numBytesDecrypted );
} else {
cryptStatus = CCCrypt( kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode,
keyPtr, kCCKeySizeAES256,
NULL,
[self bytes], dataLength,
buffer, bufferSize,
&numBytesDecrypted );
}
if( cryptStatus == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
}

free( buffer ); //free the buffer
return nil;
}

The resulting NSData element is then base64 encoded using some helper classes (used unmodified from the class) as:

static char encodingTable[64] = 
{
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
};
+ (NSData *)dataWithBase64EncodedString:(NSString *)string
{
return [[NSData allocWithZone:nil] initWithBase64EncodedString:string];
}

- (id)initWithBase64EncodedString:(NSString *)string
{
NSMutableData *mutableData = nil;

if( string )
{
unsigned long ixtext = 0;
unsigned long lentext = 0;
unsigned char ch = 0;
unsigned char inbuf[4], outbuf[3];
short i = 0, ixinbuf = 0;
BOOL flignore = NO;
BOOL flendtext = NO;
NSData *base64Data = nil;
const unsigned char *base64Bytes = nil;

// Convert the string to ASCII data.
base64Data = [string dataUsingEncoding:NSASCIIStringEncoding];
base64Bytes = [base64Data bytes];
mutableData = [NSMutableData dataWithCapacity:base64Data.length];
lentext = base64Data.length;

while( YES )
{
if( ixtext >= lentext ) break;
ch = base64Bytes[ixtext++];
flignore = NO;

if( ( ch >= 'A' ) && ( ch <= 'Z' ) ) ch = ch - 'A';
else if( ( ch >= 'a' ) && ( ch <= 'z' ) ) ch = ch - 'a' + 26;
else if( ( ch >= '0' ) && ( ch <= '9' ) ) ch = ch - '0' + 52;
else if( ch == '+' ) ch = 62;
else if( ch == '=' ) flendtext = YES;
else if( ch == '/' ) ch = 63;
else flignore = YES;

if( ! flignore )
{
short ctcharsinbuf = 3;
BOOL flbreak = NO;

if( flendtext )
{
if( ! ixinbuf ) break;
if( ( ixinbuf == 1 ) || ( ixinbuf == 2 ) ) ctcharsinbuf = 1;
else ctcharsinbuf = 2;
ixinbuf = 3;
flbreak = YES;
}

inbuf [ixinbuf++] = ch;

if( ixinbuf == 4 )
{
ixinbuf = 0;
outbuf [0] = ( inbuf[0] << 2 ) | ( ( inbuf[1] & 0x30) >> 4 );
outbuf [1] = ( ( inbuf[1] & 0x0F ) << 4 ) | ( ( inbuf[2] & 0x3C ) >> 2 );
outbuf [2] = ( ( inbuf[2] & 0x03 ) << 6 ) | ( inbuf[3] & 0x3F );

for( i = 0; i < ctcharsinbuf; i++ )
[mutableData appendBytes:&outbuf[i] length:1];
}

if( flbreak ) break;
}
}
}

self = [self initWithData:mutableData];
return self;
}

#pragma mark -

- (NSString *)base64Encoding
{
return [self base64EncodingWithLineLength:0];
}

- (NSString *)base64EncodingWithLineLength:(NSUInteger)lineLength
{
const unsigned char *bytes = [self bytes];
NSMutableString *result = [NSMutableString stringWithCapacity:self.length];
unsigned long ixtext = 0;
unsigned long lentext = self.length;
long ctremaining = 0;
unsigned char inbuf[3], outbuf[4];
unsigned short i = 0;
unsigned short charsonline = 0, ctcopy = 0;
unsigned long ix = 0;

while( YES )
{
ctremaining = lentext - ixtext;
if( ctremaining <= 0 ) break;

for( i = 0; i < 3; i++ )
{
ix = ixtext + i;
if( ix < lentext ) inbuf[i] = bytes[ix];
else inbuf [i] = 0;
}

outbuf [0] = (inbuf [0] & 0xFC) >> 2;
outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4);
outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6);
outbuf [3] = inbuf [2] & 0x3F;
ctcopy = 4;

switch( ctremaining )
{
case 1:
ctcopy = 2;
break;
case 2:
ctcopy = 3;
break;
}

for( i = 0; i < ctcopy; i++ )
[result appendFormat:@"%c", encodingTable[outbuf[i]]];

for( i = ctcopy; i < 4; i++ )
[result appendString:@"="];

ixtext += 3;
charsonline += 4;

if( lineLength > 0 )
{
if( charsonline >= lineLength )
{
charsonline = 0;
[result appendString:@"\n"];
}
}
}

return [NSString stringWithString:result];
}

The resulting base64 encoded string is then sent to the cloud where python pycryptodome can decrypt it as:

    def aesCBCEncrypt(self,key,stri):
if isinstance(key, str):
key = key.encode('utf-8')
cipher = AES.new(key, AES.MODE_CBC, self.nonce) # , nonce=self.nonce)
if isinstance(stri, str):
data = stri.encode('utf-8')
try:
data = pad(data, 16)
ciphertext = cipher.encrypt(data)
ciphertext = b64encode(ciphertext)
ret = ciphertext.decode('utf-8')
except:
print("Some Error")
ret = ""
return ret

def aesCBCDecrypt(self,key,data):
if isinstance(key, str):
key = key.encode('utf-8')
cipher = AES.new(key, AES.MODE_CBC, self.nonce) # , nonce=self.nonce)
if isinstance(data, str):
data = data.encode('utf-8')
data = b64decode(data)
try:
result = cipher.decrypt(data)
result = unpad(result, 16)
ret = result.decode('utf-8')
except:
print("Some Error")
ret = ""
return ret

def aesECBEncrypt(self,key,stri):
if isinstance(key, str):
key = key.encode('utf-8')
cipher = AES.new(key, AES.MODE_ECB) # , nonce=self.nonce)
if isinstance(stri, str):
data = stri.encode('utf-8')
try:
data = pad(data,16)
ciphertext = cipher.encrypt(data)
ciphertext = b64encode(ciphertext)
ret = ciphertext.decode('utf-8')
except:
print("Some Error")
ret = ""
return ret

def aesECBDecrypt(self,key,data):
if isinstance(key, str):
key = key.encode('utf-8')
cipher = AES.new(key, AES.MODE_ECB) #, nonce=self.nonce)
if isinstance(data, str):
data = data.encode('utf-8')
data = b64decode(data)
try:
result = cipher.decrypt(data)
result = unpad(result,16)
ret = result.decode('utf-8')
except:
print("Some Error")
ret = ""
return ret

It is extremely important that the IV (aka nonce) is the same on iOS and Python when using CBC - it will not work otherwise. This is set in this case to a string of 16 '1' characters terminated with a null. This is not really a secret key in itself, but it is likely worth changing it and securing it (possibly sending it as well with asymmetric, RSA in my case, encryption). The AES however is the critical key and should certainly be sent encrypted between the devices.

Finally, I'd recommend using the CBC even though the IV needs to be considered, as it is more secure. And when I have time I will look into integration of the Swift only Apple Crypto Kit library to support other forms as well...



Related Topics



Leave a reply



Submit