当前位置: > > > Swift - 第三方加密库CryptoSwift使用详解3(AES加密与解密)

Swift - 第三方加密库CryptoSwift使用详解3(AES加密与解密)

(本文代码已升级至Swift4)

六、AES 加密与解密

1,AES 介绍

  • 高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称 Rijndael 加密法,是美国联邦政府采用的一种区块加密标准。
  • 该标准是用来替代原先的 DES,现已经被多方分析且广为全世界所使用,成为对称密钥加密中最流行的算法之一。
  • AES 采用对称分组密码体制,加密数据块分组长度必须为 128 比特,密钥长度可以是 128 比特、192 比特、256 比特中的任意一个(如果数据块及密钥长度不足时,会补齐)

2,加密模式介绍

AES 作为一种分组加密算法为了适应不同的安全性要求和传输需求允许在多种不同的加密模式下工作:

(1)ECB 模式(电子密码本模式:Electronic codebook
  • ECB 是最简单的块密码加密模式,加密前根据加密块大小(如 AES 128 位)分成若干块,之后将每块使用相同的密钥单独加密,解密同理。
  • ECB 模式由于每块数据的加密是独立的因此加密和解密都可以并行计算。
  • ECB 模式最大的缺点是相同的明文块会被加密成相同的密文块,这种方法在某些环境下不能提供严格的数据保密性。

(2)CBC 模式(密码分组链接:Cipher-block chaining
  • CBC 模式对于每个待加密的密码块在加密前会先与前一个密码块的密文异或然后再用加密器加密。第一个明文块与一个叫初始化向量的数据块异或。
  • CBC 模式相比 ECB 有更高的保密性,但由于对每个数据块的加密依赖于前一个数据块的加密,所以加密无法并行。
  • CBC 模式与 ECB 一样在加密前需要对数据进行填充,不是很适合对流数据进行加密。 

(3)CTR 模式(计算器模式:Counter
  • 计算器模式不常见,在 CTR 模式中, 有一个自增的算子,这个算子用密钥加密之后的输出和明文异或的结果得到密文,相当于一次一密。
  • 这种加密方式简单快速,安全可靠,而且可以并行加密。
  • 但是在计算器不能维持很长的情况下,密钥只能使用一次。

(4)CFB 模式(密文反馈:Cipher feedback
  • ECB CBC 模式只能够加密块数据不同,CFB 能够将块密文(Block Cipher)转换为流密文(Stream Cipher)。
  • CFB 的加密工作分为两部分:先将前一段加密得到的密文再加密;接着将第 1 步加密得到的数据与当前段的明文异或。
  • 由于加密流程和解密流程中被块加密器加密的数据是前一段密文,因此即使明文数据的长度不是加密块大小的整数倍也是不需要填充的,这保证了数据长度在加密前后是相同的。
  • CFB 模式非常适合对流数据进行加密,解密可以并行计算。

(5)OFB 模式(输出反馈:Output feedback
  • OFB 是先用块加密器生成密钥流(Keystream),然后再将密钥流与明文流异或得到密文流,解密是先用块加密器生成密钥流,再将密钥流与密文流异或得到明文,由于异或操作的对称性所以加密和解密的流程是完全一样的。
  • OFB CFB 一样都非常适合对流数据的加密。
  • OFB 由于加密和解密都依赖与前一段数据,所以加密和解密都不能并行。

3,关于密钥长度

(1)在进行 AES 加密时,CryptoSwift 会根据密钥的长度自动选择对应的加密算法(AES128, AES192, AES256
  • AES-128 = 16 bytes
  • AES-192 = 24 bytes
  • AES-256 = 32 bytes

(2)这里我们以 ECB 模式为例。由于密钥为 16 个字符(字节),则自动采用 aes128 加密。
do {
    let str = "欢迎访问 hangge.com"
    print("原始字符串:\(str)")
    
    let key = "hangge.com123456"
    print("key密钥:\(key)")
    
    //使用AES-128-ECB加密模式
    let aes = try AES(key: key.bytes, blockMode: ECB())
    
    //开始加密
    let encrypted = try aes.encrypt(str.bytes)
    let encryptedBase64 = encrypted.toBase64() //将加密结果转成base64形式
    print("加密结果(base64):\(encryptedBase64!)")
    
    //开始解密1(从加密后的字符数组解密)
    let decrypted1 = try aes.decrypt(encrypted)
    print("解密结果1:\(String(data: Data(decrypted1), encoding: .utf8)!)")
    
    //开始解密2(从加密后的base64字符串解密)
    let decrypted2 = try encryptedBase64?.decryptBase64ToString(cipher: aes)
    print("解密结果2:\(decrypted2!)")
} catch { }

运行结果如下:

(3)如果密钥长度不够 1624 32 字节,可以使用 zeroPadding 将其补齐至 blockSize 的整数倍。
zeroPadding 补齐规则:
将长度补齐至 blockSize 参数的整数倍。比如我们将 blockSize 设置为 AES.blockSize16)。
  • 如果长度小于 16 字节:则尾部补 0,直到满足 16 字节。
  • 如果长度大于等于 16 字节,小于 32 字节:则尾部补 0,直到满足 32 字节。
  • 如果长度大于等于 32 字节,小于 48 字节:则尾部补 0,直到满足 48 字节。 以此类推......
这里还是以 ECB 模式为例,假设我们密钥只有 9 个字节,通过 zeroPadding 补齐到 16 个字节。
do {
    let str = "欢迎访问 hangge.com"
    print("原始字符串:\(str)")
    
    let key = "hangge666"
    print("key密钥:\(key)")
    
    //使用AES-128-ECB加密模式
    let aes = try AES(key: Padding.zeroPadding.add(to: key.bytes, blockSize: AES.blockSize),
                      blockMode: ECB())
    
    //开始加密
    let encrypted = try aes.encrypt(str.bytes)
    let encryptedBase64 = encrypted.toBase64() //将加密结果转成base64形式
    print("加密结果(base64):\(encryptedBase64!)")
    
    //开始解密1(从加密后的字符数组解密)
    let decrypted1 = try aes.decrypt(encrypted)
    print("解密结果1:\(String(data: Data(decrypted1), encoding: .utf8)!)")
    
    //开始解密2(从加密后的base64字符串解密)
    let decrypted2 = try encryptedBase64?.decryptBase64ToString(cipher: aes)
    print("解密结果2:\(decrypted2!)")
} catch { }
运行结果如下:

4,CBC 模式的便捷写法

(1)我们知道使用 CBC 模式加密时,除了提供一个密钥(key)外,还需要一个密钥偏移量(iv)。这个 Cipher 的完整写法如下:
do {
    let str = "欢迎访问 hangge.com"
    print("原始字符串:\(str)")
    
    let key = "hangge.com123456"
    print("key密钥:\(key)")
    
    let iv = "1234567890123456"
    print("密钥偏移量:\(iv)")
    
    //使用AES-128-CBC加密模式
    let aes = try AES(key: key.bytes, blockMode: CBC(iv: iv.bytes))
    
    //开始加密
    let encrypted = try aes.encrypt(str.bytes)
    let encryptedBase64 = encrypted.toBase64() //将加密结果转成base64形式
    print("加密结果(base64):\(encryptedBase64!)")
    
    //开始解密1(从加密后的字符数组解密)
    let decrypted1 = try aes.decrypt(encrypted)
    print("解密结果1:\(String(data: Data(decrypted1), encoding: .utf8)!)")
    
    //开始解密2(从加密后的base64字符串解密)
    let decrypted2 = try encryptedBase64?.decryptBase64ToString(cipher: aes)
    print("解密结果2:\(decrypted2!)")
} catch { }
运行结果如下:

(2)CBC 模式的 Cipher 还可以这么写,效果同上面的那个是一样的。
do {
    let str = "欢迎访问 hangge.com"
    print("原始字符串:\(str)")
    
    let key = "hangge.com123456"
    print("key密钥:\(key)")
    
    let iv = "1234567890123456"
    print("密钥偏移量:\(iv)")
    
    //使用AES-128-CBC加密模式
    let aes = try AES(key: key, iv: iv)
    
    //开始加密
    let encrypted = try aes.encrypt(str.bytes)
    let encryptedBase64 = encrypted.toBase64() //将加密结果转成base64形式
    print("加密结果(base64):\(encryptedBase64!)")
    
    //开始解密1(从加密后的字符数组解密)
    let decrypted1 = try aes.decrypt(encrypted)
    print("解密结果1:\(String(data: Data(decrypted1), encoding: .utf8)!)")
    
    //开始解密2(从加密后的base64字符串解密)
    let decrypted2 = try encryptedBase64?.decryptBase64ToString(cipher: aes)
    print("解密结果2:\(decrypted2!)")
} catch { }
 

5,随机生成密钥偏移量

除了 ECB 模式外,其它模式不仅需要提供密钥(key),还需要一个密钥偏移量(iv)。如果觉得自己定义 iv 麻烦,可以通过 AES.randomIV() 方法来自动生成。
//创建一个16字节的随机密钥偏移量
let iv = AES.randomIV(AES.blockSize)
print(iv)
运行结果如下:

6,String 的加密与解密

(1)上面的样例中,我们都是先定义一个 Cipher,然后使用这个 Cipher encrypt decrypt 方法对字符串进行加密与解密。但这过程中我们还需要手动进行一些数据转换工作:
  • 加密时需要先将字符串转为字节数组([UInt8])。加密后又需要将结果从字节数组转为 base64 的字符串形式。
  • 解密后同样需要将结果从字节数组转为字符串形式。
//使用AES-128-CBC加密模式的Cipher
let aes = try AES(key: key, iv: iv)

//方式一
let str = "欢迎访问 hangge.com"
            
let encrypted = try aes.encrypt(str.bytes)
print("加密结果(base64):\(encrypted.toBase64()!)")

let decrypted = try aes.decrypt(encrypted)
print("解密结果:\(String(data: Data(decrypted), encoding: .utf8)!)")

(2)而 CryptoSwift 其实对 String 做了扩展,增加了许多加密解密的相关方法。我们只需要传入 Cipher 即可,不再需要进行这些数据转换工作了。
//使用AES-128-CBC加密模式的Cipher
let aes = try AES(key: key, iv: iv)

//方式二
let str = "欢迎访问 hangge.com"

let encrypted = try str.encryptToBase64(cipher: aes)!
print("加密结果(base64):\(encrypted)")
            
let decrypted = try encrypted.decryptBase64ToString(cipher: aes)
print("解密结果:\(decrypted)")

7,增量更新

使用 Cryptor 实例可以进行增量操作,即每次只加密或解密一部分数据,这样对于大文件可以有效地节省内存占用。
do {
    //创建一个用于增量加密的Cryptor实例
    var encryptor = try AES(key: "hangge.com123456", iv: "drowssapdrowssap").makeEncryptor()
    
    var ciphertext = Array<UInt8>()
    //合并每个部分的结果
    ciphertext += try encryptor.update(withBytes: Array("欢迎访问".utf8))
    ciphertext += try encryptor.update(withBytes: Array(" ".utf8))
    ciphertext += try encryptor.update(withBytes: Array("hangge.com".utf8))
    //结束
    ciphertext += try encryptor.finish()
    
    //输出完整的结果(base64字符串形式)
    print(ciphertext.toBase64()!)
} catch {
    print(error)
}
运行结果如下:

8,补码方式(padding)

(1)默认情况下,CryptoSwift 使用 PKCS7 进行填充。比如下面两个定义方式是一样的。
let aes1 = try AES(key: key, blockMode: CBC(iv: iv))
        
let aes2 = try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7)

(2)我们也可以根据需求改成其它的填充方式。具体有:
  • pkcs5
  • pkcs7
  • zeroPadding
  • noPadding
评论1
  • 1楼
    2018-09-06 15:52
    WhatsXie

    我有一个已经解base64的字符串,我要用什么方法解密这个字符串啊

    站长回复

    我在文章里有写到,具体见“6,String 的加密与解密”