Last active
July 28, 2024 09:34
-
-
Save hfossli/7165dc023a10046e2322b0ce74c596f8 to your computer and use it in GitHub Desktop.
AES 256 in swift 4 with CommonCrypto
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <CommonCrypto/CommonCrypto.h> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
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 { (encryptedBytes: UnsafePointer<UInt8>!) -> () in | |
iv.withUnsafeBytes { (ivBytes: UnsafePointer<UInt8>!) in | |
key.withUnsafeBytes { (keyBytes: UnsafePointer<UInt8>!) -> () in | |
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: UnsafePointer<UInt8>(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 { (passwordBytes: UnsafePointer<Int8>!) in | |
salt.withUnsafeBytes { (saltBytes: UnsafePointer<UInt8>!) in | |
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: UnsafePointer<UInt8>(derivedBytes), count: length) | |
} | |
static func randomIv() -> Data { | |
return randomData(length: kCCBlockSizeAES128) | |
} | |
static func randomSalt() -> Data { | |
return randomData(length: 8) | |
} | |
static func randomData(length: Int) -> Data { | |
var data = Data(count: length) | |
let status = data.withUnsafeMutableBytes { mutableBytes in | |
SecRandomCopyBytes(kSecRandomDefault, length, mutableBytes) | |
} | |
assert(status == Int32(0)) | |
return data | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
extension Data { | |
var hexString: String { | |
return map { String(format: "%02hhx", $0) }.joined() | |
} | |
} | |
class ViewController: UIViewController { | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
do { | |
let digest = "hello world".data(using: .utf8)! | |
let password = "foo" | |
let salt = AES256.randomSalt() | |
let iv = AES256.randomIv() | |
let key = try AES256.createKey(password: password.data(using: .utf8)!, salt: salt) | |
let aes = try AES256(key: key, iv: iv) | |
let encrypted = try aes.encrypt(digest) | |
let decrypted = try aes.decrypt(encrypted) | |
print("Encrypted: \(encrypted.hexString)") | |
print("Decrypted: \(decrypted.hexString)") | |
print("Password: \(password)") | |
print("Key: \(key.hexString)") | |
print("IV: \(iv.hexString)") | |
print("Salt: \(salt.hexString)") | |
print(" ") | |
print("#! /bin/sh") | |
print("echo \(digest.hexString) | xxd -r -p > digest.txt") | |
print("echo \(encrypted.hexString) | xxd -r -p > encrypted.txt") | |
print("openssl aes-256-cbc -K \(key.hexString) -iv \(iv.hexString) -e -in digest.txt -out encrypted-openssl.txt") | |
print("openssl aes-256-cbc -K \(key.hexString) -iv \(iv.hexString) -d -in encrypted.txt -out decrypted-openssl.txt") | |
} catch { | |
print("Failed") | |
print(error) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Encrypted: 76be70e3e63890e12572bc55ed8f95b1 | |
Decrypted: 68656c6c6f20776f726c64 | |
Password: foo | |
Key: 848496354b13779568e1ee81f1f73428678f131c3d7501ef9ed99f7a7b4bf70c | |
IV: 4bb9f6f8bfb7adaad584e032a4cd412f | |
Salt: eedb0c8de0a9d09f | |
---- | |
echo 68656c6c6f20776f726c64 | xxd -r -p > digest.txt | |
echo 76be70e3e63890e12572bc55ed8f95b1 | xxd -r -p > encrypted.txt | |
openssl aes-256-cbc -K 848496354b13779568e1ee81f1f73428678f131c3d7501ef9ed99f7a7b4bf70c -iv 4bb9f6f8bfb7adaad584e032a4cd412f -e -in digest.txt -out encrypted-openssl.txt | |
openssl aes-256-cbc -K 848496354b13779568e1ee81f1f73428678f131c3d7501ef9ed99f7a7b4bf70c -iv 4bb9f6f8bfb7adaad584e032a4cd412f -d -in encrypted.txt -out decrypted-openssl.txt |
it appears that IV should be hardcoded or saved somewhere, because if I use it as random during the encryption and then try to decrypt the string with a new generated IV it does not work. IV bytes are not being cut.
If you need this for Swift 5
`
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
}
}`
very interesting implementation,
could you please publish also the data extension hexString?
How to encrypt image ??
Big question. Lots of good articles on the subject
I used crypto swift but it is slow. any suggestions?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
do you have update for this Swift 4.2 and later warning?
Thanks