使用 Swift,加密存储对象的最佳方法是什么?

San*_*492 2 encryption macos ios swift

我正在为一位在计算机上存储客户信息的朋友开发数据管理器。这些信息是公共记录,因此最高安全性并不是什么大问题(他目前以纯文本形式存储),但他不介意进行某种加密。在不详细介绍该程序的情况下,我将不得不过度简化我的问题......

如果我想加密文本对象或图片对象,并在写入磁盘之前用密码锁定它,我的最佳选择是什么?我计划在 macOS 和 iOS 上实现这一点,这样他就可以向任何人/任何设备发送和共享文件。因此对于 iOS 来说,速度是一件好事,而减小文件大小则有利于移动数据的使用。

我是 Apple 开发新手,所以我仍在筛选 API 和框架来学习所有内容,所以我很抱歉问了一个可能很简单的问题。这也是期末周,所以我只在业余时间做这件事,并且总是感谢一些帮助。

谢谢!——桑德斯

zap*_*aph 5

对称加密用于加密数据,AES(高级加密标准)是当前标准。Apple 在 Security.framework 中提供 Common Crypto,并在 iOS 上快速使用适用的 OSX 指令和硬件加密。另外,将密钥存储在钥匙串中。

已弃用文档部分的示例:

CBC 模式下的 AES 加密,具有随机 IV (Swift 3+)

iv 是加密数据的前缀

aesCBC128Encrypt将创建一个随机 IV 并添加到加密代码的前缀。
aesCBC128Decrypt将在解密期间使用前缀 IV。

输入是数据,键是数据对象。如果需要在调用方法中进行转换和/或转换,则需要使用 Base64 等编码形式。

密钥的长度应恰好为 128 位(16 字节)、192 位(24 字节)或 256 位(32 字节)。如果使用其他密钥大小,则会抛出错误。

默认情况下设置PKCS#7 填充。

此示例需要 Common Crypto ,
因此必须有一个到项目的桥接标头:
#import <CommonCrypto/CommonCrypto.h>
将 添加Security.framework到项目中。

这是示例,而不是生产代码。

enum AESError: Error {
    case KeyError((String, Int))
    case IVError((String, Int))
    case CryptorError((String, Int))
}

// The iv is prefixed to the encrypted data
func aesCBCEncrypt(data:Data, keyData:Data) throws -> Data {
    let keyLength = keyData.count
    let validKeyLengths = [kCCKeySizeAES128, kCCKeySizeAES192, kCCKeySizeAES256]
    if (validKeyLengths.contains(keyLength) == false) {
        throw AESError.KeyError(("Invalid key length", keyLength))
    }

    let ivSize = kCCBlockSizeAES128;
    let cryptLength = size_t(ivSize + data.count + kCCBlockSizeAES128)
    var cryptData = Data(count:cryptLength)

    let status = cryptData.withUnsafeMutableBytes {ivBytes in
        SecRandomCopyBytes(kSecRandomDefault, kCCBlockSizeAES128, ivBytes)
    }
    if (status != 0) {
        throw AESError.IVError(("IV generation failed", Int(status)))
    }

    var numBytesEncrypted :size_t = 0
    let options   = CCOptions(kCCOptionPKCS7Padding)

    let cryptStatus = cryptData.withUnsafeMutableBytes {cryptBytes in
        data.withUnsafeBytes {dataBytes in
            keyData.withUnsafeBytes {keyBytes in
                CCCrypt(CCOperation(kCCEncrypt),
                        CCAlgorithm(kCCAlgorithmAES),
                        options,
                        keyBytes, keyLength,
                        cryptBytes,
                        dataBytes, data.count,
                        cryptBytes+kCCBlockSizeAES128, cryptLength,
                        &numBytesEncrypted)
            }
        }
    }

    if UInt32(cryptStatus) == UInt32(kCCSuccess) {
        cryptData.count = numBytesEncrypted + ivSize
    }
    else {
        throw AESError.CryptorError(("Encryption failed", Int(cryptStatus)))
    }

    return cryptData;
}

