2016-12-02 50 views
2

我正在爲在他的計算機上存儲客戶端信息的朋友的數據管理員工作。信息是公開記錄,因此最高安全性不是什麼大問題(他目前以純文本形式存儲),但他不介意進行某種加密。沒有詳細的程序,我不得不過分簡化我的問題...使用Swift,加密存儲對象的最佳方法是什麼?

如果我想加密文本對象或圖片對象,並在寫入磁盤之前用密碼鎖定它,我最好的選擇是什麼?我計劃在macOS和iOS上實現這一點,以便他可以發送文件並將其分享給任何人/任何設備。所以對於iOS來說,速度會是一件好事,並且保持文件的大小對於移動數據的使用是有利的。

我是蘋果開發新手,所以我仍在篩選API和框架來學習一切,所以我很抱歉問什麼可能是一個簡單的問題。這也是決賽的一週,所以我只在業餘時間工作,總是感激一些幫助。

謝謝! -Sanders

+0

SO是不是真的喜歡這個一般性問題。網上有大量的信息,教程和建議。我相信你會弄明白的。如果您稍後有關於您的代碼的詳細問題,請回來;所以人們會很樂意提供幫助。祝你們總決賽好運! –

+0

哦,我沒有意識到我在這裏做錯了事。對於那個很抱歉!我會確保我未來的問題更具體。此外,感謝您選擇正確答案和聲譽的提示 - 正如您所看到的,我沒有花太多時間在SO上,並且仍然在學習如何進行下去。 – Sanders0492

回答

3

對稱加密用於加密數據,而AES(高級加密標準)是當前的標準。 Apple在Security.framework中提供Common Crypto,並在iOS上快速使用適用的OSX指令和硬件加密。另外將密鑰存儲在鑰匙串中。從棄用文檔部分

實施例:

AES加密CBC模式與隨機IV(SWIFT 3+)

的IV被前綴到加密數據

aesCBC128Encrypt將創建一個隨機IV並以加密碼爲前綴。
aesCBC128Decrypt將在解密期間使用前綴IV。

輸入是數據,鍵是數據對象。如果需要的話,如Base64等編碼形式轉換爲和/或從調用方法中轉換。

密鑰的長度應該正好是128位(16字節),192位(24字節)或256位(32字節)。如果使用其他密鑰大小,則會引發錯誤。

PKCS#7 padding默認設置。

這個例子需要公共的密碼
這是需要有一個橋接報的項目:
#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; 
} 

實例:

let clearData = "clearData".data(using:String.Encoding.utf8)! 
let keyData = "keyData89".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)") 
} 

示例輸出:

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> 

注:
與CBC模式示例代碼的一個典型問題是,它離開隨機的創建和共享IV給用戶。這個例子包括生成IV,在加密數據前加上前綴,並在解密時使用前綴IV。這使臨時用戶免於CBC mode所需的細節。

爲了安全,加密的數據也應該有認證,這個示例代碼沒有規定爲了小,並且允許其他平臺更好的互操作性。

還缺少的是從密碼中得到密鑰的關鍵推導,建議使用PBKDF2是將文本密碼用作密鑰材料。

要獲得穩健的生產就緒多平臺加密代碼,請參見RNCryptor。 同時考慮RNCryptor的完整實現,包括密碼派生,加密驗證和版本控制。

注意密碼和密鑰:

密碼和密鑰之間有區別。 AES加密密鑰恰好是3種長度中的一種:128,192或256位,並且看起來應該是隨機位。密碼/密碼是人類可讀的文本。當使用密碼時,加密密鑰需要從中派生出來,還有一些功能可以實現,例如PBKDF2(基於密碼的密鑰派生功能2)。 RNCryptor包含這樣的派生函數。

基於密碼的密鑰派生2(SWIFT 3+)

基於密碼的密鑰派生可以用於從密碼文本加密密鑰和保存用於身份驗證密碼均可使用。

有幾種散列算法可以使用,包括本示例代碼提供的SHA1,SHA256,SHA512。

rounds參數用於使計算速度變慢,以便攻擊者必須在每次嘗試中花費大量時間。典型的延遲值在100ms到500ms之間,如果有不可接受的性能,可以使用較短的值。

這個例子需要公共的密碼
這是需要有一個橋接報的項目:
#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 
} 

用法示例:

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)") 

輸出示例:

derivedKey (SHA1): <6b9d4fa3 0385d128 f6d196ee 3f1d6dbf> 
+0

謝謝! 「密鑰」是指用戶選擇的密碼正確嗎? – Sanders0492

+0

有關密碼和鑰匙的信息,請參閱答案的註釋。 – zaph

+0

這就是我問的原因!再次感謝,我非常感謝幫助。所以,密鑰可以從密碼派生出來,我創建一個基於選定密碼的密鑰並用它來加密文件。那麼只要密鑰派生過程是相同的,另一臺計算機上的另一個用戶可以輸入密碼來訪問該文件? – Sanders0492

相關問題