AES加密和解密

38

我用Swift编写了一个应用程序,需要AES加密和解密功能。我从另一个.Net解决方案接收到加密数据,但找不到对其进行处理的方法。

这是我的.Net加密:

 public static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
    {
        byte[] encryptedBytes = null;

        byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };

        using (MemoryStream ms = new MemoryStream())
        {
            using (RijndaelManaged AES = new RijndaelManaged())
            {
                AES.KeySize = 256;
                AES.BlockSize = 128;

                var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
                AES.Key = key.GetBytes(AES.KeySize / 8);
                AES.IV = key.GetBytes(AES.BlockSize / 8);

                AES.Mode = CipherMode.CBC;

                using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
                    cs.Close();
                }
                encryptedBytes = ms.ToArray();
            }
        }

        return encryptedBytes;
    }

我需要在Swift中解密函数。


我找到了这个,https://github.com/Pakhee/Cross-platform-AES-encryption 我可以将Objective-C文件添加到我的项目中,但是在调用方法和返回值方面遇到了很多问题,Swift和Objective-C不兼容:( - mehr
你找到解决方案了吗?我也需要一个适用于iOS的解决方案。 - Abdurrashid Khatri
12个回答

36

CryptoSwift 示例

已更新至 Swift 2

import Foundation
import CryptoSwift

extension String {
    func aesEncrypt(key: String, iv: String) throws -> String{
        let data = self.dataUsingEncoding(NSUTF8StringEncoding)
        let enc = try AES(key: key, iv: iv, blockMode:.CBC).encrypt(data!.arrayOfBytes(), padding: PKCS7())
        let encData = NSData(bytes: enc, length: Int(enc.count))
        let base64String: String = encData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0));
        let result = String(base64String)
        return result
    }

    func aesDecrypt(key: String, iv: String) throws -> String {
        let data = NSData(base64EncodedString: self, options: NSDataBase64DecodingOptions(rawValue: 0))
        let dec = try AES(key: key, iv: iv, blockMode:.CBC).decrypt(data!.arrayOfBytes(), padding: PKCS7())
        let decData = NSData(bytes: dec, length: Int(dec.count))
        let result = NSString(data: decData, encoding: NSUTF8StringEncoding)
        return String(result!)
    }
}

使用方法:

let key = "bbC2H19lkVbQDfakxcrtNMQdd0FloLyw" // length == 32
let iv = "gqLOHUioQ0QjhuvI" // length == 16
let s = "string to encrypt"
let enc = try! s.aesEncrypt(key, iv: iv)
let dec = try! enc.aesDecrypt(key, iv: iv)
print(s) // string to encrypt
print("enc:\(enc)") // 2r0+KirTTegQfF4wI8rws0LuV8h82rHyyYz7xBpXIpM=
print("dec:\(dec)") // string to encrypt
print("\(s == dec)") // true

确保您的iv长度为16,密钥长度为32,这样就不会出现“块大小和初始化向量必须具有相同长度!”错误。


当进行加密时,此代码会抛出错误“块大小和初始化向量必须具有相同的长度!” - Deekor
@Deekor 我刚刚添加了用法示例。iv和key需要具有正确的长度。iv为16,key为32。 - yutelin
1
有没有一种方法可以从密钥值创建IV? - Fred Faust
4
请注意,CryptoSwift 的速度比 Common Crypto 慢 500 到 1000 倍。 - zaph
有没有没有iv的示例?如果有,你能在这里分享一下吗? - Dinesh
显示剩余6条评论

33

CryptoSwift 示例

更新 SWIFT 4.*

func aesEncrypt() throws -> String {
    let encrypted = try AES(key: KEY, iv: IV, padding: .pkcs7).encrypt([UInt8](self.data(using: .utf8)!))
    return Data(encrypted).base64EncodedString()
}

func aesDecrypt() throws -> String {
    guard let data = Data(base64Encoded: self) else { return "" }
    let decrypted = try AES(key: KEY, iv: IV, padding: .pkcs7).decrypt([UInt8](data))
    return String(bytes: decrypted, encoding: .utf8) ?? self
}