// The iv is prefixed to the encrypted data
func aesCBCDecrypt(data:Data, keyData:Data) throws -> Data? {
    let keyLength = keyData.count
    let validKeyLengths = [kCCKeySizeAES128, kCCKeySizeAES192, kCCKeySizeAES256]
    if (validKeyLengths.contains(keyLength) == false) {
        throw AESError.KeyError(("Invalid key length", keyLength))
    }

    let ivSize = kCCBlockSizeAES128;
    let clearLength = size_t(data.count - ivSize)
    var clearData = Data(count:clearLength)

    var numBytesDecrypted :size_t = 0
    let options   = CCOptions(kCCOptionPKCS7Padding)

    let cryptStatus = clearData.withUnsafeMutableBytes {cryptBytes in
        data.withUnsafeBytes {dataBytes in
            keyData.withUnsafeBytes {keyBytes in
                CCCrypt(CCOperation(kCCDecrypt),
                        CCAlgorithm(kCCAlgorithmAES128),
                        options,
                        keyBytes, keyLength,
                        dataBytes,
                        dataBytes+kCCBlockSizeAES128, clearLength,
                        cryptBytes, clearLength,
                        &numBytesDecrypted)
            }
        }
    }

    if UInt32(cryptStatus) == UInt32(kCCSuccess) {
        clearData.count = numBytesDecrypted
    }
    else {
        throw AESError.CryptorError(("Decryption failed", Int(cryptStatus)))
    }
    
    return clearData;
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

let clearData = "clearData0123456".data(using:String.Encoding.utf8)!
let keyData   = "keyData890123456".data(using:String.Encoding.utf8)!
print("clearData:   \(clearData as NSData)")
print("keyData:     \(keyData as NSData)")

var cryptData :Data?
do {
    cryptData = try aesCBCEncrypt(data:clearData, keyData:keyData)
    print("cryptData:   \(cryptData! as NSData)")
}
catch (let status) {
    print("Error aesCBCEncrypt: \(status)")
}

let decryptData :Data?
do {
    let decryptData = try aesCBCDecrypt(data:cryptData!, keyData:keyData)
    print("decryptData: \(decryptData! as NSData)")
}
catch (let status) {
    print("Error aesCBCDecrypt: \(status)")
}
Run Code Online (Sandbox Code Playgroud)

示例输出:

clearData:   <636c6561 72446174 61303132 33343536>
keyData:     <6b657944 61746138 39303132 33343536>
cryptData:   <92c57393 f454d959 5a4d158f 6e1cd3e7 77986ee9 b2970f49 2bafcf1a 8ee9d51a bde49c31 d7780256 71837a61 60fa4be0>
decryptData: <636c6561 72446174 61303132 33343536>
Run Code Online (Sandbox Code Playgroud)

注意:
CBC 模式示例代码的一个典型问题是它将随机 IV 的创建和共享留给了用户。该示例包括 IV 的生成、加密数据的前缀以及在解密期间使用前缀 IV。这使临时用户无需考虑CBC 模式所需的详细信息。

为了安全起见,加密数据还应该进行身份验证,此示例代码没有提供身份验证,以便较小并允许与其他平台更好的互操作性。

还缺少从密码中导出密钥的密钥,建议使用PBKDF2,因为文本密码用作密钥材料。

有关强大的生产就绪多平台加密代码,请参阅RNCryptor。还可以考虑使用 RNCryptor来实现完整的实现,包括密码派生、加密身份验证和版本控制。

注意密码和密钥:

密码和密钥之间是有区别的。AES 加密密钥恰好是 3 个长度之一:128、192 或 256 位,并且应该显示为随机位。密码/密码短语是人类可读的文本。当使用密码时,需要从中导出加密密钥,有一些函数可以实现此目的,例如 PBKDF2(基于密码的密钥导出函数 2)。RNCryptor 包含这样的导出函数。

基于密码的密钥派生 2 (Swift 3+)

基于密码的密钥派生既可用于从密码文本派生加密密钥,也可用于保存密码以进行身份​​验证。

可以使用多种哈希算法,包括本示例代码提供的 SHA1、SHA256、SHA512。

rounds 参数用于使计算变慢,以便攻击者必须在每次尝试上花费大量时间。典型的延迟值在 100ms 到 500ms 之间,如果性能不可接受,可以使用更短的值。

此示例需要 Common Crypto ,
因此必须有一个到项目的桥接标头:
#import <CommonCrypto/CommonCrypto.h>
将 添加Security.framework到项目中。

参数:

password     password String  
salt         salt Data  
keyByteCount number of key bytes to generate
rounds       Iteration rounds

returns      Derived key


func pbkdf2SHA1(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    return pbkdf2(hash:CCPBKDFAlgorithm(kCCPRFHmacAlgSHA1), password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
}

func pbkdf2SHA256(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    return pbkdf2(hash:CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256), password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
}

func pbkdf2SHA512(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    return pbkdf2(hash:CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512), password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
}

func pbkdf2(hash :CCPBKDFAlgorithm, password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    let passwordData = password.data(using:String.Encoding.utf8)!
    var derivedKeyData = Data(repeating:0, count:keyByteCount)

    let derivationStatus = derivedKeyData.withUnsafeMutableBytes {derivedKeyBytes in
        salt.withUnsafeBytes { saltBytes in

            CCKeyDerivationPBKDF(
                CCPBKDFAlgorithm(kCCPBKDF2),
                password, passwordData.count,
                saltBytes, salt.count,
                hash,
                UInt32(rounds),
                derivedKeyBytes, derivedKeyData.count)
        }
    }
    if (derivationStatus != 0) {
        print("Error: \(derivationStatus)")
        return nil;
    }

    return derivedKeyData
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

let password     = "password"
//let salt       = "saltData".data(using: String.Encoding.utf8)!
let salt         = Data(bytes: [0x73, 0x61, 0x6c, 0x74, 0x44, 0x61, 0x74, 0x61])
let keyByteCount = 16
let rounds       = 100000

let derivedKey = pbkdf2SHA1(password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
print("derivedKey (SHA1): \(derivedKey! as NSData)")
Run Code Online (Sandbox Code Playgroud)

示例输出:

derivedKey (SHA1): <6b9d4fa3 0385d128 f6d196ee 3f1d6dbf>
Run Code Online (Sandbox Code Playgroud)