IDZSwiftCommonCrypto

Carthage compatible Build Status Coverage Status

Apple的CommonCrypto库的Swift封装。

IDZSwiftCommonCrypto 可以与 CocoaPods 和 Carthage 一起使用。有关如何将其安装到您的项目的更多详细信息,请参阅INSTALL.md

如果您使用的是 CocoaPods,则在升级 Xcode 后必须使用 pod cache clean IDZSwiftCommonCrypto --all。 这是为了避免使用 CocoaPods 缓存中的过期模块映射。 仅删除您的 Podfile.lock 和 Pods 目录是不够的。

IDZSwiftCommonCrypto 提供了以下类

使用哪个版本

您使用的版本取决于您当前使用的 Xcode 和 Swift 版本。 请参考下面的列表

使用 Digest

要计算消息摘要,您需要创建一个 Digest 实例,使用要计算摘要的数据调用一次或多次 update,最后调用 final 来获取摘要本身。

update 方法可以接受 String

let  s = "The quick brown fox jumps over the lazy dog."
var md5s2 : Digest = Digest(algorithm:.MD5)
md5s2.update(s)
let digests2 = md5s2.final()

// According to Wikipedia this should be
// e4d909c290d0fb1ca068ffaddf22cbd0
hexStringFromArray(digests2)
assert(digests2 == arrayFromHexString("e4d909c290d0fb1ca068ffaddf22cbd0"))

或者一个 UInt8 元素的数组

let b : [UInt8] = 
[0x54,0x68,0x65,0x20,0x71,0x75,0x69,0x63,
0x6b,0x20,0x62,0x72,0x6f,0x77,0x6e,0x20,
0x66,0x6f,0x78,0x2e]
var md5s1 : Digest = Digest(algorithm:.MD5)
md5s1.update(b)
let digests1 = md5s1.final()

如果您只有一个缓冲区,您可以简单地写入

  var digests3 = Digest(algorithm: .md5).update(b)?.final() // digest is of type [UInt8]?

或者

  var digests4 = Digest(algorithm: .md5).update(s)?.final() // digest is of type [UInt8]?

支持的算法

Digest 类支持以下算法

使用 HMAC

计算密钥哈希消息身份验证码 (HMAC) 与计算消息摘要非常相似,不同之处在于初始化例程现在需要一个密钥以及一个算法参数。

var keys5 = arrayFrom(hexString: "0102030405060708090a0b0c0d0e0f10111213141516171819")
var datas5 : [UInt8] = Array(count:50, repeatedValue:0xcd)
var expecteds5 = arrayFrom(hexString: "4c9007f4026250c6bc8414f9bf50c86c2d7235da")
var hmacs5 = HMAC(algorithm:.sha1, key:keys5).update(datas5)?.final()

// RFC2202 says this should be 4c9007f4026250c6bc8414f9bf50c86c2d7235da
let expectedRFC2202 = arrayFrom(hexString: "4c9007f4026250c6bc8414f9bf50c86c2d7235da")
assert(hmacs5! == expectedRFC2202)

支持的算法

使用 Cryptor

let algorithm = Cryptor.Algorithm.aes
var iv = try! Random.generateBytes(byteCount: algorithm.blockSize())
var key = arrayFrom(hexString: "2b7e151628aed2a6abf7158809cf4f3c")
var plainText = "The quick brown fox jumps over the lazy dog. The fox has more or less had it at this point."

var cryptor = Cryptor(operation:.encrypt, algorithm:algorithm, options:.PKCS7Padding, key:key, iv:iv)
var cipherText = cryptor.update(plainText)?.final()

cryptor = Cryptor(operation:.decrypt, algorithm:algorithm, options:.PKCS7Padding, key:key, iv:iv)
var decryptedPlainText = cryptor.update(cipherText!)?.final()
var decryptedString = String(bytes: decryptedPlainText!, encoding: .utf8)
decryptedString
assert(decryptedString == plainText)

支持的算法

使用 StreamCryptor

要加密大型文件或网络流,请使用 StreamCryptorStreamCryptor 类不会累积加密或解密的数据,而是每次调用 update 都会产生一个输出缓冲区。

下面的示例展示了如何使用 StreamCryptor 来加密和解密图像文件。

