Reputation: 1768
Does anyone see what is going wrong in the below code, as the code below works well when encrypting and sending to the server (which means my encryption function is fine), however there seems some problem with my decryption logic which is not decrypting the information correctly.
Note: The server side enc/dec logic works well with other languages e.g. Java
Server-side implementation sample: Nodejs Crypto to Swift commonCrypto
MyEncDec.swift
import Foundation
import CommonCrypto
struct AES256 {
private var key: Data
private var iv: Data
public init(key: Data, iv: Data) throws {
guard key.count == kCCKeySizeAES256 else {
throw Error.badKeyLength
}
guard iv.count == kCCBlockSizeAES128 else {
throw Error.badInputVectorLength
}
self.key = key
self.iv = iv
}
enum Error: Swift.Error {
case keyGeneration(status: Int)
case cryptoFailed(status: CCCryptorStatus)
case badKeyLength
case badInputVectorLength
}
func encrypt(_ digest: Data) throws -> Data {
return try crypt(input: digest, operation: CCOperation(kCCEncrypt))
}
func decrypt(_ encrypted: Data) throws -> Data {
return try crypt(input: encrypted, operation: CCOperation(kCCDecrypt))
}
private func crypt(input: Data, operation: CCOperation) throws -> Data {
var outLength = Int(0)
var outBytes = [UInt8](repeating: 0, count: input.count + kCCBlockSizeAES128)
var status: CCCryptorStatus = CCCryptorStatus(kCCSuccess)
input.withUnsafeBytes { rawBufferPointer in
let encryptedBytes = rawBufferPointer.baseAddress!
iv.withUnsafeBytes { rawBufferPointer in
let ivBytes = rawBufferPointer.baseAddress!
key.withUnsafeBytes { rawBufferPointer in
let keyBytes = rawBufferPointer.baseAddress!
status = CCCrypt(operation,
CCAlgorithm(kCCAlgorithmAES128), // algorithm
CCOptions(kCCOptionPKCS7Padding), // options
keyBytes, // key
key.count, // keylength
ivBytes, // iv
encryptedBytes, // dataIn
input.count, // dataInLength
&outBytes, // dataOut
outBytes.count, // dataOutAvailable
&outLength) // dataOutMoved
}
}
}
guard status == kCCSuccess else {
throw Error.cryptoFailed(status: status)
}
return Data(bytes: &outBytes, count: outLength)
}
static func createKey(password: Data, salt: Data) throws -> Data {
let length = kCCKeySizeAES256
var status = Int32(0)
var derivedBytes = [UInt8](repeating: 0, count: length)
password.withUnsafeBytes { rawBufferPointer in
let passwordRawBytes = rawBufferPointer.baseAddress!
let passwordBytes = passwordRawBytes.assumingMemoryBound(to: Int8.self)
salt.withUnsafeBytes { rawBufferPointer in
let saltRawBytes = rawBufferPointer.baseAddress!
let saltBytes = saltRawBytes.assumingMemoryBound(to: UInt8.self)
status = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), // algorithm
passwordBytes, // password
password.count, // passwordLen
saltBytes, // salt
salt.count, // saltLen
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1), // prf
10000, // rounds
&derivedBytes, // derivedKey
length) // derivedKeyLen
}
}
guard status == 0 else {
throw Error.keyGeneration(status: Int(status))
}
return Data(bytes: &derivedBytes, count: length)
}
static func randomIv() -> Data {
return randomData(length: kCCBlockSizeAES128)
}
static func iV() -> Data {
let arr: [UInt8] = [0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1]
return Data(arr)
}
static func randomSalt() -> Data {
return randomData(length: 8)
}
static func randomData(length: Int) -> Data {
var data = Data(count: length)
var mutableBytes: UnsafeMutableRawPointer!
data.withUnsafeMutableBytes { rawBufferPointer in
mutableBytes = rawBufferPointer.baseAddress!
}
let status = SecRandomCopyBytes(kSecRandomDefault, length, mutableBytes)
assert(status == Int32(0))
return data
}
}
Upvotes: 1
Views: 507
Reputation: 2089
I've just try to run your client side code encrypting and decrypting a sample data and it seems to work properly. Is the issue present only when you are trying to decrypt a data buffer coming from the server?
I've tested your code in the following way:
let keyData = Data("KEY01234567890123456789012345678".utf8)
let data = Data("TEST123".utf8)
let iv = AES256.randomIv()
if let aes = try? AES256.init(key: keyData, iv: iv), let aes2 = try? AES256.init(key: keyData, iv: iv) {
if let encriptedData = try? aes.encrypt(data) {
if let decryptedData = try? aes2.decrypt(encriptedData) {
let decryptedString = String(decoding: decryptedData, as: UTF8.self)
print(decryptedString)
}
}
}
I am not sure about how you are using the AES256 class but taking a look to the server side code:
The encryption function generates a string composition of the iv and the data using ":" as separator.
let final_encrypted = iv.toString('hex') + ':' + encrypted.toString('hex');
So the string coming from the server has to be parsed before decrypting it in order to retrieve iv and data.
func parseAndDecrypt(encryptedString: String) {
let keyData = Data("KEY01234567890123456789012345678".utf8)
let substrings = encryptedString.split(separator: ":")
if let ivString = substrings.first, let dataString = substrings.last {
let iv = Data(ivString.utf8)
let encryptedData = Data(dataString.utf8)
if let aes = try? AES256.init(key: keyData, iv: iv) {
if let decryptedData = try? aes.decrypt(encryptedData) {
let decryptedString = String(decoding: decryptedData, as: UTF8.self)
print(decryptedString)
}
}
}
}
Upvotes: 1
Reputation: 9173
I need to take a guess since you have not provided the code that runs encrypt
and decrypt
. But I think that you provide iv
using randomIV()
on both encryption and decryption side and this will be the problem.
You need to provide same iv
for decryption and encryption side. It means that you need to send your random iv from encryption side to decryption side and use that iv in decryption process.
I'm not experienced in Swift, But I think you need to use your code like this in order to works correctly:
// Encryption side
let keyData = Data("KEY01234567890123456789012345678".utf8)
let data = Data("TEST123".utf8)
let iv = AES256.randomIv()
if let aes = try? AES256.init(key: keyData, iv: iv) {
if let encryptedData = try? aes.encrypt(data) {
var resultData = iv
resultData.append(encryptedData)
// use resultData here
}
}
// Decryption side
let keyData = Data("KEY01234567890123456789012345678".utf8)
let data = resultData
let iv = data.subdata(in: ..<kCCBlockSizeAES128)
let encryptedPart = data.subdata(in: kCCBlockSizeAES128...)
if let aes = try? AES256.init(key: keyData, iv: iv) {
if let decryptedData = try? aes.decrypt(encryptedPart) {
// use decryptedData here
}
}
I used cristallo code as base for this code.
Upvotes: 2