4
最好避免使用CryptoSwift,因为它比基于Common Crypto的实现慢500到1000倍。苹果的Common Crypto已经获得了FIPS认证,因此经过了很好的审查,而使用CryptoSwift则是冒着正确性和安全性的风险。 - zaph
我正在使用它来加密聊天消息。基本上你的意思是这不是一个好主意?我已经使用了一段时间,没有注意到任何不好的地方... - Yaroslav Dukal
8
几乎每一起数据泄露都可能会说“我已经使用了一段时间,并没有注意到任何不好的事情”...但随后发生了不好的事情。这就像安全带,为什么要费心去戴呢。 - zaph
1
@zaph如果您能提供推荐的实现示例,那就太好了。 - Yaroslav Dukal
1
@zaph,你在谈论哪个数据泄露事件?如果不知道用于解密的方法、密钥和IV,你怎么能解密加密字符串呢? - Yaroslav Dukal
显示剩余4条评论

14

SHS提供的代码对我无效,但是这个显然有效(我使用了桥接头:#import <CommonCrypto/CommonCrypto.h>):

extension String {

    func aesEncrypt(key:String, iv:String, options:Int = kCCOptionPKCS7Padding) -> String? {
        if let keyData = key.data(using: String.Encoding.utf8),
            let data = self.data(using: String.Encoding.utf8),
            let cryptData    = NSMutableData(length: Int((data.count)) + kCCBlockSizeAES128) {


            let keyLength              = size_t(kCCKeySizeAES128)
            let operation: CCOperation = UInt32(kCCEncrypt)
            let algoritm:  CCAlgorithm = UInt32(kCCAlgorithmAES128)
            let options:   CCOptions   = UInt32(options)



            var numBytesEncrypted :size_t = 0

            let cryptStatus = CCCrypt(operation,
                                      algoritm,
                                      options,
                                      (keyData as NSData).bytes, keyLength,
                                      iv,
                                      (data as NSData).bytes, data.count,
                                      cryptData.mutableBytes, cryptData.length,
                                      &numBytesEncrypted)

            if UInt32(cryptStatus) == UInt32(kCCSuccess) {
                cryptData.length = Int(numBytesEncrypted)
                let base64cryptString = cryptData.base64EncodedString(options: .lineLength64Characters)
                return base64cryptString


            }
            else {
                return nil
            }
        }
        return nil
    }

    func aesDecrypt(key:String, iv:String, options:Int = kCCOptionPKCS7Padding) -> String? {
        if let keyData = key.data(using: String.Encoding.utf8),
            let data = NSData(base64Encoded: self, options: .ignoreUnknownCharacters),
            let cryptData    = NSMutableData(length: Int((data.length)) + kCCBlockSizeAES128) {

            let keyLength              = size_t(kCCKeySizeAES128)
            let operation: CCOperation = UInt32(kCCDecrypt)
            let algoritm:  CCAlgorithm = UInt32(kCCAlgorithmAES128)
            let options:   CCOptions   = UInt32(options)

            var numBytesEncrypted :size_t = 0

            let cryptStatus = CCCrypt(operation,
                                      algoritm,
                                      options,
                                      (keyData as NSData).bytes, keyLength,
                                      iv,
                                      data.bytes, data.length,
                                      cryptData.mutableBytes, cryptData.length,
                                      &numBytesEncrypted)

            if UInt32(cryptStatus) == UInt32(kCCSuccess) {
                cryptData.length = Int(numBytesEncrypted)
                let unencryptedMessage = String(data: cryptData as Data, encoding:String.Encoding.utf8)
                return unencryptedMessage
            }
            else {
                return nil
            }
        }
        return nil
    }


}

从我的ViewController

 let encoded = message.aesEncrypt(key: keyString, iv: iv)
 let unencode = encoded?.aesDecrypt(key: keyString, iv: iv)

1
运行得很好。然后在我的php代码中使用了以下内容。$ decryptedData = openssl_decrypt(base64_decode($ data),OPENSSL_CIPHER_NAME,$ key,OPENSSL_RAW_DATA,$ iv); - Sean Mayes
#user1094081,你有没有Objective-C的相同代码? - Kirti Nikam
运行得非常好。服务器端使用C#代码和服务端Crypto JS,非常容易实现,因为输入和输出都是字符串。 - Balakrishnan Mca

8

有一个有趣的“纯Swift”开源库:

AES解密示例(来自项目README.md文件):

import CryptoSwift
let setup = (key: keyData, iv: ivData)
let decryptedAES = AES(setup).decrypt(encryptedData)

1
出现以下错误:类型“Cipher”没有成员“AES”。 - Qadir Hussain
@QadirHussain 尝试使用 AES 而不是 Cipher.AES - rolling_codes
在哪里获取ivData的值? - Ravi Sharma

6
我曾使用MihaelIsaev/HMAC.swift的代码通过CommonCrypto生成哈希。这个实现没有使用Bridging-Header,而是创建了模块文件。现在,为了使用AESEncrypt和Decrypt,我直接将这些函数添加到HAMC.swift中的“extension String {”中。
func aesEncrypt(key:String, iv:String, options:Int = kCCOptionPKCS7Padding) -> String? {
    if let keyData = key.dataUsingEncoding(NSUTF8StringEncoding),
        data = self.dataUsingEncoding(NSUTF8StringEncoding),
        cryptData    = NSMutableData(length: Int((data.length)) + kCCBlockSizeAES128) {

            let keyLength              = size_t(kCCKeySizeAES128)
            let operation: CCOperation = UInt32(kCCEncrypt)
            let algoritm:  CCAlgorithm = UInt32(kCCAlgorithmAES128)
            let options:   CCOptions   = UInt32(options)

            var numBytesEncrypted :size_t = 0

            let cryptStatus = CCCrypt(operation,
                algoritm,
                options,
                keyData.bytes, keyLength,
                iv,
                data.bytes, data.length,
                cryptData.mutableBytes, cryptData.length,
                &numBytesEncrypted)

            if UInt32(cryptStatus) == UInt32(kCCSuccess) {
                cryptData.length = Int(numBytesEncrypted)
                let base64cryptString = cryptData.base64EncodedStringWithOptions(.Encoding64CharacterLineLength)
                return base64cryptString
            }
            else {
                return nil
            }
    }
    return nil
}

func aesDecrypt(key:String, iv:String, options:Int = kCCOptionPKCS7Padding) -> String? {
    if let keyData = key.dataUsingEncoding(NSUTF8StringEncoding),
        data = NSData(base64EncodedString: self, options: .IgnoreUnknownCharacters),
        cryptData    = NSMutableData(length: Int((data.length)) + kCCBlockSizeAES128) {

            let keyLength              = size_t(kCCKeySizeAES128)
            let operation: CCOperation = UInt32(kCCDecrypt)
            let algoritm:  CCAlgorithm = UInt32(kCCAlgorithmAES128)
            let options:   CCOptions   = UInt32(options)

            var numBytesEncrypted :size_t = 0

            let cryptStatus = CCCrypt(operation,
                algoritm,
                options,
                keyData.bytes, keyLength,
                iv,
                data.bytes, data.length,
                cryptData.mutableBytes, cryptData.length,
                &numBytesEncrypted)

            if UInt32(cryptStatus) == UInt32(kCCSuccess) {
                cryptData.length = Int(numBytesEncrypted)
                let unencryptedMessage = String(data: cryptData, encoding:NSUTF8StringEncoding)
                return unencryptedMessage
            }
            else {
                return nil
            }
    }
    return nil
}

这些函数是从RNCryptor获取的。在哈希函数和单个文件"HMAC.swift"中很容易添加,而不使用桥接头文件。我希望这对需要哈希和AES加密/解密的Swift开发人员有用。
以下是使用AESDecrypt的示例。
 let iv = "AA-salt-BBCCDD--" // should be of 16 characters.
 //here we are convert nsdata to String
 let encryptedString = String(data: dataFromURL, encoding: NSUTF8StringEncoding)
 //now we are decrypting
 if let decryptedString = encryptedString?.aesDecrypt("12345678901234567890123456789012", iv: iv) // 32 char pass key
 {                    
      // Your decryptedString
 }

请问您能否提供一个使用RNCryptor进行文件加密和解密的演示程序?谢谢。 - Shree Prakash
@ShreePool - 您可以从此链接 https://dev59.com/0mAf5IYBdhLWcg3w9mps#41465726 获取完整的Swift 3.0代码和HMAC使用方法。 - SHS
实际上我想解决这个问题。http://stackoverflow.com/questions/41542890/how-to-make-file-password-protected-in-swift-programatically - Shree Prakash

5

CryptoSwift是一个非常有趣的项目,但目前存在一些AES速度限制。如果您需要进行一些严肃的加密,请小心 - 可能值得去经历桥接实现CommonCrypto的痛苦。

向Marcin致敬,因为他实现了纯Swift版本。


4
是的,使用CryptoSwift进行AES加密比Common Crypto慢500到1000倍,这是因为Common Crypto利用了硬件加密功能而CryptoSwift没有。 - zaph

5
您可以使用iOS上的CommonCrypto或外部库CryptoSwift。下面列出了这两个工具的实现。 也就是说,应该测试CommonCrypto在AES输出方面的表现,因为在CC文档中不清楚它使用哪种AES模式。
在Swift 4.2中使用CommonCrypto:
    import CommonCrypto
func encrypt(data: Data) -> Data { return cryptCC(data: data, key: key, operation: kCCEncrypt) }
func decrypt(data: Data) -> Data { return cryptCC(data: data, key: key, operation: kCCDecrypt) }
private func cryptCC(data: Data, key: String operation: Int) -> Data {
guard key.count == kCCKeySizeAES128 else { fatalError("密钥大小错误!") }
var ivBytes: [UInt8] var inBytes: [UInt8] var outLength: Int
if operation == kCCEncrypt { ivBytes = [UInt8](repeating: 0, count: kCCBlockSizeAES128) guard kCCSuccess == SecRandomCopyBytes(kSecRandomDefault, ivBytes.count, &ivBytes) else { fatalError("IV创建失败!") }
inBytes = Array(data) outLength = data.count + kCCBlockSizeAES128
} else { ivBytes = Array(Array(data).dropLast(data.count - kCCBlockSizeAES128)) inBytes = Array(Array(data).dropFirst(kCCBlockSizeAES128)) outLength = inBytes.count
}
var outBytes = [UInt8](repeating: 0, count: outLength) var bytesMutated = 0
guard kCCSuccess == CCCrypt(CCOperation(operation), CCAlgorithm(kCCAlgorithmAES128), CCOptions(kCCOptionPKCS7Padding), Array(key), kCCKeySizeAES128, &ivBytes, &inBytes, inBytes.count, &outBytes, outLength, &bytesMutated) else { fatalError("加密操作 \(operation) 失败") }
var outData = Data(bytes: &outBytes, count: bytesMutated)
if operation == kCCEncrypt { ivBytes.append(contentsOf: Array(outData)) outData = Data(bytes: ivBytes) } return outData
}
在Swift 4.2中使用CryptoSwift v0.14:
    枚举操作 {
        案例加密
        案例解密
    }
私有的让keySizeAES128等于16 私有的让aesBlockSize等于16
函数加密(data: Data, key: String) -> Data { 返回crypt(data: data, key: key, operation: .encrypt) }
函数解密(data: Data, key: String) -> Data { 返回crypt(data: data, key: key, operation: .decrypt) }
私有函数crypt(data: Data, key: String, operation: Operation) -> Data {
确保key.count等于keySizeAES128,否则 { 致命错误("密钥大小失败!") } var outData: Data? = nil
如果operation == .encrypt { 让ivBytes为长度为aesBlockSize的值全为0的UInt8数组 确保0 == SecRandomCopyBytes(kSecRandomDefault, ivBytes.count, &ivBytes),否则 { 致命错误("IV创建失败!") }
尝试 { 让aes等于尝试用CBC(iv: ivBytes)块模式创建一个AES对象,并以Array(key.data(using: .utf8)!)作为密钥 让encrypted等于尝试对Array(data)进行加密后得到的结果 将encrypted添加到ivBytes中 让outData等于由ivBytes生成的Data对象
} 捕捉 { 打印("加密错误:\ (错误)") }
} else { 让ivBytes等于从data中删除最后一个长度为aesBlockSize的元素的数组 让inBytes等于从data中删除前面aesBlockSize个元素的数组
尝试 { 让aes等于尝试用CBC(iv: ivBytes)块模式创建一个AES对象,并以Array(key.data(using: .utf8)!)作为密钥 让decrypted等于尝试对inBytes进行解密后得到的结果 让outData等于由decrypted生成的Data对象
} 捕捉 { 打印("解密错误:\ (错误)") } } 返回outData! }

在这个答案的 CommonCrypto 版本中,刚发现一个非常大的错误。至少,在 Swift 5.2 中存在问题 - 也许 Swift 4.2 中不存在。问题是只有密钥的第一个字符似乎是重要的。我可以用任何以相同字母开头的相同长度的密钥解密我的消息!问题似乎出在作为 CCCrypt 第四个参数传递的 Array(key) 上。如果我将其替换为 key,它似乎可以正常工作。 - Steven W. Klassen

4

更新Swift 4.2

例如,我们将字符串加密为base64编码的字符串,然后将其解密为可读字符串(这与我们的输入字符串相同)。

在我的情况下,我使用它来加密一个字符串并嵌入到QR码中。然后另一方扫描并解密相同的内容。因此,中间人无法理解QR码。

步骤1:加密字符串“Encrypt My Message 123”

步骤2:加密的base64编码字符串:+yvNjiD7F9/JKmqHTc/Mjg==(同样打印在QR码上)

步骤3:扫描并解密字符串“+yvNjiD7F9/JKmqHTc/Mjg==”

步骤4:得到最终结果 - “Encrypt My Message 123”

加密和解密函数:

func encryption(stringToEncrypt: String) -> String{
    let key = "MySecretPKey"
    //let iv = "92c9d2c07a9f2e0a"
    let data = stringToEncrypt.data(using: .utf8)
    let keyD = key.data(using: .utf8)
    let encr = (data as NSData?)!.aes128EncryptedData(withKey: keyD)
    let base64String: String = (encr as NSData?)!.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
    print(base64String)
    return base64String
}

func decryption(encryptedString:String) -> String{
    let key = "MySecretPKey"
    //let iv = "92c9d2c07a9f2e0a"
    let keyD = key.data(using: .utf8)
    let decrpStr = NSData(base64Encoded: encryptedString, options: NSData.Base64DecodingOptions(rawValue: 0))
    let dec = (decrpStr)!.aes128DecryptedData(withKey: keyD)
    let backToString = String(data: dec!, encoding: String.Encoding.utf8)
    print(backToString!)
    return backToString!
}

用法:

    let enc = encryption(stringToEncrypt: "Encrypt My Message 123")
    let decryptedString = decryption(encryptedString: enc)
    print(decryptedString) 

这些类是用Objective-C编写的,用于支持AES加密功能。因此,如果使用Swift,您需要使用桥接头文件来支持这些功能。

类名称:NSData+AES.h

#import <Foundation/Foundation.h>

@interface NSData (AES)

- (NSData *)AES128EncryptedDataWithKey:(NSData *)key;
- (NSData *)AES128DecryptedDataWithKey:(NSData *)key;
- (NSData *)AES128EncryptedDataWithKey:(NSData *)key iv:(NSData *)iv;
- (NSData *)AES128DecryptedDataWithKey:(NSData *)key iv:(NSData *)iv;

@end

类名:NSData+AES.m

#import "NSData+AES.h"
#import <CommonCrypto/CommonCryptor.h>

@implementation NSData (AES)

- (NSData *)AES128EncryptedDataWithKey:(NSData *)key
{
    return [self AES128EncryptedDataWithKey:key iv:nil];
}

- (NSData *)AES128DecryptedDataWithKey:(NSData *)key
{
    return [self AES128DecryptedDataWithKey:key iv:nil];
}

- (NSData *)AES128EncryptedDataWithKey:(NSData *)key iv:(NSData *)iv
{
    return [self AES128Operation:kCCEncrypt key:key iv:iv];
}

- (NSData *)AES128DecryptedDataWithKey:(NSData *)key iv:(NSData *)iv
{
    return [self AES128Operation:kCCDecrypt key:key iv:iv];
}

- (NSData *)AES128Operation:(CCOperation)operation key:(NSData *)key iv:(NSData *)iv
{
    
    NSUInteger dataLength = [self length];
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    
    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(operation,
                                          kCCAlgorithmAES128,
                                          kCCOptionPKCS7Padding | kCCOptionECBMode,
                                          key.bytes,
                                          kCCBlockSizeAES128,
                                          iv.bytes,
                                          [self bytes],
                                          dataLength,
                                          buffer,
                                          bufferSize,
                                          &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }
    free(buffer);
    return nil;
}

@end

正是我所需要的。谢谢。 - JAnton
解密字符串时得到了 nil 值,你能帮我吗?我按照你的代码进行操作。我只有密钥和加密字符串。 - Raviteja Mathangi

4

Swift4:

let key = "ccC2H19lDDbQDfakxcrtNMQdd0FloLGG" // length == 32
let iv = "ggGGHUiDD0Qjhuvv" // length == 16
func encryptFile(_ path: URL) -> Bool{
    do{
        let data = try Data.init(contentsOf: path)
        let encodedData = try data.aesEncrypt(key: key, iv: iv)
        try encodedData.write(to: path)
        return true
    }catch{
        return false
    }
}

func decryptFile(_ path: URL) -> Bool{
    do{
        let data = try Data.init(contentsOf: path)
        let decodedData = try data.aesDecrypt(key: key, iv: iv)
        try decodedData.write(to: path)
        return true
    }catch{
        return false
    }
}

安装 CryptoSwift

import CryptoSwift
extension Data {
    func aesEncrypt(key: String, iv: String) throws -> Data{
        let encypted = try AES(key: key.bytes, blockMode: CBC(iv: iv.bytes), padding: .pkcs7).encrypt(self.bytes)
        return Data(bytes: encypted)
    }

    func aesDecrypt(key: String, iv: String) throws -> Data {
        let decrypted = try AES(key: key.bytes, blockMode: CBC(iv: iv.bytes), padding: .pkcs7).decrypt(self.bytes)
        return Data(bytes: decrypted)
    }
}

2
我找到了解决方案,这是一个很棒的库。
跨平台256位AES加密/解密。
该项目包含256位AES加密的实现,可在所有平台(C#,iOS,Android)上运行。其中一个关键目标是使AES在所有平台上以简单的实现方式工作。
支持的平台: iOS, Android, Windows(C#)。 https://github.com/Pakhee/Cross-platform-AES-encryption

4
这是一份非常糟糕的库。a) 如果密钥和IV长度不正确,它们会用“null”进行填充。这很糟糕,因为人们可能只是使用密码作为密钥,但是密码没有足够的熵来作为密钥!相反,可以使用PBKDF2或Argon2等方案从密码派生密钥。b) 这个库根本没有提供身份验证。c) IV是基于文本的而不是二进制的,原因未知。d) 用户需要自己管理IV,很可能会搞错。 - Artjom B.
使用该库进行加密和解密时出现问题。 - Rekha.t

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接