1

I need to encode a string to UTF-16LE (and convert to sha1 later), but I'm having some problems. This is what I had tried:

let utf16array = Array("password".utf16)
print(utf16array)
// [112, 97, 115, 115, 119, 111, 114, 100]

But this is what I was expecting:

// [112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0] 

Same thing using utf8array:

let utf8array = "password".utf8.map({ $0 as UInt8 })
// [112, 97, 115, 115, 119, 111, 114, 100]

So, this is what I did to "fix" it:

var bytesArray:[UInt16] = []
for byte in utf16array {
    bytesArray.append(byte)
    bytesArray.append(0)
}
print(bytesArray)
// [112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0]

But I'm sure this is not the right way. Any suggestions?

4

3 回答 3

6

You can get a representation as UTF-16LE data with

let password = "password€"
let data = password.data(using: .utf16LittleEndian)!
print(data as NSData)
// <70006100 73007300 77006f00 72006400 ac20>

That would already be sufficient to compute the SHA1 digest (code from How to crypt string to sha1 with Swift?):

var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
data.withUnsafeBytes { 
    _ = CC_SHA1($0, CC_LONG(data.count), &digest)
}
let hexEncodedDigest = digest.map { String(format: "%02hhx", $0) }.joined()
print(hexEncodedDigest)
// 177f0d080dfe533e102dd67d6321204813cf1b0c

But if you need it as a byte array then

let bytesArray = data.map { $0 }
print(bytesArray)
// [112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 172, 32]

would work.

(I have appended a non-ASCII characters for demonstration, € = U+20AC becomes 172, 32.)


If you are curious how to convert the [UInt16] array to an [UInt8] array, this is how you could do it with some pointer juggling (and just a single copy):

let utf16array = Array("password€".utf16)
print(utf16array)
// [112, 97, 115, 115, 119, 111, 114, 100, 8364]

let bytes = Array(utf16array.withUnsafeBufferPointer {
    $0.baseAddress!.withMemoryRebound(to: UInt8.self, capacity: 2 * utf16array.count) {
        UnsafeBufferPointer(start: $0, count: 2 * utf16array.count)
    }
})
print(bytes)
// [112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 172, 32]
于 2016-10-18T19:37:44.437 回答
0

Using the String to Data method data(using:) is the fit for purpose solution here.

But for the fun of it, an alternative making use of the String:s UTF-16 encoding approach (utf16 property of String) of the OP. Using the init(truncatingBitPattern: UInt16) initializer of UInt8, in combination with zip followed by a flatMap, the latter flattening the zipped tuples into an array:

let pw = "password€"

let bytes = zip(pw.utf16.map{ UInt8(truncatingBitPattern: $0) },
                pw.utf16.map{ UInt8(truncatingBitPattern: $0 >> 8) })
            .flatMap{ [$0, $1] }

print(bytes)
// [112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 172, 32]
于 2016-10-18T19:25:29.713 回答
0

What about using the cString accessor?

var bytes = str.cStringUsingEncoding(NSUTF16LittleEndianStringEncoding)
于 2016-10-18T20:02:39.190 回答