func crypt(sc : StreamCryptor,  inputStream: InputStream, outputStream: OutputStream, bufferSize: Int) -> (bytesRead: Int, bytesWritten: Int)
{
    var inputBuffer = Array<UInt8>(repeating:0, count:1024)
    var outputBuffer = Array<UInt8>(repeating:0, count:1024)


    var cryptedBytes : Int = 0
    var totalBytesWritten = 0
    var totalBytesRead = 0
    while inputStream.hasBytesAvailable
    {
        let bytesRead = inputStream.read(&inputBuffer, maxLength: inputBuffer.count)
        totalBytesRead += bytesRead
        let status = sc.update(bufferIn: inputBuffer, byteCountIn: bytesRead, bufferOut: &outputBuffer, byteCapacityOut: outputBuffer.count, byteCountOut: &cryptedBytes)
        assert(status == Status.success)
        if(cryptedBytes > 0)
        {
            let bytesWritten = outputStream.write(outputBuffer, maxLength: Int(cryptedBytes))
            assert(bytesWritten == Int(cryptedBytes))
            totalBytesWritten += bytesWritten
        }
    }
    let status = sc.final(bufferOut: &outputBuffer, byteCapacityOut: outputBuffer.count, byteCountOut: &cryptedBytes)    
    assert(status == Status.success)
    if(cryptedBytes > 0)
    {
        let bytesWritten = outputStream.write(outputBuffer, maxLength: Int(cryptedBytes))
        assert(bytesWritten == Int(cryptedBytes))
        totalBytesWritten += bytesWritten
    }
    return (totalBytesRead, totalBytesWritten)
}

let imagePath = Bundle.main.path(forResource: "Riscal", ofType:"jpg")!
let tmp = NSTemporaryDirectory() as NSString
let encryptedFilePath = "\(tmp)/Riscal.xjpgx"
var decryptedFilePath = "\(tmp)/RiscalDecrypted.jpg"

// Prepare the input and output streams for the encryption operation
guard let imageInputStream = InputStream(fileAtPath: imagePath) else {
    fatalError("Failed to initialize the image input stream.")
}
imageInputStream.open()
guard let  encryptedFileOutputStream = OutputStream(toFileAtPath: encryptedFilePath, append:false) else
{
    fatalError("Failed to open output stream.")
}
encryptedFileOutputStream.open()

// Generate a new, random initialization vector
let initializationVector = try! Random.generateBytes(byteCount: algorithm.blockSize())

// A common way to communicate the initialization vector is to write it at the beginning of the encrypted data.
let bytesWritten = encryptedFileOutputStream.write(initializationVector, maxLength: initializationVector.count)

// Now write the encrypted data
var sc = StreamCryptor(operation:.encrypt, algorithm:algorithm, options:.PKCS7Padding, key:key, iv:initializationVector)
guard  bytesWritten == initializationVector.count else
{
    fatalError("Failed to write initialization vector to encrypted output file.")
}
let outputResult = crypt(sc: sc, inputStream: imageInputStream, outputStream: encryptedFileOutputStream, bufferSize: 1024)
encryptedFileOutputStream.close()
outputResult

// Uncomment this line to verify that the file is encrypted
//var encryptedImage = NSImage(contentsOfFile:encryptedFile)

// Prepare the input and output streams for the decryption operation
guard let encryptedFileInputStream = InputStream(fileAtPath: encryptedFilePath) else
{
    fatalError("Failed to open the encrypted file for input.")
}
encryptedFileInputStream.open()
guard let decryptedFileOutputStream = OutputStream(toFileAtPath: decryptedFilePath, append:false) else {
    fatalError("Failed to open the file for the decrypted output file.")
}
decryptedFileOutputStream.open()

// Read back the initialization vector.
var readbackInitializationVector = Array<UInt8>(repeating: 0, count: algorithm.blockSize())
let bytesRead = encryptedFileInputStream.read(&readbackInitializationVector, maxLength: readbackInitializationVector.count)

// Uncomment this to verify that we did indeed read back the initialization vector.
//assert(readbackInitializationVector == initializationVector)

// Now use the read back initialization vector (along with the key) to
sc = StreamCryptor(operation:.decrypt, algorithm:algorithm, options:.PKCS7Padding, key:key, iv:readbackInitializationVector)
let inputResult = crypt(sc: sc, inputStream: encryptedFileInputStream, outputStream: decryptedFileOutputStream, bufferSize: 1024)

// Uncomment this to verify that decrypt operation consumed all the encrypted data
// and produced the correct number of bytes of plaintext output.
//assert(inputResult.bytesRead == outputResult.bytesWritten && inputResult.bytesWritten == outputResult.bytesRead)

var image = NSImage(named:"Riscal.jpg")
var decryptedImage = NSImage(contentsOfFile:decryptedFilePath)
decryptedImage

使用 PBKDF

PBKDF 类提供了一种从用户密码派生密钥的方法。 以下示例派生一个 20 字节的密钥

let keys6 = PBKDF.deriveKey("password", salt: "salt", prf: .SHA1, rounds: 1, derivedKeyLength: 20)
// RFC 6070 - Should derive 0c60c80f961f0e71f3a9b524af6012062fe037a6
let expectedRFC6070 = arrayFrom(hexString: "0c60c80f961f0e71f3a9b524af6012062fe037a6")
assert(keys6 == expectedRFC6070)

支持的伪随机函数