Swift Sha256 Encryption Returns Different Encrypted String Compare to Objective C

Swift SHA256 encryption returns different encrypted string compare to Objective C

The problem is that cKey and cData include the terminating null character of the strings, and in the Swift version that is counted in cKey.count and cData.count, whereas in the Objective-C version strlen(cKey) and strlen(cData) do not count the terminating null character of the strings.

Doing

 CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), cKey, strlen(key), cData, strlen("message", &digest)

instead would fix the issue in your example, but is not safe for non-ASCII characters.

What I actually would do is to convert the strings to Data values (which do not include a terminating null byte) with the UTF-8 representation. Then pass the underlying byte buffers to the encryption method:

let key = "1234567890123456789012345678901234567890123456789012345678901234"
let cKey = Data(key.utf8)
let cData = Data("message".utf8)
var digest = [CUnsignedChar](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
cKey.withUnsafeBytes { keyPtr in
cData.withUnsafeBytes { dataPtr in
CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), keyPtr.baseAddress, cKey.count, dataPtr.baseAddress, cData.count, &digest)
}
}
let hash = Data(digest)
let encryptedString = hash.base64EncodedString()

This produces the same result ZNjnsz2Uv5L0PvWIJjSh0BrOovuRXOSFWQ0s1Rd8VSM= as your Objective-C code.

SHA256 in swift

You have to convert explicitly between Int and CC_LONG, because Swift does not
do implicit conversions, as in (Objective-)C.

You also have to define hash as an array of the required size.

func sha256(data : NSData) -> NSData {
var hash = [UInt8](count: Int(CC_SHA256_DIGEST_LENGTH), repeatedValue: 0)
CC_SHA256(data.bytes, CC_LONG(data.length), &hash)
let res = NSData(bytes: hash, length: Int(CC_SHA256_DIGEST_LENGTH))
return res
}

Alternatively, you can use NSMutableData to allocate the needed buffer:

func sha256(data : NSData) -> NSData {
let res = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH))
CC_SHA256(data.bytes, CC_LONG(data.length), UnsafeMutablePointer(res.mutableBytes))
return res
}

Update for Swift 3 and 4:

func sha256(data : Data) -> Data {
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA256($0, CC_LONG(data.count), &hash)
}
return Data(bytes: hash)
}

Update for Swift 5:

func sha256(data : Data) -> Data {
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
}
return Data(hash)
}

Decrypting string saved in keychain

Nothing in the above code encrypts anything. You appear to be running your value through NSString hash and then SHA-256. Both of these are one-way hashes. By design, they cannot be reversed.

In general, this code is very confusing, and it's not clear what you're trying to achieve. You don't usually encrypt data that you're putting into keychain.

Note that your hash function will truncate multi-byte strings (Chinese, for example).

const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:input.length];

This should be:

NSData *data = [input dataUsingEncoding:NSUTF8StringEncoding];

How to decrypt a SHA-256 encrypted string?

SHA-256 is a cryptographic (one-way) hash function, so there is no direct way to decode it. The entire purpose of a cryptographic hash function is that you can't undo it.

One thing you can do is a brute-force strategy, where you guess what was hashed, then hash it with the same function and see if it matches. Unless the hashed data is very easy to guess, it could take a long time though.

You may find the question "Difference between hashing a password and encrypting it" interesting.

decoding SHA256 Objective C

The SHA (Secure Hash Algorithm) is one of a number of cryptographic hash functions. A cryptographic hash is like a signature for a text or a data file. SHA-256 algorithm generates an almost-unique, fixed size 256-bit (32-byte) hash. Hash is a one way function – it cannot be decrypted back. This makes it suitable for password validation, challenge hash authentication, anti-tamper, digital signatures.

Hope this info helps you..

aesEncrypt produces different results

Disclaimer: I can't run the question code. Among other things it is not complete, the extension declaration is missing. Also it seems to be Swift 2 code, this needs to be at least updated to Swift 3.

encryption: cryptdata

<01b9f69b 45deb31d eda46c2d dc9ad9e8 00000000 00>

Is completely wrong, it is even the wrong length Encrypted data will be a multiple of the block size.

With PKCS#7 padding and CBC mode the encrypted result should be: C99A30D8DA44968418E8B66F42790216. See Cyyptomathic AES CALCULATOR. Note the 0b0b0b0b0b0b0b0b0b0b0b is the PKCS#7 padding.

Here is an example in Swift 3, this is not production code, it is missing error handling at a minimum.

func SHA256(string:String) -> Data {
let data = string.data(using:.utf8)!
var hashData = Data(count: Int(CC_SHA256_DIGEST_LENGTH))

_ = hashData.withUnsafeMutableBytes {digestBytes in
data.withUnsafeBytes {messageBytes in
CC_SHA256(messageBytes, CC_LONG(data.count), digestBytes)
}
}
return hashData
}

