1

To keep this simple, I'll only be encoding/decoding a single byte.

If I encode the byte 127, I get the base64 string "fw==" which can be successfully decoded back to byte 127. If, however, I encode a byte ≥ 128, then even though I can produce a base64 string without error (for example, byte 128 gives the string "gA=="), I get an error when I try to decode it.

Here's my code which can be copy-pasted into any Xcode playground to reproduce the problem:

func stringToByteArray(string: String) -> [UInt8] {
    var bytes: [UInt8] = [];
    for code in string.utf8 {
        bytes.append(UInt8(code));
    }
    return bytes;
}

func byteArrayToBase64(bytes: [UInt8]) -> String {
    let nsdata: NSData = NSData(bytes: bytes as [Byte], length: bytes.count)
    let base64Encoded: NSString = nsdata.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0));
    return String(base64Encoded);
}

func base64ToByteArray(base64String: String) -> [UInt8] {
    let nsdata: NSData = NSData(base64EncodedString: base64String, options: NSDataBase64DecodingOptions(rawValue: 0))!
    let base64Decoded: NSString = NSString(data: nsdata, encoding: NSUTF8StringEncoding)!
    return stringToByteArray(String(base64Decoded));
}

/* Replacing 127 with 128 below or greater produces an error */
var testString = byteArrayToBase64([127]);
base64ToByteArray(testString)
0

1 Answer 1

5

The problem is here:

let base64Decoded: NSString = NSString(data: nsdata, encoding: NSUTF8StringEncoding)!

You convert the decoded data to a string. This fails for [128] because that does not represent a valid UTF-8 sequence.

Here is a version that avoids the intermediate string:

func base64ToByteArray(base64String: String) -> [UInt8] {
    let nsdata: NSData = NSData(base64EncodedString: base64String, options: NSDataBase64DecodingOptions(rawValue: 0))!
    // Create array of the required size ...
    var bytes = [UInt8](count: nsdata.length, repeatedValue: 0)
    // ... and fill it with the data
    nsdata.getBytes(&bytes)
    return bytes
}

Remarks:

  • options: NSDataBase64DecodingOptions(rawValue: 0) can be simplified to options: nil.
  • There are some unnecessary type annotations and conversions in your code.
  • Your function crashes if baseString is not a valid Base64 string. You could change it to return an optional.

Then it would look like this:

func byteArrayToBase64(bytes: [UInt8]) -> String {
    let nsdata = NSData(bytes: bytes, length: bytes.count)
    let base64Encoded = nsdata.base64EncodedStringWithOptions(nil);
    return base64Encoded;
}

func base64ToByteArray(base64String: String) -> [UInt8]? {
    if let nsdata = NSData(base64EncodedString: base64String, options: nil) {
        var bytes = [UInt8](count: nsdata.length, repeatedValue: 0)
        nsdata.getBytes(&bytes)
        return bytes
    }
    return nil // Invalid input
}

Example usage:

let testString = byteArrayToBase64([127, 128, 0, 130]);
println(testString) // Output: f4AAgg==
if let result = base64ToByteArray(testString) {
    println(result) // Output: [127, 128, 0, 130]
} else {
    println("failed")
}

Update for Swift 2 / Xcode 7:

func byteArrayToBase64(bytes: [UInt8]) -> String {
    let nsdata = NSData(bytes: bytes, length: bytes.count)
    let base64Encoded = nsdata.base64EncodedStringWithOptions([]);
    return base64Encoded;
}

func base64ToByteArray(base64String: String) -> [UInt8]? {
    if let nsdata = NSData(base64EncodedString: base64String, options: []) {
        var bytes = [UInt8](count: nsdata.length, repeatedValue: 0)
        nsdata.getBytes(&bytes, length: bytes.count)
        return bytes
    }
    return nil // Invalid input
}

let testString = byteArrayToBase64([127, 128, 0, 130]);
print(testString) // Output: f4AAgg==
if let result = base64ToByteArray(testString) {
    print(result) // Output: [127, 128, 0, 130]
} else {
    print("failed")
}

Update for Swift 3 and later:

func byteArrayToBase64(bytes: [UInt8]) -> String {
    let data = Data(bytes)
    let base64Encoded = data.base64EncodedString()
    return base64Encoded;
}

func base64ToByteArray(base64String: String) -> [UInt8]? {
    guard let data = Data(base64Encoded: base64String)  else {
        return nil
    }
    return Array(data)
}

let testString = byteArrayToBase64(bytes: [127, 128, 0, 130]);
print(testString) // Output: f4AAgg==
if let result = base64ToByteArray(base64String: testString) {
    print(result) // Output: [127, 128, 0, 130]
} else {
    print("failed")
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.