Bootstrap

iOS AES/CBC/CTR加解密以及AES-CMAC

感觉iOS自带的CryptoKit不好用,有个第三方库CryptoSwift还不错,好巧不巧,清理过Xcode缓存后死活下载不下来,当然也可以自己编译个Framework,但是偏偏不想用第三方库了,于是研究了一下,自带的CommonCrypto也可以达到项目需求。

代码主要包含以下算法:

AES128/CBC/NoPadding

AES128/CTR/NoPadding

AES-CMAC

import Foundation
import CommonCrypto

class AESUtil {
    
    private init(){}

    ///
    ///AES-CMAC
    ///
    static func CMAC(key: Data, data: Data) -> Data? {
        let blockSize = 16
        var subKey1 = Data(count: blockSize)
        var subKey2 = Data(count: blockSize)

        // Step 1: Generate subkeys
        guard generateSubKeys(key: key, subKey1: &subKey1, subKey2: &subKey2) else {
            return nil
        }

        // Step 2: Calculate the number of blocks
        let blockCount = (data.count + blockSize - 1) / blockSize

        // Step 3: Process each block
        var lastBlock = Data(count: blockSize)
        for i in 0..<blockCount {
            let blockRange = i * blockSize..<min((i + 1) * blockSize, data.count)
            var block = data.subdata(in: blockRange)

            if i == blockCount - 1 {
                if block.count < blockSize {
                    block.append(0x80)
                    while block.count < blockSize {
                        block.append(0x00)
                    }
                    block = xor(data: block, with: subKey2)
                } else {
                    block = xor(data: block, with: subKey1)
                }
            }

            lastBlock = xor(data: lastBlock, with: block)
            lastBlock = CBC(key: key, data: lastBlock, isEncrypt: true)!
        }

        return lastBlock
    }

    private static func generateSubKeys(key: Data, subKey1: inout Data, subKey2: inout Data) -> Bool {
        let blockSize = 16
        let zeroBlock = Data(count: blockSize)

        guard let L = CBC(key: key, data: zeroBlock, isEncrypt: true) else {
            return false
        }

        subKey1 = generateSubKey(block: L)
        subKey2 = generateSubKey(block: subKey1)

        return true
    }

    private static func generateSubKey(block: Data) -> Data {
        let blockSize = 16
        var subKey = Data(count: 16)

        var overflow = false
        for i in (0..<blockSize).reversed() {
            let byte = block[i]
            let shiftedByte = byte << 1
            subKey[i] = shiftedByte | (overflow ? 1 : 0)
            overflow = (byte & 0x80) != 0
        }

        if overflow {
            subKey[blockSize - 1] ^= 0x87
        }

        return subKey
    }


    private static func xor(data: Data, with other: Data) -> Data {
        var result = Data(count: data.count)
        for i in 0..<data.count {
            result[i] = data[i] ^ other[i]
        }
        return result
    }
    
    ///
    ///AES128/CBC/NoPadding加解密
    ///
    ///@param isEncrypt true加密,false解密
    ///
    static func CBC(key: Data, data: Data, isEncrypt: Bool) -> Data? {
        return AES128NoPadding(key: key, iv: Data(count: 16), data: data, mode: "CBC", isEncrypt: isEncrypt)
    }
    
    
    ///
    ///AES128/CTR/NoPadding加解密
    ///
    ///@param isEncrypt true加密,false解密
    ///
    static func CTR(key: Data, data: Data, isEncrypt: Bool) -> Data? {
        return AES128NoPadding(key: key, iv: Data(count: 16), data: data, mode: "CTR", isEncrypt: isEncrypt)
    }
    
    ///
    ///AES128/NoPadding加解密
    ///
    ///@param mode 支持CBC、CTR
    ///@param isEncrypt true加密,false解密
    ///
    static func AES128NoPadding(key: Data, iv: Data, data: Data, mode: String, isEncrypt: Bool) -> Data? {
        let bufferLength = data.count + kCCKeySizeAES128
        var buffer = Data(count: bufferLength)
        var numBytesEncrypted: size_t = 0
        
        let operation = isEncrypt ? kCCEncrypt : kCCDecrypt
        
        let cryptStatus: CCCryptorStatus = buffer.withUnsafeMutableBytes { (bufferPtr: UnsafeMutableRawBufferPointer) in
            key.withUnsafeBytes { (keyPtr: UnsafeRawBufferPointer) in
                iv.withUnsafeBytes { (ivPtr: UnsafeRawBufferPointer) in
                    data.withUnsafeBytes { (dataPtr: UnsafeRawBufferPointer) in
                        //调用加密函数
                        var modeSource = 0
                        if mode == "CBC" {
                            modeSource = kCCModeCBC
                        } else if mode == "CTR" {
                            modeSource = kCCModeCTR
                        }
                        let cryptorRef = UnsafeMutablePointer<CCCryptorRef?>.allocate(capacity: 1)
                        var status = CCCryptorCreateWithMode(CCOperation(operation), CCMode(modeSource), CCAlgorithm(kCCAlgorithmAES), CCPadding(ccNoPadding), ivPtr.baseAddress, keyPtr.baseAddress, kCCKeySizeAES128, nil, 0, 0, CCModeOptions(0), cryptorRef)
                        
                        if status == kCCSuccess {
                            status = CCCryptorUpdate(cryptorRef.pointee, dataPtr.baseAddress, data.count, bufferPtr.baseAddress, bufferLength, &numBytesEncrypted)
                        } else {
                            print("CCCryptorCreateWithMode fail: \(encryptError(status))")
                        }
                        return status
                    }
                }
            }
        }
        if cryptStatus == kCCSuccess {
            buffer.removeSubrange(numBytesEncrypted..<bufferLength)
            return buffer
        }
        print("AES/\(mode)/NoPadding加解密失败: \(encryptError(cryptStatus))")
        return nil
    }
    
    
    private static func encryptError(_ status: CCCryptorStatus)-> String {
        if status == kCCParamError {
            return "kCCParamError"
        } else if status == kCCBufferTooSmall {
            return "kCCBufferTooSmall"
        } else if status == kCCMemoryFailure {
            return "kCCMemoryFailure"
        } else if status == kCCAlignmentError {
            return "kCCAlignmentError"
        } else if status == kCCDecodeError {
            return "kCCDecodeError"
        } else if status == kCCUnimplemented {
            return "kCCUnimplemented"
        } else if status == kCCOverflow {
            return "kCCOverflow"
        } else if status == kCCRNGFailure {
            return "kCCRNGFailure"
        } else if status == kCCUnspecifiedError {
            return "kCCUnspecifiedError"
        } else if status == kCCCallSequenceError {
            return "kCCCallSequenceError"
        } else if status == kCCKeySizeError {
            return "kCCKeySizeError"
        } else if status == kCCInvalidKey {
            return "kCCInvalidKey"
        }
        return "\(status)"
    }
}
;