func aesCBCEncrypt(data:Data, keyData:Data, ivData:Data) -> Data {
let cryptLength = size_t(kCCBlockSizeAES128 + data.count + kCCBlockSizeAES128)
var cryptData = Data(count:cryptLength)
var numBytesEncrypted :size_t = 0

let cryptStatus = cryptData.withUnsafeMutableBytes {cryptBytes in
data.withUnsafeBytes {dataBytes in
keyData.withUnsafeBytes {keyBytes in
ivData.withUnsafeBytes {ivBytes in
CCCrypt(CCOperation(kCCEncrypt),
CCAlgorithm(kCCAlgorithmAES),
CCOptions(kCCOptionPKCS7Padding),
keyBytes, keyData.count,
ivBytes,
dataBytes, data.count,
cryptBytes, cryptLength,
&numBytesEncrypted)
}}}}

cryptData.count = (cryptStatus == kCCSuccess) ? numBytesEncrypted : 0

return cryptData;
}

let keyString = "sample_key"
let keyData = SHA256(string:keyString)
print("keyString: \(keyString)")
print("keyData: \(hexEncode(keyData))")

let clearData = hexDecode("68656c6c6f")
// let keyData = hexDecode("d5a78c66e9b3ed40b3a92480c732527f1a919fdcf68957d2b7e9218f6221085d")
let ivData = hexDecode("00000000000000000000000000000000")

print("clearData: \(hexEncode(clearData))")
print("keyData: \(hexEncode(keyData))")
print("ivData: \(hexEncode(ivData))")

let cryptData = aesCBCEncrypt(data:clearData, keyData:keyData, ivData:ivData)
print("cryptData: \(hexEncode(cryptData))")

Output:


keyString: sample_key
keyData: d5a78c66e9b3ed40b3a92480c732527f1a919fdcf68957d2b7e9218f6221085d

clearData: 68656c6c6f
keyData: d5a78c66e9b3ed40b3a92480c732527f1a919fdcf68957d2b7e9218f6221085d
ivData: 00000000000000000000000000000000
cryptData: c99a30d8da44968418e8b66f42790216

How to encrypt with AES 256 CBC in Objective C

Thanks Nirav Kotecha for your answer.

I ended up using CrytoSwift and Add Extension class NSString and String to call it.

AES-256 produces different ciphertexts in iOS and Node.js with same password

You don't need to use CryptoJS, because node.js' crypto module provides everything you need for this to work. CryptoJS has a different binary representation than node.js' native Buffer, so there will be problem using both in conjunction.

Problems:

  • You're using crypto.createCipher() which will derive the key from a password on its own in an OpenSSL compatible format. You want to use crypto.createCipheriv().
  • You're not passing an IV to in Objective-C which defaults to a zero filled IV. You need to do the same in node.js by initializing a zero-filled Buffer.
  • You provide the key in Base64 encoded form in node.js, but you have to provide the bytes (Buffer).
  • Since the key size is 256 bit you're actually using AES-256 and not AES-128. The CommonCrypto code seems to change automatically to 256 bit despite specifying 128 bit, but node.js requires you to specify 256 bit explicitly. Also, "aes128" or "aes256" will default to ECB mode in node.js, but CommonCrypto defaults to CBC mode, so you need to explicitly specify this.

Full working code:

var crypto = require('crypto');
var password = "1234567890123456";
var salt = "gettingsaltyfoo!";

var sha256 = crypto.createHash("sha256");
sha256.update(salt);
var hash = sha256.digest();

var key = crypto.pbkdf2Sync(password, hash, 1000, 32, "sha1");

var iv = new Buffer(16);
iv.fill(0);

var algorithm = 'aes-256-cbc';

function encrypt(text){
var cipher = crypto.createCipheriv(algorithm, key, iv);
var crypted = cipher.update(text,'utf8','base64');
crypted += cipher.final('base64');
return crypted;
}

function decrypt(text){
var decipher = crypto.createDecipheriv(algorithm, key, iv);
var dec = decipher.update(text,'base64','utf8');
dec += decipher.final('utf8');
return dec;
}

console.log(encrypt("Hello World"));

Output:


vfOzya0yV9G5hLHeSh3R1g==

Other considerations:

  • You need to generate a random IV for every encryption that you do. If you don't do this, then an attacker may see that you encrypted the same message multiple times without actually decrypting it if you use the same key every time. Since you derive a key from a password, then you can do this a bit better by generating a random salt and derive 384 bit (48 byte) from PBKDF2. Use the first 32 byte for the key and the rest for the IV.

  • You need to authenticate the ciphertexts. If you don't then an attacker might mount a padding oracle attack on your system. You can easily do this by running an HMAC over the ciphertext and send the resulting tag along with it. You can then verify the tag before decryption by running the HMAC again over the received ciphertext in order to check for manipulation.

    Or you could use an authenticated mode like GCM.



Related Topics



Leave a reply



Submit